天天看點

Aho-Corasick自動機

    在模式比對問題中,如果模闆有很多個,KMP算法就不太适合了。因為每次查找一個模闆。都要周遊整個文本串。可不可以隻周遊一次文本串呢?可以,方法是把所有模闆組成一個大的狀态轉移圖(稱為$Aho-Corasick$自動機,簡稱$AC$自動機),而不是每個模闆各建一個狀态轉移圖。注意到KMP的狀态轉移圖是線性的字元串加上失配邊組成的,不難想到AC自動機是Trie加上失配邊組成的。

    下圖是$\{he,she,his,hers \}$的Trie。

Aho-Corasick自動機

   下圖是對應的Aho-Corasick自動機。

Aho-Corasick自動機

如果已經構造好AC自動機,比對算法幾乎和KMP是一樣的,代碼如下:

其中print函數為:

    代碼中出現了一個陌生的數組last,下面解釋以下。和Trie一樣,我們認為所有val[j]>0的結點都是單詞結點,反之亦然。但和Trie不同的是,同一個結點可能對應多個字元串的結尾,如圖所示:

Aho-Corasick自動機

    結點B不僅意味着找到串101,還意味着找到串01。換句話說,當找到一個模闆後,應該順着失配指針往回走,,看看有沒有其它串。當然,失配指針不一定指向一個單詞結點(比如,兩個串是101和010,那麼上圖的結點A不是單詞結點),為了提高效率,這裡增設一個指針last[j],表示結點j沿着失配指針往回走時,遇到的下一個單詞結點編号。這個last[j]在正規文獻中叫字尾連結(suffix link)。

    計算失配函數的方式和KMP很接近,隻是把線性遞歸改成了按照BFS順序遞推,代碼如下:

   由于失配工程比較複雜,要反複沿着失配邊走,在實踐中常常會把上述AC自動機改造一下,把所有不存在的邊補上,即把計算失配函數中的語句"​<code>​if(!u)  continue;​</code>​"改成:​<code>​if(!u){ ch[r][c] = ch[f[r]][c]; continue;}​</code>​

    這樣,就完全不需要失配函數了,而是對所有的轉移一視同仁也就是說,find函數中的語句​<code>​"while(j &amp;&amp; !ch[j][c])  j=f[j];"​</code>​; 可以直接完全删除。

個性簽名:時間會解決一切

繼續閱讀