1. 程式人生 > >AC自動機演算法及模板

AC自動機演算法及模板

  • 關於AC自動機
  1. AC自動機:Aho-Corasickautomation,該演算法在1975年產生於貝爾實驗室,是著名的多模匹配演算法之一。一個常見的例子就是給出n個單詞,再給出一段包含m個字元的文章,讓你找出有多少個單詞在文章裡出現過。要搞懂AC自動機,先得有模式樹(字典樹)Trie和KMP模式匹配演算法的基礎知識。AC自動機演算法分為3步:構造一棵Trie樹,構造失敗指標和模式匹配過程。
  2. 簡單來說,AC自動機是用來進行多模式匹配(單個主串,多個模式串)的高效演算法。
  • AC自動機的構造過程
使用Aho-Corasick演算法需要三步:
  1. 建立模式串的Trie
  2. 給Trie新增失敗路徑
  3. 根據AC自動機,搜尋待處理的文字

我們以下面這個例子來介紹AC自動機的運作過程

給定5個單詞:say she shr he her,然後給定一個字串  yasherhs。問一共有多少單詞在這個字串中出現過。

  • 確定資料結構
首先,我們需要確定AC自動機所需的資料儲存結構,它們的用處之後會講到。
struct Node
{
	int cnt;//是否為該單詞的最後一個結點 
	Node *fail;//失敗指標 
	Node *next[26];//Trie中每個結點的各個節點 
}*queue[500005];//佇列,方便用BFS構造失敗指標 
char s[1000005];//主字串 
char keyword[55];//需要查詢的單詞 
int head,tail;
Node *root;//頭結點 

第一步:構建Trie

根據輸入的 keyword 一 一 構建在Trie樹中

void Build_trie(char *keyword)//構建Trie樹 
{
	Node *p,*q;
	int i,v;
	int len=strlen(keyword);
	for(i=0,p=root;i<len;i++)
	{
		v=keyword[i]-'a';
		if(p->next[v]==NULL)
		{
			q=(struct Node *)malloc(sizeof(Node));
			Init(q);
			p->next[v]=q;//結點連結 
		}
		p=p->next[v];//指標移動到下一個結點 
	}
	p->cnt++;//單詞最後一個結點cnt++,代表一個單詞 
}

構建完成後的效果如下圖:

  • 構建失敗指標

  • 構建失敗指標是AC自動機的關鍵所在,可以說,若沒有失敗指標,所謂的AC自動機只不過是Trie樹而已。
  • 失敗指標原理:
  • 構建失敗指標,使當前字元失配時跳轉到另一段從root開始每一個字元都與當前已匹配字元段某一個字尾完全相同且長度最大的位置繼續匹配,如同KMP演算法一樣,AC自動機在匹配時如果當前字串匹配失敗,那麼利用失配指標進行跳轉。由此可知如果跳轉,跳轉後的串的字首必為跳轉前的模式串的字尾,並且跳轉的新位置的深度(匹配字元個數)一定小於跳之前的節點(跳轉後匹配字元數不可能大於跳轉前,否則無法保證跳轉後的序列的字首與跳轉前的序列的字尾匹配)。所以可以利用BFS在Trie上進行失敗指標求解。
  • 失敗指標利用:
  • 如果當前指標在某一字元s[m+1]處失配,即(p->next[s[m+1]]==NULL),則說明沒有單詞s[1...m+1]存在,此時,如果當前指標的失配指標指向root,則說明當前序列的任何字尾不是是某個單詞的字首,如果指標的失配指標不指向root,則說明當前序列s[i...m]是某一單詞的字首,於是跳轉到當前指標的失配指標,以s[i...m]為字首繼續匹配s[m+1]。
  • 對於已經得到的序列s[1...m],由於s[i...m]可能是某單詞的字尾,s[1...j]可能是某單詞的字首,所以s[1...m]中可能會出現單詞,但是當前指標的位置是確定的,不能移動,我們就需要temp臨時指標,令temp=當前指標,然後依次測試s[1...m],s[i...m]是否是單詞。
  • >>>簡單來說,失敗指標的作用就是將主串某一位之前的所有可以與模式串匹配的單詞快速在Trie樹中找出。

