一:概念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