AC自動機是一種多模匹配的文本匹配算法。html
若是採用naive的方法,即依次比較文本串s中是否包含模式串p1, p2,...很是耗時。考慮到這些模式串中可能具備相同子串,能夠利用已經比較過的那些模式串的一些信息,來優化效率。容易想到的一種方法是爲這些模式串構建一個trie樹,能夠較好的利用模式串的公共前綴信息。node
可是若是隻是採用普通的trie樹,仍有 若是一個模式串P1不匹配,就要從新回到根節點再找下一個模式串P2,也就是對於下一個模式串P2,要從P2的起始元素開始,依次與文本串S比較。這一樣不夠高效。P2若是能夠利用和P1的一些共性信息,使得能夠從P2的儘量靠後的元素開始,與文本串S比較,那麼算法的時間複雜度可能會有效下降。算法
AC自動機採用了KMP算法找next的思路,爲trie樹中每一個節點找fail節點。數組
KMP中next與AC中fail的區別:app
KMP算法 中 next[j] = k 表示前綴 [0 ~ (k - 1)] 與 後綴 [(j - k) ~ (j - 1)] 這k個元素是對應相等的,可是P[j] 和 P[k] 是不必定相等要在下一次進行比較的;函數
AC自動機中則是 cur = cur.fail,也即從 [根節點 ~ cur.fail (包含cur.fail節點)] 這個前綴,與 [cur節點所在路徑上某節點 ~ cur節點(包含cur節點)] 這個後綴,對應相等。優化
(轉自參考連接1)spa
AC自動機的基礎是Trie樹。和Trie樹不一樣的是,樹中的每一個結點除了有指向孩子的指針(或者說引用),還有一個fail指針,它表示輸入的字符與當前結點的全部孩子結點都不匹配時(注意,不是和該結點自己不匹配),自動機的狀態應轉移到的狀態(或者說應該轉移到的結點)。fail指針的功能能夠類比於KMP算法中next數組的功能。指針
咱們如今來看一個用目標字符串集合{abd,abdk, abchijn, chnit, ijabdf, ijaij}構造出來的AC自動機code
上圖是一個構建好的AC自動機,其中根結點不存儲任何字符,根結點的fail指針爲null。虛線表示該結點的fail指針的指向,全部表示字符串的最後一個字符的結點外部都用紅圈表示,咱們稱該結點爲這個字符串的終結結點。每一個結點實際上都有fail指針,但爲了表示方便,本文約定一個原則,即全部指向根結點的 fail虛線都未畫出。
從上圖中的AC自動機,咱們能夠看出一個重要的性質:每一個結點的fail指針表示由根結點到該結點所組成的字符序列的全部後綴 和 整個目標字符串集合(也就是整個Trie樹)中的全部前綴 二者中最長公共的部分。
好比圖中,由根結點到目標字符串「ijabdf」中的 ‘d’組成的字符序列「ijabd」的全部後綴在整個目標字符串集{abd,abdk, abchijn, chnit, ijabdf, ijaij}的全部前綴中最長公共的部分就是abd,而圖中d結點(字符串「ijabdf」中的這個d)的fail正是指向了字符序列abd的最後一個字符。
1)表示當前結點的指針指向AC自動機的根結點,即curr = root
2)從文本串中讀取(下)一個字符
3)從當前結點的全部孩子結點中尋找與該字符匹配的結點,
若成功:判斷當前結點以及當前結點fail指向的結點是否表示一個字符串的結束,如果,則將文本串中索引發點記錄在對應字符串保存結果集合中(索引發點= 當前索引-字符串長度+1)。curr指向該孩子結點,繼續執行第2步
若失敗:執行第4步。
4)若fail == null(說明目標字符串中沒有任何字符串是輸入字符串的前綴,至關於重啓狀態機)curr = root, 執行步驟2,
不然,將當前結點的指針指向fail結點,執行步驟3)
來一個具體的例子加深理解,初始時當前結點爲root結點,咱們如今假設文本串text = 「abchnijabdfk」。
圖中的紫色實曲線表示了整個搜索過程當中的當前結點指針的轉移過程,結點旁的文字表示了當前結點下讀取的文本串字符。好比初始時,當前指針指向根結點時,輸入字符‘a’,則當前指針指向結點a,此時再輸入字符‘b’,自動機狀態轉移到結點b,……,以此類推。圖中AC自動機的最後狀態只是剛好回到根結點,並不必定都會回到根節點。
須要說明的是,當指針位於結點b(圖中曲線通過了兩次b,這裏指第二次的b,即目標字符串「ijabdf」中的b),這時讀取文本串字符下標爲9的字符(即‘d’)時,因爲b的全部孩子結點(這裏剛好只有一個孩子結點)中存在可以匹配輸入字符d的結點,那麼當前結點指針就指向告終點d,而此時該結點d的fail指針指向的結點又剛好表示了字符串「abc」的終結結點(用紅圈表示),因此咱們找到了目標字符串「abc」一次。這個過程咱們在圖中用虛線表示,但狀態沒有轉移到「abd」中的d結點。
在輸入完全部文本串字符後,咱們在文本串中找到了目標字符串集合中的abd一次,位於文本串中下標爲7的位置;目標字符串ijabdf一次,位於文本串中下標爲5的位置。
首先咱們將全部的目標字符串插入到Trie樹中,而後經過廣度優先遍歷爲每一個結點的全部孩子節點的fail指針找到正確的指向。
肯定fail指針指向的問題和KMP算法中構造next數組的方式一模一樣。具體方法以下
1)將根結點的全部孩子結點的fail指向根結點,而後將根結點的全部孩子結點依次入列。
2)若隊列不爲空:
2.1)出列,咱們將出列的結點記爲curr, failTo表示curr的fail指向的結點,即failTo = curr.fail
2.2) a.判斷curr.child[i] == failTo.child[i]是否成立,
成立:curr.child[i].fail = failTo.child[i],
不成立:判斷 failTo == null是否成立
成立: curr.child[i].fail == root
不成立:執行failTo = failTo.fail,繼續執行2.2)
b.curr.child[i]入列,再次執行再次執行步驟2)
若隊列爲空:結束
1 #coding:utf-8 2 import queue 3 4 class Node(object): 5 def __init__(self): 6 self.children = {} 7 self.fail = None 8 self.isWord = False 9 self.word = "" 10 11 class ACAutomation(object): 12 """ AC Automation 13 """ 14 def __init__(self): 15 self.root = Node() 16 17 def add(self, word): 18 cur_node = self.root 19 for char in word: 20 if char not in cur_node.children: 21 cur_node.children[char] = Node() 22 cur_node = cur_node.children[char] 23 cur_node.isWord = True 24 cur_node.word = word 25 26 def link_fail(self): 27 que = queue.Queue() 28 que.put(self.root) 29 30 while que.empty() == False: 31 32 cur_node = que.get() 33 cur_fail = cur_node.fail 34 35 for child_key, child_value in cur_node.children.items(): 36 37 while True: 38 if cur_fail is None: 39 cur_node.children[child_key].fail = self.root 40 break 41 42 elif child_key in cur_fail.children: 43 cur_node.children[child_key].fail = cur_fail.children[child_key] 44 break 45 46 else: 47 cur_fail = cur_fail.fail 48 49 que.put(cur_node.children[child_key]) 50 51 52 def curWords(self, cur_node): 53 """ 該函數爲查找當前節點處全部可能的匹配的模式串的集合 54 Args: 55 cur_node 當前節點 56 Returns: 57 set 當前節點處全部可能的匹配的模式串的集合 58 59 匹配成功模式串有兩種狀況: 60 1. 當前節點處 isWord = True, 則匹配的模式串即爲 cur_node.word (如圖例'ijabdf') 61 2. 當前節點的fail節點處 isWord = True, 則匹配的模式串爲 cur_node.fail.word 62 (如圖例 'abd',文字標紅處有解釋) 63 (固然fail節點也可能有fail.fail...須要while循環繼續推一下.) 64 """ 65 ret = set() 66 cur_fail = cur_node.fail 67 if cur_node.isWord: 68 ret.add(cur_node.word) 69 #ret.append(cur_node.word) 70 while cur_fail is not None and cur_fail is not self.root: 71 if cur_fail.isWord: 72 #ret.append(cur_fail.word) 73 ret.add(cur_fail.word) 74 cur_fail = cur_fail.fail 75 return ret 76 77 def search(self, s): 78 cur_node = self.root 79 # result = [] 80 result = set() 81 cur_pos = 0 82 83 while cur_pos < len(seq): 84 word = seq[cur_pos] 85 #result.extend(self.curWords(cur_node)) 86 87 while word in cur_node.children == False and cur_node != self.root: 88 # result.extend(self.curWords(cur_node)) 89 result |= self.curWords(cur_node) 90 cur_node = cur_node.fail 91 92 if word in cur_node.children: 93 #result.extend(self.curWords(cur_node)) 94 result |= self.curWords(cur_node) 95 cur_node = cur_node.children[word] 96 97 else: 98 cur_node = self.root 99 100 result |= self.curWords(cur_node) 101 102 cur_pos += 1 103 104 return list(result) 105 106 ac = ACAutomation() 107 l = ['abd', 'abdk', 'abchijn', 'chnit', 'ijabdf', 'ijaij'] 108 for e in l: 109 ac.add(e) 110 ac.link_fail() 111 seq = 'abchnijabdfk' 112 ret = ac.search(seq) 113 print(ret) 114 115 """output 116 """ 117 # ['abd', 'ijabdf']
參考連接:
1. 多模字符串匹配算法之AC自動機—原理與實現:http://www.javashuo.com/article/p-vqattoiu-cr.html
2. 從頭至尾完全理解KMP:http://www.javashuo.com/article/p-qceswqfj-hh.html
原文出處:https://www.cnblogs.com/shiyublog/p/12494120.html