第二步:構建失敗指標

  1. 在構造完Tire樹之後,接下去的工作就是構造失敗指標。構造失敗指標的過程概括起來就一句話:設這個節點上的字母為C,沿著它父親節點的失敗指標走,直到走到一個節點,它的子結點中也有字母為C的節點。然後把當前節點的失敗指標指向那個字母也為C的兒子。如果一直走到了root都沒找到,那就把失敗指標指向root。具體操作起來只需要:先把root加入佇列(root的失敗指標指向自己或者NULL),這以後我們每處理一個點,就把它的所有兒子加入佇列。
  2. 觀察構造失敗指標的流程:對照圖來看,首先root的fail指標指向NULL,然後root入隊,進入迴圈。從佇列中彈出root,root節點與s,h節點相連,因為它們是第一層的字元,肯定沒有比它層數更小的共同前後綴,所以把這2個節點的失敗指標指向root,並且先後進入佇列,失敗指標的指向對應圖中的(1),(2)兩條虛線;從佇列中先彈出h(右邊那個),h所連的只有e結點,所以接下來掃描指標指向e節點的父節點h節點的fail指標指向的節點,也就是root,root->next['e']==NULL,並且root->fail==NULL,說明匹配序列為空,則把節點e的fail指標指向root,對應圖中的(3),然後節點e進入佇列;從佇列中彈出s,s節點與a,h(左邊那個)相連,先遍歷到a節點,掃描指標指向a節點的父節點s節點的fail指標指向的節點,也就是root,root->next['a']==NULL,並且root->fail==NULL,說明匹配序列為空,則把節點a的fail指標指向root,對應圖中的(4),然後節點a進入佇列。接著遍歷到h節點,掃描指標指向h節點的父節點s節點的fail指標指向的節點,也就是root,root->next['h']!=NULL,所以把節點h的fail指標指向右邊那個h,對應圖中的(5),然後節點h進入佇列...由此類推,最終失配指標如圖所示。
構建失敗指標的程式碼:
void Build_AC_automation(Node *root)
{
	head=0,tail=0;//佇列頭、尾指標 
	queue[head++]=root;//先將root入隊 
	while(head!=tail)
	{
		Node *p=NULL;
		Node *temp=queue[tail++];//彈出隊頭結點 
		for(int i=0;i<26;i++)
		{
			if(temp->next[i]!=NULL)//找到實際存在的字元結點 
			{ //temp->next[i] 為該結點,temp為其父結點 
				if(temp==root)//若是第一層中的字元結點,則把該結點的失敗指標指向root 
					temp->next[i]->fail=root;
				else
				{
					//依次回溯該節點的父節點的失敗指標直到某節點的next[i]與該節點相同,
                	//則把該節點的失敗指標指向該next[i]節點; 
                	//若回溯到 root 都沒有找到,則該節點的失敗指標指向 root
					p=temp->fail;//將該結點的父結點的失敗指標給p 
					while(p!=NULL)
					{
						if(p->next[i]!=NULL)
						{
							temp->next[i]->fail=p->next[i];
							break;
						}
						p=p->fail;
					}
					//讓該結點的失敗指標也指向root 
					if(p==NULL)
						temp->next[i]->fail=root;
				}
				queue[head++]=temp->next[i];//每處理一個結點,都讓該結點的所有孩子依次入隊 
			}
		}
	}
}
  • 為什麼上述那個方法是可行的,是可以保證從root到所跳轉的位置的那一段字串長度小於當前匹配到的字串長度且與當前匹配到的字串的某一個字尾完全相同且長度最大呢?

  • 顯然我們在構建失敗指標的時候都是從當前節點的父節點的失敗指標出發,由於Trie樹將所有單詞中相同字首壓縮在了一起,所以所有失敗指標都不可能平級跳轉(到達另一個與自己深度相同的節點),因為如果平級跳轉,很顯然跳轉所到達的那個節點肯定不是當前匹配到的字串的字尾的一部分,否則那兩個節點會合為一個,所以跳轉只能到達比當前深度小的節點,又因為是由當前節點父節點開始的跳轉,所以這樣就可以保證從root到所跳轉到位置的那一段字串長度小於當前匹配到的字串長度。另一方面,我們可以類比KMP求NEXT陣列時求最大匹配數量的思想,那種思想在AC自動機中的體現就是當構建失敗指標時不斷地回到之前的跳轉位置,然後判斷跳轉位置的下一個字元是否包含當前字元,如果是就將失敗指標與那個跳轉位置連線,如果跳轉位置指向NULL就說明當前匹配的字元在當前深度之前沒有出現過,無法與任何跳轉位置匹配,而若是找到了第一個跳轉位置的下一個字元包含當前字元的的跳轉位置,則必然取到了最大的長度,這是因為其餘的當前正在匹配的字元必然在第一個跳轉位置的下一個字元包含當前字元的的跳轉位置深度之上,而那樣的跳轉位置就算可以,也不會是最大的(最後一個字元的深度比當前找到的第一個可行的跳轉位置的最後一個字元的深度小,串必然更短一些)。
  • 第三步:匹配 這樣就證明了這種方法構建失敗指標的可行性。
