AC自動機講解
阿新 • • 發佈:2020-11-03
三步走:
①將所有的模式串構成一顆Trie樹。
②對Trie上所有的節點構造字首指標。
③利用字首指標Fail對主串進行匹配。
實際上這個字首指標Fail與KMP演算法中的nxt陣列非常相似,因此AC自動機可以看作是Trie與KMP演算法的結合(Trie上的KMP演算法)。
第一步:字典樹的構建.
第二步:
找Fail指標:
運用廣度優先搜尋 (BFS)來求得。
對於與根節點直接相連的點來說,如果這些節點失配,他們的Fail指標直接指向root即可。
其他節點其Fail指標求法如下:
設當前節點為father,其子節點為child。
求child的Fail指標時,首先我們要找到其father的Fail指標所指向的節點設為F,看F的孩子中有沒有和child節點所表示的字母相同的節點,如果有的話,這個節
點就是child的Fail指標,如果沒有,則需要再找到F的Fail指標所指向的節點,如果一直找都找不到,則child的Fail指標就要指向root。
第三步:
文字串的匹配:
匹配過程分兩種情況:
(1)當前字元匹配,從當前節點沿著樹邊有一條路徑可以到達目標字元,如果當前匹配的字元是一個單詞的結尾,就沿著當前字元的Fail指標,一直遍歷到根,如果這些節點末尾有標記(當前節點單詞末尾的標記),這些節點全都是可以匹配上的節點。統計完畢後,並將那些節點標記。此時只需沿該路徑走向下一個節點繼續匹配即可,目標字串指標移向下個字元繼續匹配;
(2)當前字元不匹配,則去當前節點失敗指標所指向的字元繼續匹配,當指標指向root時結束。
重複這2個過程中的任意一個,直到模式串走到結尾為止。
eg:
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n; 4 char s[2000001]; 5 int trie[1000001][30]; 6 int que[1000001],end[1000001],nxt[1000001]; 7 int ans,cnt; 8 void insert(char *str)//Trie樹構建過程 9 { 10 int p=1; 11 int len=strlen(str); 12 for(int i=0;i<len;i++)13 { 14 int ch=str[i]-'a'; 15 if(!trie[p][ch]) 16 { 17 trie[p][ch]=++cnt; 18 memset(trie[cnt],0,sizeof(trie[cnt]));//每次只需要清空我們會用得到的行 19 } 20 p=trie[p][ch]; 21 } 22 end[p]++;//因為有可能會有重複的單詞,故在此end統計在此有多少個單詞結束,而不是有沒有單詞結束 23 } 24 void build()//BFS構建Fail指標 25 { 26 for(int i=0;i<26;i++)//為了方便將0的所有轉一遍都設為根節點1 27 trie[0][i]=1; 28 nxt[1]=0;//若在根節點失配, 則無法匹配字元 29 que[1]=1; 30 int head=1,tail=1; 31 while(head<=tail) 32 { 33 for(int i=0;i<26;i++) 34 if(!trie[que[head]][i])trie[que[head]][i]=trie[nxt[que[head]]][i];//注意這裡,下面會有詳細解釋 35 else 36 { 37 que[++tail]=trie[que[head]][i]; 38 int flag=nxt[que[head]]; 39 while(flag&&!trie[flag][i])flag=nxt[flag];//迴圈往前找 40 nxt[trie[que[head]][i]]=trie[nxt[que[head]]][i]; 41 } 42 head++;//注意隊頭++ 43 } 44 } 45 void find(char *str)//匹配 46 { 47 int p=1; 48 int len=strlen(str); 49 for(int i=0;i<len;i++) 50 { 51 int flag=p=trie[p][str[i]-'a']; 52 while(end[flag]!=-1&&flag) 53 { 54 ans+=end[flag]; 55 end[flag]=-1;//標記這個點已經訪問過,以後不再訪問 56 flag=nxt[flag]; 57 } 58 } 59 } 60 int main() 61 { 62 int T; 63 scanf("%d",&T); 64 while(T--) 65 { 66 memset(end,0,sizeof(end));//多測不清空,爆零兩行淚(寶寶別哭) 67 cnt=1; 68 ans=0; 69 for(int i=0;i<26;i++) 70 trie[0][i]=1,trie[1][i]=0;//亦是清空 71 scanf("%d",&n); 72 for(int i=1;i<=n;i++) 73 { 74 scanf("%s",s); 75 insert(s);//讀入子串並插入Trie樹 76 } 77 build(); 78 scanf("%s",s); 79 find(s);//匹配 80 printf("%d\n",ans); 81 } 82 return 0; 83 }