標籤:style http 使用 strong 資料 width
1. 問題原型:
???????? 給定一篇網頁,當中有非常多禁用語或者無效的詞,須要找到一種演算法,找到這些敏感詞。
2. 怎樣求解呢?
?? 2.1 第一個簡單的思路是:
????????? step1: for i = 0 to in #text???
????????? step2:?????? foreach pattern p?
????????? step3:?????????? 比較 text[i+j] and p[0+j]? where j = 0 to #p??
???? 這個思路最大問題是,逐個比較每一個文本和每一個模式,演算法的複雜度是O(m*n*k),m是文本長度,n是pattern個數,k是全部pattern的總長度。my god!可想而知,這種演算法是不可容忍的。那麼有哪些改進方法呢:
???? 首先, hash_map 能夠降低大量的比較。
???? 其次, 對一個模式的比較,有非常多演算法如: KMP和Boyer-Moore。這兩個改進的演算法思想是:比較過的文本就不須要在比較了,直接shift 模式而不須要回溯Text,這兩個演算法能夠看這裡。以後有機會具體討論這些。多模式比對演算法也是能夠有相似的方法。
??? 我們知道,單模式比對演算法的複雜度能夠達到O(m+n),所以最主要的改進是:
??? 2.2.? naive muti-pattern match
?????????? step1: foreach pattern p
?????????? step2:??????? 運行 O(m+#p)的單模式比對演算法?
????????? 演算法複雜度是: O(m + #P1 + m + #P2 + …… + m + #pn) = O( n*m + k)
??? 好慢!假設模式的個數有幾百萬個,速度是不可忍受
3. 有哪些演算法解決多模式比對問題:
??? AC (Aho-Corasick algorithm)
??? ACBM(CW)[ A String Matching Algorithm Fast on the Average ]
??? WM [Wu and Manber]
??? ACQS
??? DAWG(ACRF)
??? MultiBDM
3.1 Aho-Corsick Algorithm:
AC 演算法的核心思想是構造詞典的自己主動機(能夠使用trie樹來實現), 其演算法複雜度是O(m+k+z), m是文本長度,k是全部pattern長度之和,z是字串中出現pattern的個數。
舉個範例來說:Pattern P = {he; she; his; hers} ,AC演算法將建立例如以的字典樹(或者成為keyword tree)
當中node中有數位表示在詞典(pattern)中,每一條邊表示一個字元,同一個節點指出的不同邊擁有不同的字元標籤。樸素字典樹的構建相當簡單,?? 僅僅須要一個pattern一個pattern的插入就能夠了(當然其它改進的演算法和改進的資料結構,如double-trie)
Construction:
1. foreach pattern in P{p1,p2,…,pn}
2. start at the Root
??? 2.1 假設從根節點出發的路徑在Pi結束之前結束,建立為Pi中每一個沒有結束的字元建立節點。 否則繼續搜尋
??? 2.2 建立Pi的終止節點,設定唯一標識
一旦字典數構建結束,尋找過程跟我們平時用查字典的過程是一樣的,從根節點一步步向下查,直到找到該詞或者沒有之結點為止。可是,從以上過程我們發現其複雜度是O(m×k),這顯然不是我們想要的結果。okay,那我們該建立一個自己主動機(automaton):
自己主動機由狀態(states)和行動(action)組成,在本例中:
State:?? 字典樹的節點,初始狀態為0
Action:? action有3個函數決定:
?????????? 1. goto函數 g(q,a), 它表示,假設目前狀態是q,輸入輸入字元是a,那麼下一個狀態是g(q,a)
?????????????? a)假設邊edge(q, r) 標記為a, 那麼 g(q,a) = r
?????????????? b) 假設a不輸入0狀態的出邊 g(0,a)? = 0 , g(q,a) =? $(空)
?????????? 2. failure函數 f(q), 對q不為0的狀態,failure函數表示在狀態q失配
?????????????? f(q)是狀態機器中的一個節點,標記為pattern中某個pattern恰當的最長尾碼(例如以5的he)
?????????? 3. out函數 out(q), 狀態q,識別出某個pattern
虛線就是表示failure函數的action,比方從5到2的action。假設字串為sher,搜尋到s—h—e,下一個串是r,failure函數能夠使得字典樹的搜尋從5,跳到2, ({he} 是{ she} 的尾碼),然後迅速的找到her。 能夠看到整個過程中,匹配過程直接依照自己主動機的順序匹配,根本不須要回溯字串(比方sher的範例,一旦she找到,直接從e的下一個字元r開始匹配,而不是從s的下一個字元h開始)。演算法例如以下:
step1 q = 0
step2 for i = 0 to m do
step3????? while (g(q, T[i])) == $ do
step4??????????? q = f(q)
step5?????? q = g(q, T[i])
step6?????? if out(q) != $ then output(q)
好簡潔,這個搜尋過複雜度是O(m+z)z是pattern在字串中出現的次數!證明從略^_^
問題是,這個AC自己主動機怎樣構建?
1. 第一步:構建字典數,和root
2. 第二步:構建failure函數
第一步:
??????? 1.1 構建字典數, foreach pattern pi in P,設定out(v)= pi,假設v節點標記為pi
??????? 1.2 構建root, g(0,a) = 0, 假設a不是root的輸出邊
結果
?
第二步:
第二步基於一個簡單的事實,假設 g(q,a) = u (q---a--->u), 而且{f(q),f(f(q)), f(f(f(q)))…,root}都已經計算出來,那麼f(u)為
第一個不為空白的g(fi(q),a),說起來好繞口啊,還是直接看演算法吧:
1.? Q = emptyQueue
2.? foreach a in alphabet??
3?????? if q(0,a) == q != 0 then
4????????????? f(q):=0; Q.enqueue(q)?
5?? while no Q.empty() do
6?????? r = Q.dequeue()
7?????? foreach a in alphabet
8?????????? if g(r,a) == u != null then
9?????????????? Q.enqueue(u)
10????????????? v = f(r)
11????????????? while g(v,a) == null do v = f(v)
12????????????? f(u) = g(v,a)
13????????????? out(u) = out(u) union out(f(u))
大功告成!注意,AC演算法對多模式比對的一些變種問題也能夠非常好的處理(e.g? pattern中含有萬用字元,甚至是正則表達式),代碼我就不貼了,download here! ac.h, ac.c
參考:
1.? http://www.egeen.ee/u/vilo/edu/2005-06/Text_Algorithms/index.cgi?f=L2_Multiple_String
2.? http://www.itl.nist.gov/div897/sqg/dads/HTML/boyermoore.html
下一篇多模式比對的ACBM演算法,當中還涉及單個模式比對(或者稱為Exact search)的Boyer-Moore技術