第三步:匹配
  1. 最後,我們便可以在AC自動機上查詢模式串中出現過哪些單詞了。匹配過程分兩種情況:(1)當前字元匹配,表示從當前節點沿著樹邊有一條路徑可以到達目標字元,此時只需沿該路徑走向下一個節點繼續匹配即可,目標字串指標移向下個字元繼續匹配;(2)當前字元不匹配,則去當前節點失敗指標所指向的字元繼續匹配,匹配過程隨著指標指向root結束。重複這2個過程中的任意一個,直到模式串走到結尾為止。
  2. 對例子來說:其中模式串為yasherhs。對於i=0,1。Trie中沒有對應的路徑,故不做任何操作;i=2,3,4時,指標p走到左下節點e。因為節點e的count資訊為1,所以cnt+1,並且講節點e的count值設定為-1,表示改單詞已經出現過了,防止重複計數,最後temp指向e節點的失敗指標所指向的節點繼續查詢,以此類推,最後temp指向root,退出while迴圈,這個過程中count增加了2。表示找到了2個單詞she和he。當i=5時,程式進入第5行,p指向其失敗指標的節點,也就是右邊那個e節點,隨後在第6行指向r節點,r節點的count值為1,從而count+1,迴圈直到temp指向root為止。最後i=6,7時,找不到任何匹配,匹配過程結束。
  3. AC自動機時間複雜性為:O(L(T)+max(L(Pi))+m)其中m是模式串的數量
匹配程式碼:
int query(Node *root)
{ //i為主串指標,p為模式串指標 
	int i,v,count=0;
	Node *p=root;
	int len=strlen(s);
	for(i=0;i<len;i++)
	{
		v=s[i]-'a';
		//由失敗指標回溯查詢,判斷s[i]是否存在於Trie樹中 
		while(p->next[v]==NULL && p!=root)
			p=p->fail;
		p=p->next[v];//找到後p指標指向該結點 
		if(p==NULL)//若指標返回為空,則沒有找到與之匹配的字元 
			p=root;
		Node *temp=p;//匹配該結點後,沿其失敗指標回溯,判斷其它結點是否匹配 
		while(temp!=root)//匹配結束控制 
		{
			if(temp->cnt>=0)//判斷該結點是否被訪問 
			{
				count+=temp->cnt;//由於cnt初始化為 0,所以只有cnt>0時才統計了單詞的個數 
				temp->cnt=-1;//標記已訪問過 
			}
			else//結點已訪問,退出迴圈 
				break;
			temp=temp->fail;//回溯 失敗指標 繼續尋找下一個滿足條件的結點 
		}
	}
	return count;
}

相關推薦

AC自動機演算法模板

關於AC自動機 AC自動機:Aho-Corasickautomation,該演算法在1975年產生於貝爾實驗室,是著名的多模匹配演算法之一。一個常見的例子就是給出n個單詞,再給出一段包含m個字元

【python 走進NLP】兩種高效過濾敏感詞演算法--DFA演算法AC自動機演算法

一道bat面試題:快速替換10億條標題中的5萬個敏感詞,有哪些解決思路? 有十億個標題,存在一個檔案中,一行一個標題。有5萬個敏感詞,存在另一個檔案。寫一個程式過濾掉所有標題中的所有敏感詞,儲存到另一個檔案中。 1、DFA過濾敏感詞演算法 在實現文字過濾的演算法中,DFA是

