一:概念
首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段文章(长度是m),让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie的基础知识(也有人说需要KMP的知识,我觉得暂且不要理会这个。但是在看这篇文章之前,Trie字典树,你是必须要先搞懂,如果你还不理解Trie,请参考http://blog.csdn.net/laojiu_/article/details/50838421)。
与其他字符匹配不同,KMP算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算法。匹配时间复杂度是O(N),线性复杂度!
二:算法过程(三步走)
举个例子,假如现在给出5个模式串:say she shr he her
主串是:yasherhs
现在问你,这5个模式串有几个出现在主串里的?
OK,现在就拿这个例子来完成这个算法的过程。
第一步:构建Trie树,这很简单的了。构建好后,出现下图:
![](https://img-blog.csdn.net/20160311204733129)
第二步:构建失败指针
构建失败指针是AC自动机的核心所在,玩转了它也就玩转了AC自动机,失败指针就是,当我的主串在trie树中进行匹配的时候,如果当前节点不能再继续进行匹配,那么我们就会走到当前节点的fail节点继续进行匹配。
构造失败指针的过程概括起来就一句话:对于root的儿子节点,fail指针直接指向root,其他的所有节点(用到了BFS和队列),设这个节点上的字母为C,沿着它父亲的失败指针走,直到走到一个节点,它的儿子中也有字母为C的节点。然后把当前节点的失败指针指向那个字母为C的节点。如果一直走到了root都没找到,那就把失败指针指向root。
构建好后,如下图:
![](https://img-blog.csdn.net/20160311205736508)
针对图中红线的”h,e“这两个节点,我们想起了什么呢?对”her“中的”e“来说,e到root距离的n个字符恰好与”she“中的e向上的n个字符相等。
第三步:模式匹配
匹配过程分两种情况:
(1) 当前字符匹配成功,表示从当前节点沿着树边有一条路径可以到达目标字符,此时只需沿该路径走向下一个节点继续匹配即可,目标字符串指针移向下个字符继续匹配;
(2) 当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程中的任意一个,直到模式串走到结尾为止。
注意:主串所有字符在匹配完后都必须要走fail节点来结束自己的旅途,相当于一个回旋,这样做的目的防止包含节点被忽略掉。
见下图,比如:我匹配到了"she",必然会匹配到该字符串的后缀”he",要想在程序中匹配到,则必须节点要走失败指针来结束自己的旅途。
![](https://img-blog.csdn.net/20160311210430257)
三:完整代码
- #include<iostream>
- #include<queue>
-
- #define MAX 26//假设只出现26个小写英文字母
- #define ROW 4
- #define COLUMN 10
-
- using namespace std;
-
- char pattern[ROW][COLUMN] = { "nihao","hao","hs","hsr" };
- char *s = "sdmfhsgnshejfgnihaofhsrnihao";
-
- struct Node
- {
- int index;
- char x;
- Node *parent;
- Node *next[MAX];
- Node *fail;
- Node()
- {
- index = -1;
- fail = nullptr;
- parent = nullptr;
- for (int i = 0; i < MAX; i++)
- next[i] = nullptr;
- }
- };
-
- class ACTree
- {
- public:
- Node *root;
- ACTree() { root = new Node; root->fail = root; }
-
- void Add(const char *ch, int index);
- void NodeToQueue(Node *node, queue<Node*> &q);
- void BuildFailPointer();
- void ACSearch(const char *s);
- };
-
- int main()
- {
- ACTree tree;
-
- for (int i = 0; i < ROW; i++)
- tree.Add(pattern[i], i);
-
- tree.BuildFailPointer();
-
- cout << "待匹配字符串为(依次5个一组的输出):\n";
- for (int i = 1; i <= strlen(s); i++)
- {
- cout << s[i];
- if (i % 5 == 0)
- cout << " ";
- }
- cout << endl << endl;
-
- cout << "匹配结果如下:\n";
- cout << "位置\t" << "编号\t" << "模式\n";
-
- tree.ACSearch(s);
-
- return 0;
- }
-
- void ACTree::Add(const char *ch,int index)
- {
- int len = strlen(ch);
- if (len == 0) return;
-
- Node *p = root;
-
- for (int i = 0; i < len; i++)
- {
- int k = ch[i] - 'a';
-
- if (p->next[k] == nullptr)
- {
- p->next[k] = new Node;
- p->next[k]->parent = p;
- p->next[k]->x = ch[i];
- }
-
- p = p->next[k];
- }
-
- p->index = index;
- }
-
- void ACTree::NodeToQueue(Node *node, queue<Node*> &q)
- {
- if (node != nullptr)
- {
- for (int i = 0; i < MAX; i++)
- {
- if (node->next[i])
- q.push(node->next[i]);
- }
- }
- }
-
- void ACTree::BuildFailPointer()
- {
- queue<Node*> q;
-
- for (int i = 0; i < MAX; i++)
- {
- if (root->next[i])
- {
- NodeToQueue(root->next[i], q);
- root->next[i]->fail = root;
- }
- }
-
- Node *parent, *p;
- char ch;
- while (!q.empty())
- {
- p = q.front();
- ch = p->x;
- parent = p->parent;
- q.pop();
- NodeToQueue(p, q);
-
- while (1)
- {
- if (parent->fail->next[ch - 'a'] != nullptr)
- {
- p->fail = parent->fail->next[ch - 'a'];
- break;
- }
- else
- {
- if (parent->fail == root)
- {
- p->fail = root;
- break;
- }
- else
- parent = parent->fail->parent;
- }
- }
- }
- }
-
- void ACTree::ACSearch(const char *s)
- {
- int len = strlen(s);
- if (len == 0) return;
-
- Node *p = root;
-
- int i = 0;
- while (i < len)
- {
- int k = s[i] - 'a';
-
- if (p->next[k] != nullptr)
- {
- p = p->next[k];
-
- if (p->index != -1)
- cout << i - strlen(pattern[p->index]) + 1 << "\t" << p->index << "\t" << pattern[p->index] << endl;
-
- i++;
- }
- else
- {
- if (p == root)
- i++;
- else
- {
- p = p->fail;
- if (p->index != -1)
- cout << i - strlen(pattern[p->index]) + 1 << "\t" << p->index << "\t" << pattern[p->index] << endl;
- }
- }
- }
-
- while (p != root)
- {
- p = p->fail;
- if(p->index!=-1)
- cout << i - strlen(pattern[p->index]) + 1 << "\t" << p->index << "\t" << pattern[p->index] << endl;
- }
- }
四:数据测试
![](https://img-blog.csdn.net/20160311211422280)