AC 經典多模式匹配算法 (詳細)

今天說說多模式匹配AC算法(Aho and Corasick),感謝追風俠幫忙整理資料,while(1) {Juliet.say("3Q");}。前面學習了BM、Wu-Manber算法,WM由BM派生,不過AC與它們無染,是另外一種匹配思路。

 

1. 初識AC算法

Step1: 將由patterns組成的集合(要同時匹配多個patterns嘛)構成一個有限狀態自動機。

Step2: 將要匹配的text作爲自動機輸入,輸出含有哪些patterns及patterns在全文中位置。

 

 

自動機的執行動作由三個部分組成:

(1)       一個goto function

(2)       一個failure function

(3)       一個output function

 

我們先通過一個具體實例來了解一下這三個部分,及該自動機的運作方式。先有個大概印象,後面會具體講解。patterns集合{he, she, his ,hers},我們要在”ushers”中查找並匹配。

goto function 

(1) goto function

 (q)-----------------a--------------->(r)

 狀態q到r,構成邊a,

g(q,a) = r


(2) failure function

i       1   2   3   4   5   6   7   8   9

f(i)     0   0   0   1   2   0   3   0   3 (發現沒?貌似if(i)有相同前綴哦^_^)


(3) output function

i           output(i)

2           {he}

5           {she,he}

7           {his}

9           {hers}

///////////////////////////////////////////////////////////////////////

       首先我們從狀態0開始,接收匹配字符串的第一個字符u,在goto(簡稱goto function)中可以看到回到狀態0,接着第二個字符s,發現轉到狀態3,在output中查找一下output(3)爲空字符串,說明沒有匹配到patterns。繼續匹配h,轉到狀態4,查找output發現仍然沒有匹配,繼續字符e,狀態轉到了5,查找output,發現output(5)匹配了兩個字符串she和he,並輸出在整個字符串中的位置。然後接着匹配r,但發現g(5,r)=fail,這時候我們需要查找failure,發現f(5)=2,所以就轉到狀態2,並接着匹配r,狀態轉移到了8,接着匹配s,狀態轉移到了9,查看output,並輸出output(9):hers,記錄下匹配位置。至此字符串ushers匹配完畢。

 ushers

g(0,u) = 0

g(0,s) = 3  ---> output(3)=$(空) 

g(3,h) = 4  ---> output(4)=$

g(4,e) = 5  ---> output(5):she he  RECOARD

g(5,r)  = fail   --->  f(5)=2

 g(2,r) = 8  ---> output(8)=$

g(8,s) = 9  ---> output(9):hers RECOARD

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

具體的匹配算法如下:

算法1. Pattern matching machine

輸入:text和M。text是x=a1a2...an,M是模式匹配自動機(包含了goto函數g(),failure函數f(),output函數output())

輸出:text中出現的pat以及其位置。

 

state←0

for i←1 until n do //吞入text的ai

     while g(state, ai)=fail 

         state←f(state) //直到能走下去,呵呵,至少0那個狀態怎麼着都能走下去

     state←g(state,ai) //得到下一個狀態

     if output(state)≠empty //能輸出就輸出

         print i;

         print output(state)

 

     AC算法的時間複雜度是O(n),與patterns的個數及長度都沒有關係。因爲Text中的每個字符都必須輸入自動機,所以最好最壞情況下都是O(n),加上預處理時間,那就是O(M+n),M是patterns長度總和。

 

2. 構造三表

OK,下面我們看看如何通過patterns集合構造上面的3個function

這三個function的構造分兩個階段:

(1)       我們決定狀態和構造goto function

(2)       我們計算得出failure function

而output function的構造貫穿於這兩個階段中。

 

2.1 goto 與 ouput填表

我們仍然拿實例來一步步構造:patterns集合{he,she,his,hers}

首先我們構造patterns   he

 he構造

然後接着構造she

 she構造

再構造his,由於在構造his的時候狀態0接收h已經能到狀態1,所以就不用重新建一個狀態了,有點像建trie樹的過程,共用一段相同的前綴部分

 his構造

最後構造hers

 hers構造

具體構造goto function算法如下:

算法2. Construction of the goto function

輸入:patterns集合K={y1,y2,...,yk}

輸出:goto function g 和output function output的中間結果

 

/*

We assume output(s) is empty when state s is first created, and g(s,a)=fail if a is

undefined or if g(s,a) has not yet been defined. The procedure enter(y) inserts into

the goto graph a path that spells out y.

*/

 

newstate←0

fori ← 1until k //對每個模式走一下enter(yi),要插到自動機裏來嘛

     enter(yi)

for all a such that g(0,a)=fail

     g(0,a)←0

 

 

enter(a1a2...am)

{

     state←0;j←;1

     while g(state,aj)≠fail //能走下去,就儘量延用以前的老路子,走不下去,就走下面的for()拓展新路子

         state←g(state,aj)

         j←j+1

 

     for p←j until m //拓展新路子

         newstate←newstate+1

         g(state,ap)←newstate

         state←newstate

 

     output(state)←{a1a2...am} //此處state爲每次構造完一個pat時遇到的那個狀態

}

 