二分匹配 (匈牙利演算法模板

   二分匹配我認為其實就是m個女生,n個男生相互認識的在一起,求最大匹配成的對數。    二分匹配的時間複雜度是O(NM) 相關概念: 最大匹配數:最大匹配的匹配邊的數目 定理1:最大匹配數 = 最小點覆蓋數(這是 Konig 定理)

poj 3691(AC自動機,新模板)

.題意是說給了N個帶病毒的DNA串( DNA串只有AGCT幾種單元組成)...再給一長串DNA..問這長串DNA最少改動幾個(就是改..不是刪除或者新增..)能保證沒有包含病毒字串..輸出這個最小改動的次數..若怎麼修改都帶病毒子串...輸出-1... 思路:這是我第一道調

AC自動機演算法學習

KMP+TRIE int val[1000100][31],tot; int tr[1000100]; int fail[1000100]; struct AC_Trie{ void clean(){ tot=0; memset(val,0,sizeof(val));

AC 自動機演算法詳解(轉)

首先簡要介紹一下AC自動機:Aho-Corasick automation,該演算法在1975年產生於貝爾實驗室,是著名的多模匹配演算法之一。一個常見的例子就是給出n個單詞,再給出一段包含m個字元的文章,讓你找出有多少個單詞在文章裡出現過。要搞懂AC自動機,先得有模式樹(字典樹)Trie和KMP模式匹配演算法

AC自動機演算法筆記

  AC演算法是Alfred V.Aho(《編譯原理》(龍書)的作者),和Margaret J.Corasick於1974年提出(與KMP演算法同年)的一個經典的多模式匹配演算法,可以保證對於給定的長度為n的文字,和模式集合P{p1,p2,...pm},在O(n

[置頂]AC自動機-演算法詳解

What's Aho-Corasick automaton?   一種多模式串匹配演算法,該演算法在1975年產生於貝爾實驗室,是著名的多模式匹配演算法之一。   簡單的說,KMP用來在一篇文章中匹配一個模式串;但如果有多個模式串,需要在一篇文章中把出現過的模式串都匹配出來,

ac自動機背誦用模板

    void insert(){         scanf("%s",ch+1);         int l=strlen(ch+1),now=1;         for(int i=1;

演算法講解:ac自動機簡單衍生

AC自動機簡介:  首先簡要介紹一下AC自動機:Aho-Corasick automation,該演算法在1975年產生於貝爾實驗室,是著名的多模匹配演算法之一。一個常見的例子就是給出n個單詞,再給出一段包含m個字元的文章,讓你找出有多少個單詞在文章裡出現過。嗯沒錯,比

ac自動機基礎模板(hdu2222)

val gin 代碼 strlen ont logs memset define mode In the modern time, Search engine came into the life of everybody like Google, Baidu, etc.

AC自動機模板

insert 自動機 mat roo oot node truct amp 字典樹 struct node { node *next[26]; node *fail; int sum; }; int cnt; node *roo

HDU3695(AC自動機模板題)

16px tdi out 模板題 getc else return typedef queue   題意:給你n個字符串,再給你一個大的字符串A,問你著n個字符串在正的A和反的A裏出現多少個?   其實就是AC自動機模板題啊( ╯□╰ )   正著query一次再反著que

hdu2222(ac自動機模板)

har isp truct itl color 樹節點 namespace print 好的 先推薦兩篇寫的很好的ac自動機blog: http://blog.csdn.net/creatorx/article/details/71100840 http://blog.cs

HDU 2222 Keywords Search(AC自動機模板題)

stack uil empty cst keywords cto ble ont max http://acm.hdu.edu.cn/showproblem.php?pid=2222 題意:給出多個單詞,最後再給出一個模式串,求在該模式串中包含了多少個單詞。 思路

HDU 2222 Keywords Search (AC自動機模板題)

出現 tro spa 繼續 time int cas keyword arc Keywords Search Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Othe

模板練習——AC自動機】Keywords Search HDU - 2222

rpg soc key words 練習 evm cdc rmi css x床堂0jz直寐0裙brhttp://tushu.docin.com/sghp1512 6茲c苯晌62恢uk爻2http://tushu.docin.com/ipt586 gw誹喜2i4偎e2擻a

AC自動機及其模板

closed i++ auto ota color namespace 構建 malloc 模板題 模板 #include<queue> #include<stdio.h> #include<string.h> using names

luogu P3808 【模板AC自動機(簡單版)

重復 數組 max space length range spa truct ron 題目背景 這是一道簡單的AC自動機模板題。 用於檢測正確性以及算法常數。 為了防止卡OJ,在保證正確的基礎上只有兩組數據,請不要惡意提交。 管理員提示:本題數據內有重復的單詞,且重

模板 AC自動機

oid 一行 i++ else amp content ring lan algorithm 題目描述 有$N$ 個由小寫字母組成的模式串以及一個文本串$T$ 。每個模式串可能會在文本串中出現多次。你需要找出哪些模式串在文本串$T$ 中出現的次數最多。 輸入