Aho-Corasick算法是多模式匹配中的經典算法,目前在實際應用中較多。
Aho-Corasick算法對應的數據結構是Aho-Corasick自動機,簡稱AC自動機。
搞編程的一般都應該知道自動機FA吧,具體細分為:確定性有限狀態自動機(DFA)和非確定性有限狀態自動機NFA。普通的自動機不能進行多模式匹配,AC自動機增加了失敗轉移,轉移到已經輸入成功的文本的后綴,來實現。
1.多模式匹配
多模式匹配就是有多個模式串P1,P2,P3...,Pm,求出所有這些模式串在連續文本T1....n中的所有可能出現的位置。
例如:求出模式集合{"nihao","hao","hs","hsr"}在給定文本"sdmfhsgnshejfgnihaofhsrnihao"中所有可能出現的位置。
2.Aho-Corasick算法
使用Aho-Corasick算法需要三步:
1.建立模式的Trie
2.給Trie添加失敗路徑
3.根據AC自動機,搜索待處理的文本
下面說明這三步:
2.1建立多模式集合的Trie樹
Trie樹也是一種自動機。對於多模式集合{"say","she","shr","he","her"},對應的Trie樹如下,其中紅色標記的圈是表示為接收態:
2.2為多模式集合的Trie樹添加失敗路徑,建立AC自動機
構造失敗指針的過程概括起來就一句話:設這個節點上的字母為C,沿着他父親的失敗指針走,直到走到一個節點,他的兒子中也有字母為C的節點。然后把當前節點的失敗指針指向那個字母也為C的兒子。如果一直走到了root都沒找到,那就把失敗指針指向root。
使用廣度優先搜索BFS,層次遍歷節點來處理,每一個節點的失敗路徑。
特殊處理:第二層要特殊處理,將這層中的節點的失敗路徑直接指向父節點(也就是根節點)。
2.3根據AC自動機,搜索待處理的文本
從root節點開始,每次根據讀入的字符沿着自動機向下移動。
當讀入的字符,在分支中不存在時,遞歸走失敗路徑。如果走失敗路徑走到了root節點,則跳過該字符,處理下一個字符。
因為AC自動機是沿着輸入文本的最長后綴移動的,所以在讀取完所有輸入文本后,最后遞歸走失敗路徑,直到到達根節點,這樣可以檢測出所有的模式。
3.Aho-Corasick算法代碼示例
模式串集合:{"nihao","hao","hs","hsr"}
待匹配文本:"sdmfhsgnshejfgnihaofhsrnihao"
代碼:

1 #include<iostream> 2 #include<string.h> 3 #include<malloc.h> 4 #include <queue> 5 using namespace std; 6 7 typedef struct node{ 8 struct node *next[26]; //接收的態 9 struct node *par; //父親節點 10 struct node *fail; //失敗節點 11 char inputchar; 12 int patterTag; //是否為可接收態 13 int patterNo; //接收態對應的可接受模式 14 }*Tree,TreeNode; 15 char pattern[4][30]={"nihao","hao","hs","hsr"}; 16 17 /** 18 申請新的節點,並進行初始化 19 */ 20 TreeNode *getNewNode() 21 { 22 int i; 23 TreeNode* tnode=(TreeNode*)malloc(sizeof(TreeNode)); 24 tnode->fail=NULL; 25 tnode->par=NULL; 26 tnode->patterTag=0; 27 for(i=0;i<26;i++) 28 tnode->next[i]=NULL; 29 return tnode; 30 } 31 32 /** 33 將Trie樹中,root節點的分支節點,放入隊列 34 */ 35 int nodeToQueue(Tree root,queue<Tree> &myqueue) 36 { 37 int i; 38 for (i = 0; i < 26; i++) 39 { 40 if (root->next[i]!=NULL) 41 myqueue.push(root->next[i]); 42 } 43 return 0; 44 } 45 46 /** 47 建立trie樹 48 */ 49 Tree buildingTree() 50 { 51 int i,j; 52 Tree root=getNewNode(); 53 Tree tmp1=NULL,tmp2=NULL; 54 for(i=0;i<4;i++) 55 { 56 tmp1=root; 57 for(j=0;j<strlen(pattern[i]);j++) ///對每個模式進行處理 58 { 59 if(tmp1->next[pattern[i][j]-'a']==NULL) ///是否已經有分支,Trie共用節點 60 { 61 tmp2=getNewNode(); 62 tmp2->inputchar=pattern[i][j]; 63 tmp2->par=tmp1; 64 tmp1->next[pattern[i][j]-'a']=tmp2; 65 tmp1=tmp2; 66 } 67 else 68 tmp1=tmp1->next[pattern[i][j]-'a']; 69 } 70 tmp1->patterTag=1; 71 tmp1->patterNo=i; 72 } 73 return root; 74 } 75 76 /** 77 建立失敗指針 78 */ 79 int buildingFailPath(Tree root) 80 { 81 int i; 82 char inputchar; 83 queue<Tree> myqueue; 84 root->fail=root; 85 for(i=0;i<26;i++) ///對root下面的第二層進行特殊處理 86 { 87 if (root->next[i]!=NULL) 88 { 89 nodeToQueue(root->next[i],myqueue); 90 root->next[i]->fail=root; 91 } 92 } 93 94 Tree tmp=NULL,par=NULL; 95 while(!myqueue.empty()) 96 { 97 tmp=myqueue.front(); 98 myqueue.pop(); 99 nodeToQueue(tmp,myqueue); 100 101 inputchar=tmp->inputchar; 102 par=tmp->par; 103 104 while(true) 105 { 106 if(par->fail->next[inputchar-'a']!=NULL) 107 { 108 tmp->fail=par->fail->next[inputchar-'a']; 109 break; 110 } 111 else 112 { 113 if(par->fail==root) 114 { 115 tmp->fail=root; 116 break; 117 } 118 else 119 par=par->fail->par; 120 } 121 } 122 } 123 return 0; 124 } 125 126 /** 127 進行多模式搜索,即搜尋AC自動機 128 */ 129 int searchAC(Tree root,char* str,int len) 130 { 131 TreeNode *tmp=root; 132 int i=0; 133 while(i < len) 134 { 135 int pos=str[i]-'a'; 136 if (tmp->next[pos]!=NULL) 137 { 138 tmp=tmp->next[pos]; 139 if(tmp->patterTag==1) ///如果為接收態 140 { 141 cout<<i-strlen(pattern[tmp->patterNo])+1<<'\t'<<tmp->patterNo<<'\t'<<pattern[tmp->patterNo]<<endl; 142 } 143 i++; 144 } 145 else 146 { 147 if(tmp==root) 148 i++; 149 else 150 { 151 tmp=tmp->fail; 152 if(tmp->patterTag==1) //如果為接收態 153 cout<<i-strlen(pattern[tmp->patterNo])+1<<'\t'<<tmp->patterNo<<'\t'<<pattern[tmp->patterNo]<<endl; 154 } 155 } 156 } 157 while(tmp!=root) 158 { 159 tmp=tmp->fail; 160 if(tmp->patterTag==1) 161 cout<<i-strlen(pattern[tmp->patterNo])+1<<'\t'<<tmp->patterNo<<'\t'<<pattern[tmp->patterNo]<<endl; 162 } 163 return 0; 164 } 165 166 /** 167 釋放內存,DFS 168 */ 169 int destory(Tree tree) 170 { 171 if(tree==NULL) 172 return 0; 173 queue<Tree> myqueue; 174 TreeNode *tmp=NULL; 175 176 myqueue.push(tree); 177 tree=NULL; 178 while(!myqueue.empty()) 179 { 180 tmp=myqueue.front(); 181 myqueue.pop(); 182 183 for (int i = 0; i < 26; i++) 184 { 185 if(tmp->next[i]!=NULL) 186 myqueue.push(tmp->next[i]); 187 } 188 free(tmp); 189 } 190 return 0; 191 } 192 193 int main() 194 { 195 char a[]="sdmfhsgnshejfgnihaofhsrnihao"; 196 Tree root=buildingTree(); ///建立Trie樹 197 buildingFailPath(root); ///添加失敗轉移 198 cout<<"待匹配字符串:"<<a<<endl; 199 cout<<"模式"<<pattern[0]<<" "<<pattern[1]<<" "<<pattern[2]<<" "<<pattern[3]<<" "<<endl<<endl; 200 cout<<"匹配結果如下:"<<endl<<"位置\t"<<"編號\t"<<"模式"<<endl; 201 searchAC(root,a,strlen(a)); ///搜索 202 destory(root); ///釋放動態申請內存 203 return 0; 204 }
輸出:
(上面的兩個圖,參考網頁:http://www.cppblog.com/mythit/archive/2009/04/21/80633.html)