2.2  Failure 與 output填表

Failure function的構造:(這個比較抽象)

大家注意狀態0不在failure function中,下面開始構造,首先對於所有depth爲1的狀態s,f(s)=0,然後歸納爲所有depth爲d的狀態的failure值都由depth-1的狀態得到。

具體講,在計算depth爲d的所有狀態時候,我們會考慮到每一個depth爲d-1的狀態r

1.       如果對於所有的字符a,g(r,a)=fail,那麼什麼也不做,我認爲這時候r已經是trie樹的葉子結點了。

2.       否則的話,如果有g(r,a)=s,那麼執行下面三步

(a)       設置state=f(r) //state記錄跟r共前綴的東東

(b)       執行state=f(state)零次或若干次,直到使得g(state,a)!=fail(這個狀態一定會有的因爲g(0,a)!=fail)//必須找條活路,能走下去的

(c)       設置f(s)=g(state,a),即相當於找到f(s)也是由一個狀態匹配a字符轉到的狀態。

實例分析:

首先我們將depth爲1的狀態f(1)=f(3)=0,然後考慮depth爲2的結點2,6,4

計算f(2)時候,我們設置state=f(1)=0,因爲g(0,e)=0,所以f(2)=0;

計算f(6)時候,我們設置state=f(1)=0,因爲g(0,i)=0,所以f(6)=0;

計算f(4)時候,我們設置state=f(3)=0,因爲g(0,h)=1,所以f(4)=1;

然後考慮depth爲3的結點8,7,5

計算f(8)時候,我們設置state=f(2)=0,因爲g(0,r)=0,所以f(8)=0;

計算f(7)時候,我們設置state=f(6)=0,因爲g(0,s)=3,所以f(7)=3;

計算f(5)時候,我們設置state=f(4)=1,因爲g(1,e)=2,所以f(5)=2;

最後考慮depth爲4的結點9

計算f(9)時候,我們設置state=f(8)=0,因爲g(0,s)=3,所以f(9)=3;

 

具體算法:

算法3. Construction of the failure function

輸入:goto function g and output function output from 算法2

輸出:failure function f and output function output

 

queue←empty

for each a such that g(0,a)=s≠0//其實這是廣搜BFS的過程

     queue←queue∪{s}

     f(s)←0

 

while queue≠empty

     pop();

     for each a such that g(r,a)=s≠fail //r是隊列頭狀態,如果r遇到a能走下去

         queue←queue∪{s} //那就走

         state←f(r) //與r同前綴的state

         while g(state,a)=fail //其實一定能找着不fail的,因爲至少g(0,a)不會fail

              state←f(state)

 

         f(s)←g(state,a) //OK,這一步相當於找到了s的同前綴狀態,即f(s)

 

         output(s)←output(s)∪output(f(s)) //建議走一下例子中g(4,e)=5的例子,然後ouput(5)∪output(2)={she,he}

 

2.3  output

Output function的構造參見算法2,3

 

3. 算法優化

改進1:觀察一下算法3中的failure function還不夠優化

 改進1

 

我們可以看到g(4,e)=5,如果現在狀態到了4並且當前的字符爲t!=e,因爲g(4,t)=fail,

所以就根據f(4)=1,跳轉到狀態1,而之前已經知道t!=e,所以就沒必要跳到1,而直接跳到狀態f(1)=0。

爲了避免不必要的狀態遷移,和KMP算法有異曲同工之處。重新定義了一個failure function:f1

 

f1(1)=0,

對於i>1,如果能使狀態f(i)轉移的所有字符也能使i狀態轉移,那麼f1(i)=f1(f(i)),

否則f1(i)=f(i)。

 

改進2:由於goto function中並不是每個狀態對應任何一個字符都有狀態遷移的,當遷移爲fail的時候,我們就要查failure function,然後換個狀態遷移。現在我們根據goto function和failure function來構造一個確定的有限自動機next move function,該自動機的每個狀態遇到每個字符都可以進行狀態遷移,這樣就省略了failure function。

 

構造next move function的算法如下:

算法4:Construction of a deterministic finite automaton

輸入:goto functioni g and failure function f

輸出:next move function delta

 

queue←empty

for each symbol a

     delta(0,a)←g(0,a)

     if g(0,a)≠0

         queue←queue∪g(0,a)

 

while queue≠empty

     pop()

     for each symbol a

         if g(r,a)=s≠fail

              queue←queue∪{s}

              delta(r,a)←s

else delta(r,a)←delta(f(r),a)

 

Next function delta的計算如下:

 改進2

其中’.’表示除了該狀態能識別字符的其他字符。

 

改進2有利有弊:好處是能減少狀態轉移的次數;壞處是由於狀態與狀態之間的遷移多了,導致存儲的空間比較大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章