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
2 #include
3 #include
4 #include
5 using namespacestd;6
7 typedef structnode{8 struct node *next[26]; //接收的态
9 struct node *par; //父亲节点
10 struct node *fail; //失败节点
11 charinputchar;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 inti;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 returntnode;30 }31
32 /**33 将Trie树中,root节点的分支节点,放入队列34 */
35 int nodeToQueue(Tree root,queue &myqueue)36 {37 inti;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 inti,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
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 returnroot;74 }75
76 /**77 建立失败指针78 */
79 intbuildingFailPath(Tree root)80 {81 inti;82 charinputchar;83 queuemyqueue;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,intlen)130 {131 TreeNode *tmp=root;132 int i=0;133 while(i next[pos]!=NULL)137 {138 tmp=tmp->next[pos];139 if(tmp->patterTag==1) ///如果为接收态
140 {141 cout<patterNo])+1<patterNo<patterNo]<
146 {147 if(tmp==root)148 i++;149 else
150 {151 tmp=tmp->fail;152 if(tmp->patterTag==1) //如果为接收态
153 cout<patterNo])+1<patterNo<patterNo]<fail;160 if(tmp->patterTag==1)161 cout<patterNo])+1<patterNo<patterNo]<
166 /**167 释放内存,DFS168 */
169 intdestory(Tree tree)170 {171 if(tree==NULL)172 return 0;173 queuemyqueue;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 intmain()194 {195 char a[]="sdmfhsgnshejfgnihaofhsrnihao";196 Tree root=buildingTree(); ///建立Trie树
197 buildingFailPath(root); ///添加失败转移
198 cout<
202 destory(root); ///释放动态申请内存
203 return 0;204 }
View Code
输出: