一:概念node
首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段文章(长度是m),让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有字典树Trie的基础知识(也有人说须要KMP的知识,我以为暂且不要理会这个。可是在看这篇文章以前,Trie字典树,你是必需要先搞懂,若是你还不理解Trie,请参考http://blog.csdn.net/laojiu_/article/details/50838421)。ios
与其余字符匹配不一样,KMP算法是单模式串的字符匹配算法,AC自动机是多模式串的字符匹配算法。匹配时间复杂度是O(N),线性复杂度!算法
二:算法过程(三步走)数组
举个例子,假如如今给出5个模式串:say she shr he her 测试
主串是:yasherhsui
如今问你,这5个模式串有几个出如今主串里的?spa
OK,如今就拿这个例子来完成这个算法的过程。.net
第一步:构建Trie树,这很简单的了。构建好后,出现下图:指针
第二步:构建失败指针code
构建失败指针是AC自动机的核心所在,玩转了它也就玩转了AC自动机,失败指针就是,当个人主串在trie树中进行匹配的时候,若是当前节点不能再继续进行匹配,那么咱们就会走到当前节点的fail节点继续进行匹配。
构造失败指针的过程归纳起来就一句话:对于root的儿子节点,fail指针直接指向root,其余的全部节点(用到了BFS和队列),设这个节点上的字母为C,沿着它父亲的失败指针走,直到走到一个节点,它的儿子中也有字母为C的节点。而后把当前节点的失败指针指向那个字母为C的节点。若是一直走到了root都没找到,那就把失败指针指向root。
构建好后,以下图:
针对图中红线的”h,e“这两个节点,咱们想起了什么呢?对”her“中的”e“来讲,e到root距离的n个字符刚好与”she“中的e向上的n个字符相等。
第三步:模式匹配
匹配过程分两种状况:
(1) 当前字符匹配成功,表示从当前节点沿着树边有一条路径能够到达目标字符,此时只需沿该路径走向下一个节点继续匹配便可,目标字符串指针移向下个字符继续匹配;
(2) 当前字符不匹配,则去当前节点失败指针所指向的字符继续匹配,匹配过程随着指针指向root结束。重复这2个过程当中的任意一个,直到模式串走到结尾为止。
注意:主串全部字符在匹配完后都必需要走fail节点来结束本身的旅途,至关于一个回旋,这样作的目的防止包含节点被忽略掉。
见下图,好比:我匹配到了"she",必然会匹配到该字符串的后缀”he",要想在程序中匹配到,则必须节点要走失败指针来结束本身的旅途。
三:完整代码
#include<iostream> #include<queue> #include<string.h> #define MAX 26//假设只出现26个小写英文字母 #define ROW 4 #define COLUMN 10 using namespace std; char pattern[ROW][COLUMN] = { "nihao","hao","hs","hsr" }; char *s = (char *)"sdmfhsgnshejfgnihaofhsrnihao"; struct Node { int index;//存储模式串的下标 char x; Node *parent; Node *next[MAX]; Node *fail; Node() { index = -1;//pattern数组下标从0开始,-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;//注意,在此保证输入的模式串不重复,不然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]);//不知道这是干吗的??想一想BFS层次遍历的那些事 } } } void ACTree::BuildFailPointer() { queue<Node*> q; for (int i = 0; i < MAX; i++) { if (root->next[i]) { NodeToQueue(root->next[i], q);//注意,切不可写成q.push(root->next[i]); 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; } }
四:测试
$ g++ ac_auto.cpp ~$ ./a.out 待匹配字符串为(依次5个一组的输出): dmfhs gnshe jfgni haofh srnih ao 匹配结果以下: 位置 编号 模式 4 2 hs 14 0 nihao 17 1 hao 20 2 hs 20 3 hsr 23 0 nihao 26 1 hao