數據結構與算法系列----AC自動機

一:概念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
相關文章
相關標籤/搜索