日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串匹配(多模式匹配篇)

發(fā)布時間:2023/12/3 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串匹配(多模式匹配篇) 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

字符串匹配(多模式匹配篇)

摘要:

問題的提出:眾所周知,KMP算法在O(n)的時間中solve單模式串匹配問題。但怎樣solve多模式串匹配問題呢?

Solve:本文用簡要記敘了使用trie樹,trie圖(AC自動機)solve該問題的方法。

關(guān)鍵字:

字符串,多模式串匹配,trie樹,trie圖,AC自動機。

前言:

KMP算法是一種極其優(yōu)秀的單模式串匹配算法,它通過前綴函數(shù)fail來減少匹配次數(shù),以達到O(n)的單串匹配。但當(dāng)KMP算法用于解決多模式串匹配問題時,時間復(fù)雜度為O(nq),十分低效。

因此,我們?nèi)ヌ剿饕恍└m合于多模式串匹配問題的算法用以解決這個問題。

第1節(jié)主要介紹trie樹。

第2節(jié)主要介紹trie圖。

第三節(jié)給出一些例題。

1.trie樹

1.0問題的引入:

? ? ? ?給定一個原串s,n個模式串st[i],求st[i]是否出現(xiàn)在s中。

1.1字典樹的定義:


又稱單詞查找樹,Trie樹,是一種樹形結(jié)構(gòu),是一種哈希樹的變種。典型應(yīng)用是用于統(tǒng)計,排序和保存大量的字符串(但不僅限于字符串),所以經(jīng)常被搜索引擎系統(tǒng)用于文本詞頻統(tǒng)計。

——來源于百度百科

1.2字典樹的性質(zhì):

? ? ?根節(jié)點不包含字符,除根節(jié)點外每一個節(jié)點都只包含一個字符; 從根節(jié)點到某一節(jié)點,路徑上經(jīng)過的字符連接起來,為該節(jié)點對應(yīng)的字符串; 每個節(jié)點的所有子節(jié)點包含的字符都不相同。

? ? ?每一個節(jié)點u都包含next[v],value。

? ? ?next[v]表示節(jié)點u的v邊指向的節(jié)點編號。

? ? ?value表示危險節(jié)點所屬的模式串編號,若value=0表示這不是一個危險節(jié)點。

? ? ?根節(jié)點表示空串。

? ? ?這樣一棵trie樹的深度為O(max(len)+1)

? ? ?可以發(fā)現(xiàn)當(dāng)且僅當(dāng)s1是s2的前綴,那么在trie樹上,s2的路徑是包含s1的路徑的。

? ? ? ? ?

1.2字典樹的實現(xiàn):

字典樹的操作是十分簡單的(建議讀者根據(jù)性質(zhì)自己推導(dǎo)實現(xiàn)過程)。

插入:

void build_trie_tree(char *st,int len,int p) {int x=1;for (int i=0;i<len;i++){if (trie[x].next[st[i]]==0) //若該邊不存在,新建點和邊。{nodenum++;trie[x].next[st[i]]=nodenum;}x=trie[x].next[st[i]]; //跳轉(zhuǎn)至下一個節(jié)點。}trie[x].value=p; //標(biāo)記是否為危險節(jié)點。 }

(刪除一個子樹的操作不再敘述,基本思想是增加一個tag懶標(biāo)記,表示此節(jié)點及其子樹被刪除。)

查詢(查詢某一字符串是否在trie樹中):

bool query_trie_tree(char *st,int len) {int x=1;for (int i=0;i<len;i++) x=trie[x].next[st[i]]; //跳轉(zhuǎn)至目標(biāo)節(jié)點if (!trie[x].value) return 1; }

查詢2(多串查詢):

我們只需要枚舉起始位置i,尋找st[i->j](i<=j<=len)是否出現(xiàn)即可。

void query_trie_tree(char *st,int k,int len) {int x=1;for (int i=k;i<len;i++){x=trie[x].next[st[i]]; //跳轉(zhuǎn) if (x) return; //匹配失敗退出 if (trie[x].value) fst[trie[x].value]=k; //fst[i]記錄i模式串出現(xiàn)在原串中的起始位置 } } void work(char *st,int len) {for (int i=0;i<len;i++)query_trie_tree(st,i,len); //枚舉所有起點位置 }


1.4trie樹的分析:(注:下文中|SIGMA|表示字符集大小,而sigma表示求和函數(shù))

構(gòu)造出trie樹的結(jié)構(gòu),構(gòu)造時間是O(sigma(len)),單串匹配的時間復(fù)雜度很明顯是O(len)級別的。

多串匹配需要枚舉原串的起始點u,再從trie樹中查詢,時間為O(lens*max(len))

比起這個,更讓我們關(guān)心的是空間復(fù)雜度,O(|SIGMA|n)。倘若空間不足,可以將next[v]用邊表的形式記錄下來,或者用左兒子,右兄弟的方法記錄。

這樣的數(shù)據(jù)結(jié)構(gòu)無論從時間或是空間上都和KMP相差無幾,但更加形象具體了。那么如何改變這個數(shù)據(jù)結(jié)構(gòu)使它能夠完成多串匹配任務(wù)呢?


注:將trie樹從上到下,從左到右標(biāo)號,根為1


我們發(fā)現(xiàn)在trie樹上多串匹配,會產(chǎn)生許多浪費。

比如模式串為ab。

以上圖中的trie樹來匹配,跳轉(zhuǎn)順序是

1->2->5(ab)

1->3(b)

而匹配ab時已經(jīng)將b匹配了一遍,但在做完ab之后卻返回了根,重新匹配了b,得不償失。

所以想要優(yōu)化trie樹,就要使每一次精確跳轉(zhuǎn)到最有效的位置。

進入到trie圖時代。


2.trie圖

2.0trie圖的引入——解決上述問題:

什么是精確跳轉(zhuǎn)呢?


在這個圖中,abc的精確跳轉(zhuǎn)應(yīng)該是bc。

abd的精確跳轉(zhuǎn)為根(空串)

字符串s的精確跳轉(zhuǎn)節(jié)點是trie樹中存在的s最長的后綴,稱為后綴fail。

2.1trie圖的概念:

在trie樹上添加前綴指針fail并補齊trie樹的邊,所構(gòu)造的圖。

2.2trie圖的性質(zhì):

trie圖的目的是讓每一次都精確地跳轉(zhuǎn)。

? ? ? ??

? ? ? ? 如該圖中的的fail應(yīng)該指向bc。

? ? ? ?節(jié)點u的fail應(yīng)該為u的父親節(jié)點的fail點的該邊。(fail[u]=trie[father[fail[u]]].next[ch])

? ? ? ?讀者可以計算一下當(dāng)trie圖中只有單串的時候,fail和KMP的next兩個數(shù)組有什么特殊的聯(lián)系。

? ? ? ?能夠很輕易地看出:若trie圖按0開始編號,next[i]=fail[i]

2.3trie圖的實現(xiàn):(建議讀者自行思考后對照)

? ?構(gòu)造trie圖:

void build_trie_graph() { trie[1].fail=1; //根的后綴為根for (int i=1;i<=p;i++)if (trie[1].next[i]) {trie[trie[1].next[i]].fail=1;que.push(trie[1].next[i]);} //根的兒子的后綴為根else trie[1].next[i]=1; //根的新邊為根while (!que.empty()){int q=que.front();que.pop();for (int i=1;i<=p;i++){int v=trie[q].next[i];if (v){trie[v].fail=trie[trie[q].fail].next[i];que.push(v);}else trie[q].next[i]=trie[trie[q].fail].next[i];}if (trie[trie[q].fail].value) trie[q].value=trie[trie[q].fail].value;} }

遍歷:

void query_trie_tree(char *st,int len) {int x=1;for (int i=0;i<len;i++) { if (!trie[x].value) solve_probleam();x=trie[x].next[st[i]]; //跳轉(zhuǎn)至目標(biāo)節(jié)點} }


3.來幾道例題練練手!

3.1 ?Video Game

Bessie is playing a video game! In the game, the three letters 'A', 'B',

and 'C' are the only valid buttons. Bessie may press the buttons in any
order she likes; however, there are only N distinct combos possible (1 <= N
<= 20). Combo i is represented as a string S_i which has a length between 1
and 15 and contains only the letters 'A', 'B', and 'C'.

Whenever Bessie presses a combination of letters that matches with a combo,
she gets one point for the combo. Combos may overlap with each other or
even finish at the same time! For example if N = 3 and the three possible
combos are "ABA", "CB", and "ABACB", and Bessie presses "ABACB", she will
end with 3 points. Bessie may score points for a single combo more than once.

Bessie of course wants to earn points as quickly as possible. If she
presses exactly K buttons (1 <= K <= 1,000), what is the maximum number of
points she can earn?

給你個模式串(每個長度≤15,1≤N≤20),串中只含有“ABC”三種字母。求一長度為K(1≤K≤1000)的字符串,使得匹配數(shù)最大(重復(fù)匹配計多次),輸出最大值。

題解:先構(gòu)造trie圖,然后動態(tài)規(guī)劃。

f[step][u]表示第step步走到u點經(jīng)過的最多危險節(jié)點數(shù)量。

f[step+1][trie[u].next[k]]=max{f[step+1][trie[u].next[k]],f[step[u]+trie[trie[u].next[k]].value }

#include<bits/stdc++.h> using namespace std; const int MAXANS=10000000; struct node {int ch[5],fail,value; } trie[505]; int nodenum=1,n,m; int f[1005][505]; char st[25]; queue<int> que; int smax(int x,int y){return x>y?x:y;} void build_trie_tree(char *st,int len) {int u=1;for (int i=0;i<len;i++){if (!trie[u].ch[st[i]-64]){nodenum++;trie[u].ch[st[i]-64]=nodenum;}u=trie[u].ch[st[i]-64];}trie[u].value++; } void build_trie_graph() {trie[1].fail=1;for (int i=1;i<=3;i++)if (trie[1].ch[i]){trie[trie[1].ch[i]].fail=1;que.push(trie[1].ch[i]);}else trie[1].ch[i]=1;while (!que.empty()){int u=que.front();que.pop();for (int i=1;i<=3;i++)if (trie[u].ch[i]){trie[trie[u].ch[i]].fail=trie[trie[u].fail].ch[i];que.push(trie[u].ch[i]);}else trie[u].ch[i]=trie[trie[u].fail].ch[i];if (trie[trie[u].fail].value) trie[u].value+=trie[trie[u].fail].value;} } void solve() {for (int step=0;step<=m;step++)for (int i=1;i<=nodenum;i++)f[step][i]=-MAXANS;f[0][1]=0;for (int step=0;step<m;step++)for (int i=1;i<=nodenum;i++)for (int j=1;j<=3;j++){int v=trie[i].ch[j];f[step+1][v]=smax(f[step+1][v],f[step][i]+trie[v].value);}int ans=0;for (int i=2;i<=nodenum;i++) ans=smax(ans,f[m][i]); printf("%d\n",ans); } int main() {scanf("%d%d",&n,&m);for (int i=1;i<=n;i++){scanf("%s",st);build_trie_tree(st,strlen(st));}build_trie_graph();solve();return 0; }

OJ上實測速度很快。只用了20MS(rank1膨脹逃)。


3.2阿貍的打字機

BZOJ2434 阿貍的打字機
阿貍喜歡收藏各種稀奇古怪的東西,最近他淘到一臺老式的打字機。打字機上只有28個按鍵,分別印有26個小寫英文字母和’B’、’P’兩個字母。?
經(jīng)阿貍研究發(fā)現(xiàn),這個打字機是這樣工作的:?
·輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最后)。?
·按一下印有’B’的按鍵,打字機凹槽中最后一個字母會消失。?
·按一下印有’P’的按鍵,打字機會在紙上打印出凹槽中現(xiàn)有的所有字母并換行,但凹槽中的字母不會消失。?
例如,阿貍輸入aPaPBbP,紙上被打印的字符如下:?
a?
aa?
ab?
我們把紙上打印出來的字符串從1開始順序編號,一直到n。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數(shù)字的小鍵盤,在小鍵盤上輸入兩個數(shù)(x,y)(其中1≤x,y≤n),打字機會顯示第x個打印的字符串在第y個打印的字符串中出現(xiàn)了多少次。?
阿貍發(fā)現(xiàn)了這個功能以后很興奮,他想寫個程序完成同樣的功能,你能幫助他么?
Input
輸入的第一行包含一個字符串,按阿貍的輸入順序給出所有阿貍輸入的字符。
第二行包含一個整數(shù)m,表示詢問個數(shù)。
接下來m行描述所有由小鍵盤輸入的詢問。其中第i行包含兩個整數(shù)x, y,表示第i個詢問為(x, y)。
Output
輸出m行,其中第i行包含一個整數(shù),表示第i個詢問的答案。
Sample Input
aPaPBbP
3
1 2
1 3
2 3
Sample Output?
2
1
0
Hint
1<=N<=10^5
1<=M<=10^5
輸入總長<=10^5

明顯的trie圖。

不解釋,大佬們都會的。

(noi壓軸題原來這么簡單!!!(逃))



3.3宇宙隊神題!

題目受宇宙隊管制,暫時無法查看。


3.4經(jīng)典好題!

POJ 1204 Word Puzzles

POJ 1625 Censored!?

POJ 2778 DNA Sequence?

HDU 2457 DNA repair

ural 1269. Obscene Words Filter

[BZOJ1195][HNOI2006]最短母串


4.總結(jié)

trie樹,trie圖還是屬于簡單易寫的匹配算法。

trie樹,trie圖一般用于解決三種問題:

1.多個字符串的存儲。

2.多個字符串的匹配、查詢、字符串樹(圖)上操作。

3.輔助其他算法(如DP等)存取數(shù)據(jù)。

大家有時間可以對比一下KMP和trie圖的其他性質(zhì)的相同點。

trie滿結(jié)


總結(jié)

以上是生活随笔為你收集整理的字符串匹配(多模式匹配篇)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。