字符串匹配(多模式匹配篇)
字符串匹配(多模式匹配篇)
摘要:
問題的提出:眾所周知,KMP算法在O(n)的時間中solve單模式串匹配問題。但怎樣solve多模式串匹配問題呢?
Solve:本文用簡要記敘了使用trie樹,trie圖(AC自動機)solve該問題的方法。
關鍵字:
字符串,多模式串匹配,trie樹,trie圖,AC自動機。前言:
KMP算法是一種極其優秀的單模式串匹配算法,它通過前綴函數fail來減少匹配次數,以達到O(n)的單串匹配。但當KMP算法用于解決多模式串匹配問題時,時間復雜度為O(nq),十分低效。
因此,我們去探索一些更適合于多模式串匹配問題的算法用以解決這個問題。
第1節主要介紹trie樹。
第2節主要介紹trie圖。
第三節給出一些例題。
1.trie樹
1.0問題的引入:
? ? ? ?給定一個原串s,n個模式串st[i],求st[i]是否出現在s中。
1.1字典樹的定義:
又稱單詞查找樹,Trie樹,是一種樹形結構,是一種哈希樹的變種。典型應用是用于統計,排序和保存大量的字符串(但不僅限于字符串),所以經常被搜索引擎系統用于文本詞頻統計。
——來源于百度百科1.2字典樹的性質:
? ? ?根節點不包含字符,除根節點外每一個節點都只包含一個字符; 從根節點到某一節點,路徑上經過的字符連接起來,為該節點對應的字符串; 每個節點的所有子節點包含的字符都不相同。
? ? ?每一個節點u都包含next[v],value。
? ? ?next[v]表示節點u的v邊指向的節點編號。
? ? ?value表示危險節點所屬的模式串編號,若value=0表示這不是一個危險節點。
? ? ?根節點表示空串。
? ? ?這樣一棵trie樹的深度為O(max(len)+1)
? ? ?可以發現當且僅當s1是s2的前綴,那么在trie樹上,s2的路徑是包含s1的路徑的。
? ? ? ? ?
1.2字典樹的實現:
字典樹的操作是十分簡單的(建議讀者根據性質自己推導實現過程)。
插入:
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]]; //跳轉至下一個節點。}trie[x].value=p; //標記是否為危險節點。 }(刪除一個子樹的操作不再敘述,基本思想是增加一個tag懶標記,表示此節點及其子樹被刪除。)
查詢(查詢某一字符串是否在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]]; //跳轉至目標節點if (!trie[x].value) return 1; }查詢2(多串查詢):
我們只需要枚舉起始位置i,尋找st[i->j](i<=j<=len)是否出現即可。
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]]; //跳轉 if (x) return; //匹配失敗退出 if (trie[x].value) fst[trie[x].value]=k; //fst[i]記錄i模式串出現在原串中的起始位置 } } void work(char *st,int len) {for (int i=0;i<len;i++)query_trie_tree(st,i,len); //枚舉所有起點位置 }1.4trie樹的分析:(注:下文中|SIGMA|表示字符集大小,而sigma表示求和函數)
構造出trie樹的結構,構造時間是O(sigma(len)),單串匹配的時間復雜度很明顯是O(len)級別的。
多串匹配需要枚舉原串的起始點u,再從trie樹中查詢,時間為O(lens*max(len))。
比起這個,更讓我們關心的是空間復雜度,O(|SIGMA|n)。倘若空間不足,可以將next[v]用邊表的形式記錄下來,或者用左兒子,右兄弟的方法記錄。
這樣的數據結構無論從時間或是空間上都和KMP相差無幾,但更加形象具體了。那么如何改變這個數據結構使它能夠完成多串匹配任務呢?
注:將trie樹從上到下,從左到右標號,根為1
我們發現在trie樹上多串匹配,會產生許多浪費。
比如模式串為ab。
以上圖中的trie樹來匹配,跳轉順序是
1->2->5(ab)
1->3(b)
而匹配ab時已經將b匹配了一遍,但在做完ab之后卻返回了根,重新匹配了b,得不償失。
所以想要優化trie樹,就要使每一次精確跳轉到最有效的位置。
進入到trie圖時代。
2.trie圖
2.0trie圖的引入——解決上述問題:
什么是精確跳轉呢?
在這個圖中,abc的精確跳轉應該是bc。
abd的精確跳轉為根(空串)
字符串s的精確跳轉節點是trie樹中存在的s最長的后綴,稱為后綴fail。
2.1trie圖的概念:
在trie樹上添加前綴指針fail并補齊trie樹的邊,所構造的圖。
2.2trie圖的性質:
trie圖的目的是讓每一次都精確地跳轉。
? ? ? ??
? ? ? ? 如該圖中的的fail應該指向bc。
? ? ? ?節點u的fail應該為u的父親節點的fail點的該邊。(fail[u]=trie[father[fail[u]]].next[ch])
? ? ? ?讀者可以計算一下當trie圖中只有單串的時候,fail和KMP的next兩個數組有什么特殊的聯系。
? ? ? ?能夠很輕易地看出:若trie圖按0開始編號,next[i]=fail[i]。
2.3trie圖的實現:(建議讀者自行思考后對照)
? ?構造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]]; //跳轉至目標節點} }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)的字符串,使得匹配數最大(重復匹配計多次),輸出最大值。
題解:先構造trie圖,然后動態規劃。
f[step][u]表示第step步走到u點經過的最多危險節點數量。
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’兩個字母。?
經阿貍研究發現,這個打字機是這樣工作的:?
·輸入小寫字母,打字機的一個凹槽中會加入這個字母(這個字母加在凹槽的最后)。?
·按一下印有’B’的按鍵,打字機凹槽中最后一個字母會消失。?
·按一下印有’P’的按鍵,打字機會在紙上打印出凹槽中現有的所有字母并換行,但凹槽中的字母不會消失。?
例如,阿貍輸入aPaPBbP,紙上被打印的字符如下:?
a?
aa?
ab?
我們把紙上打印出來的字符串從1開始順序編號,一直到n。打字機有一個非常有趣的功能,在打字機中暗藏一個帶數字的小鍵盤,在小鍵盤上輸入兩個數(x,y)(其中1≤x,y≤n),打字機會顯示第x個打印的字符串在第y個打印的字符串中出現了多少次。?
阿貍發現了這個功能以后很興奮,他想寫個程序完成同樣的功能,你能幫助他么?
Input
輸入的第一行包含一個字符串,按阿貍的輸入順序給出所有阿貍輸入的字符。
第二行包含一個整數m,表示詢問個數。
接下來m行描述所有由小鍵盤輸入的詢問。其中第i行包含兩個整數x, y,表示第i個詢問為(x, y)。
Output
輸出m行,其中第i行包含一個整數,表示第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經典好題!
POJ 1204 Word Puzzles
POJ 1625 Censored!?
POJ 2778 DNA Sequence?
HDU 2457 DNA repair
[BZOJ1195][HNOI2006]最短母串
4.總結
trie樹,trie圖還是屬于簡單易寫的匹配算法。
trie樹,trie圖一般用于解決三種問題:
1.多個字符串的存儲。
2.多個字符串的匹配、查詢、字符串樹(圖)上操作。
3.輔助其他算法(如DP等)存取數據。
大家有時間可以對比一下KMP和trie圖的其他性質的相同點。
trie系列圓滿結束!!!
總結
以上是生活随笔為你收集整理的字符串匹配(多模式匹配篇)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 葶苈子的功效与作用、禁忌和食用方法
- 下一篇: 艾灰的功效与作用、禁忌和食用方法