AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。要搞懂AC自动机,先得有模式树(字典树)Trie、广度优先策略和KMP模式匹配算法的基础知识。
关于AC自动机
建立Aho-Corasick automation算法需要三步:
- 建立模式串的Trie
- 给Trie添加失配路径
- 根据AC自动机,搜索待处理的文本
这里以航电OJ上的一道题hdu 2222 KeywordsSearch为例子。
1 | 给定5个单词:say she shr he her |
2 | 一个字符串:yasherhs |
3 | 问一共有多少单词在这个字符串中出现。 |
1 |
|
2 |
|
3 |
|
4 | struct Node |
5 | { |
6 | int cnt;//是否为该单词的最后一个结点 |
7 | Node *fail;//失败指针 |
8 | Node *next[26];//Trie中每个结点的各个节点 |
9 | }*queue[500005];//队列,方便用BFS构造失败指针 |
10 | char s[1000005];//主字符串 |
11 | char keyword[55];//需要查找的单词 |
12 | Node *root;//头结点 |
13 | void Init(Node *root)//每个结点的初始化 |
14 | { |
15 | root->cnt=0; |
16 | root->fail=NULL; |
17 | for(int i=0;i<26;i++) |
18 | root->next[i]=NULL; |
19 | } |
20 | void Build_trie(char *keyword)//构建Trie树 |
21 | { |
22 | Node *p,*q; |
23 | int i,v; |
24 | int len=strlen(keyword); |
25 | for(i=0,p=root;i<len;i++) |
26 | { |
27 | v=keyword[i]-'a'; |
28 | if(p->next[v]==NULL) |
29 | { |
30 | q=(struct Node *)malloc(sizeof(Node)); |
31 | Init(q); |
32 | p->next[v]=q;//结点链接 |
33 | } |
34 | p=p->next[v];//指针移动到下一个结点 |
35 | } |
36 | p->cnt++;//单词最后一个结点cnt++,代表一个单词 |
37 | } |
38 | void Build_AC_automation(Node *root) |
39 | { |
40 | int head=0,tail=0;//队列头、尾指针 |
41 | queue[head++]=root;//先将root入队 |
42 | while(head!=tail) |
43 | { |
44 | Node *p=NULL; |
45 | Node *temp=queue[tail++];//弹出队头结点 |
46 | for(int i=0;i<26;i++) |
47 | { |
48 | if(temp->next[i]!=NULL)//找到实际存在的字符结点 |
49 | { //temp->next[i] 为该结点,temp为其父结点 |
50 | if(temp==root)//若是第一层中的字符结点,则把该结点的失败指针指向root |
51 | temp->next[i]->fail=root; |
52 | else |
53 | { |
54 | //依次回溯该节点的父节点的失败指针直到某节点的next[i]与该节点相同, |
55 | //则把该节点的失败指针指向该next[i]节点; |
56 | //若回溯到 root 都没有找到,则该节点的失败指针指向 root |
57 | p=temp->fail;//将该结点的父结点的失败指针给p |
58 | while(p!=NULL) |
59 | { |
60 | if(p->next[i]!=NULL) |
61 | { |
62 | temp->next[i]->fail=p->next[i]; |
63 | break; |
64 | } |
65 | p=p->fail; |
66 | } |
67 | //让该结点的失败指针也指向root |
68 | if(p==NULL) |
69 | temp->next[i]->fail=root; |
70 | } |
71 | queue[head++]=temp->next[i];//每处理一个结点,都让该结点的所有孩子依次入队 |
72 | } |
73 | } |
74 | } |
75 | } |
76 | int query(Node *root) |
77 | { //i为主串指针,p为模式串指针 |
78 | int i,v,count=0; |
79 | Node *p=root; |
80 | int len=strlen(s); |
81 | for(i=0;i<len;i++) |
82 | { |
83 | v=s[i]-'a'; |
84 | //由失败指针回溯查找,判断s[i]是否存在于Trie树中 |
85 | while(p->next[v]==NULL && p!=root) |
86 | p=p->fail; |
87 | p=p->next[v];//找到后p指针指向该结点 |
88 | if(p==NULL)//若指针返回为空,则没有找到与之匹配的字符 |
89 | p=root; |
90 | Node *temp=p;//匹配该结点后,沿其失败指针回溯,判断其它结点是否匹配 |
91 | while(temp!=root)//匹配结束控制 |
92 | { |
93 | if(temp->cnt>=0)//判断该结点是否被访问 |
94 | { |
95 | count+=temp->cnt;//由于cnt初始化为 0,所以只有cnt>0时才统计了单词的个数 |
96 | temp->cnt=-1;//标记已访问过 |
97 | } |
98 | else//结点已访问,退出循环 |
99 | break; |
100 | temp=temp->fail;//回溯 失败指针 继续寻找下一个满足条件的结点 |
101 | } |
102 | } |
103 | return count; |
104 | } |
105 | int main() |
106 | { |
107 | int T,n; |
108 | scanf("%d",&T); |
109 | while(T--) |
110 | { |
111 | root=(struct Node *)malloc(sizeof(Node)); |
112 | Init(root); |
113 | scanf("%d",&n); |
114 | for(int i=0;i<n;i++) |
115 | { |
116 | scanf("\n%s",keyword); |
117 | Build_trie(keyword); |
118 | } |
119 | Build_AC_automation(root); |
120 | scanf("\n%s",s); |
121 | printf("%d\n",query(root)); |
122 | } |
123 | return 0; |
124 | } |