AC自動機詳解

2022-08-05 14:21:26 字數 1635 閱讀 9144

ac自動機全稱aho-corasick automaton,該演算法在1975年產生于貝爾實驗室,是著名的多模匹配演算法。

考慮這樣一個場景,給出l個模式字串(加總長度為n),以及長度為m大文字,要求從大文字中提取每個模式字串出現的位置。如果使用kmp演算法,時間複雜度將達到o(lm+n),而使用ac自動機可以在o(n+m)時間複雜度內解決這一問題,當l很大時,ac自動機的優勢非常明顯。

ac自動機實際上是字首樹,但是會引入一個與kmp類似的失敗轉移的概念。我們先為所有模式建立對應的字首樹,之後為每個字首樹結點新增一個指標fail,指向另外一個字首樹中的結點。每個字首樹中的結點實際上都代表了某個模式的一段字首,我們之後將結點與其對應的字首等同起來。令結點x的fail指標指向y(y不為x),其中y是x的字尾,且y是所有符合這類條件的結點中深度最大的(字首長度最大的),我們稱y是x的字尾結點,稱x是y的偽父,顯然偽父的偽父依舊還是偽父。可以很容易證明以x為起點沿著fail指標不斷移動,可以遍歷所有x的有效字尾,且訪問到的結點深度遞減。如果無法為結點的fail指標無法找到有效的結點,那麼將fail指標指向字首樹的根結點root。

ac自動機的難度在於要如何為每個結點建立fail指標。由於fail指標指向的結點深度必然小於fail指標的持有者,因此可以用dp的思路,我們先為深度較小的結點建立fail指標,再為深度較大的結點建立fail指標。這個過程可以通過廣度優先搜尋演算法實現。要建立x的fail指標,考慮到x.fail.father必然是x.father的某個有效字尾,因此我們可以通過以x.father為起點,沿著fail指標移動以尋找x.fail.father,並從而找到x.fail。這個過程十分類似於kmp中建立跳轉表的過程,這裡對其具體操作不再贅述。

如何使用ac自動機呢?我們維護一個軌跡結點trace,對於每個輸入字元c,我們判斷trace是否有c號孩子,如果有就將trace設定為其c號孩子,否則我們將trace設定trace.fail,並繼續詢問,直到trace成為root或者找到了c號孩子。重複上面過程直到讀完文字。

若最後trace成功設定為其c號孩子,則我們稱訪問了c號孩子。可以證明若輸入文字t中t[a...b]與某個模式p相匹配,那麼當我們讀入t[b]時,p和p的所有偽父中有且只有一個結點被訪問。*對於任意c時間複雜度分為建立ac自動機的時間複雜度和匹配的時間複雜度。

設所有模式的長度和為n,文字長度為m。建立字首樹的時間複雜度為o(n),而建立fail指標的時間複雜度分析類似於kmp演算法中建立跳轉表的時間複雜度。我們可以定義每個結點x的fail指標指向的y結點的深度為x的“子深”,記作x.cd。很容易發現x.cd<=x.father.cd+1,而我們每次從x.father出發沿著fail指標移動,x的子深也在不斷遞減但不會低於0,在為某個模式上的結點建立fail時,每次後移最多提供1個子深,因此在建立模式pi時我們最多沿著fail指標移動了|pi|次,故建立所有模式總共沿著fail指標最多移動o(n)次,到此說明了建立fail指標的時間複雜度為o(n)。

對模式匹配,每當我們讀入一個字元c時,trace或者向下移動(即有c號孩子)並結束或者沿著fail移動到某個自己的字尾上去。顯然向下移動最多發生o(m)次,而沿著fail移動,就如同我所說的每次都必定會降低子深,而每次向下移動可以提供最多1子深,因此可以保證沿著fail移動的次數最多為o(m)次。故總的時間複雜度為o(m)。

時間複雜度的總和為o(n+m),空間複雜度為o(cn),其中c為使用的字符集的大小(用於建立字首樹)。