字符串模式匹配算法2 - AC算法


 

上篇文章(http://www.cnblogs.com/zzqcn/p/3508442.html)里提到的BF和KMP算法都是單模式串匹配算法,也就是說,模式串只有一個。當需要在字符串中搜索多個關鍵字(模式)時,則需要用到多模式串匹配算法。

簡介

AC(Aho-Corasick)算法是一個經典的多模式串匹配算法,它借鑒了KMP算法的思想,可以由有限狀態機(Finite State Automata:FSA)來表示。AC算法的基本原理是:

先根據多模式串建立一個有限狀態自動機FSA,在進行模式匹配時,設當前狀態為Scur,輸入串中的當前符號為C,運行FSA,試圖得到下一狀態Snext。如果Snext無效,即發生失配,則根據規則,將當前狀態回退到一個適合的狀態Sfail,然后重復上一過程,直到Snext有效或Scur為0。在匹配的過程中,如果當前狀態正好匹配了某個模式P,則輸出它。

 

由AC算法的原理,我們可以知道,用於匹配的FSA與輸入串無關,而只與模式串有關;匹配過程中如果發生失配,則FSA應回退到某一狀態,而輸入串指針無需回退。這兩點都與KMP算法的思想吻合。

要實現基本的AC算法,即實現FSA邏輯,需要構建3樣東西:

  1. 狀態跳轉表goto :決定對於當前狀態S和條件C,如何得到下一狀態S‘
  2. 失配跳轉表fail : 決定goto表得到的下一狀態無效時,應該回退到哪一個狀態
  3. 匹配結果輸出表output : 決定在哪個狀態時輸出哪個恰好匹配的模式

 

構造示例

原理比較抽象,下面以一個簡單而且經典的例子(來自於AC算法創始人的論文)來演示一下AC算法的原理及goto、fail、output這3個表的構建。設多模式為 {“he”, “she”, “his”, “hers”}。下面來一步一步構建FSA。

首先規定一個初始狀態0,接着依次處理多個模式串,首先是he:

 

每個圓圈代表一個狀態(State),圓圈中的數字表示狀態的編號;有向箭頭代表狀態的轉換,它由當前狀態指向下一狀態,箭頭上的字符表示此次狀態轉換的條件。

接下來是she,注意每處理一個模式串時,都要先回到起始狀態0:

 

注意,如果給定條件C,從當前狀態出發能轉換到一個有效狀態,那么只進行狀態轉換,而不創建新狀態,這一點在處理后面兩個模式串時可以看到。接着是his:

 

最后是hers,而且AC規定,當當前狀態為初始狀態(0)時,對於任意條件C來,都能轉換到有效狀態(這也避免了回退的死循環),因此,除了條件h和s外,對其他任意條件,狀態0還應轉換至狀態0:

 

 

其實,最后的FSA圖就表示了goto表,因為上面畫的狀態及狀態轉換都是有效的。另外,也可以自然地知道,每處理完一個模式時,FSA的當前狀態都對應着此模式。因此,狀態2,5,7,8分別對應模式he, she, his, hers,也就是2,5,7,8這4個狀態對應的output表中的值。不過,到目前為止output表還未創建完成。

在下面的討論中,我們用S’ = goto(S,C)表示狀態S經由條件C轉換到狀態S‘,fail(S)表示狀態S的fail表值,output(S)表示狀態S的output表值。

goto表反映了有效的狀態轉換。而在fail表反映了轉換失敗時應回退到的狀態。比如,設當前狀態為2,當條件不是r時,狀態轉換就失敗,必須回退到某一個合適的狀態,這個狀態就是狀態2的fail值。而fail值的求法呢,和KMP算法中求next函數一樣,可以用遞推法來進行。

首先規定與狀態0距離為1(即深度為1)的所有狀態的fail值都為0。然后設當前狀態是S1,求fail(S1)。我們知道,S1的前一狀態必定是唯一的(從FSA圖也可以看出),設S1的前一狀態是S2,S2轉換到S1的條件為C,測試S3 = goto(fail(S2), C),如果成功,則fail(S1) = goto(fail(S2), C),如果不成功,繼續測試S4 = goto(fail(S3), C)是否成功,如此重復,直到轉換到某個有效的狀態Sn,令fail(S1) = Sn

做為例子,我們來求狀態3,4,5的fail值。首先,按照約定fail(3) == 0。接着求fail(4),由於goto(fail(3), h) == goto(0, h) == 1,所以fail(4) == 1。接着求fail(5),由於goto(fail(4), e) == goto(1, e) == 2,所以fail(5) == 2。在這里我們注意到,當FSA的狀態轉換到狀態5時,不僅匹配了she,而且匹配了he,這意味着對兩個狀態S和S’,S>S’,如果fail(S) == S‘,則output(S)應添加output(S’)。這樣一來,output表也構建完整了。fail表如下:

狀態 0 1 2 3 4 5 6 7 8 9
fail N/A 0 0 0 1 2 0 3 0 3

 

3個表構建完成后,就可以對輸入串進行模式匹配了。設輸入串為“ushers”,則匹配過程如下:

符號 狀態轉換 輸出
u 0->0  
s 0->3  
h 3->4  
e 4->5 she he
r 5->2 2->8  
s 8->9 hers

 

 注意,雖然掃描到符號r時經歷了狀態2,但那只是一個中間狀態,之后立刻切換到狀態8,因此並不輸出output(2)的內容{he},這也避免了模式”he”的重復輸出。

至此,我們已經構建了goto,fail,output 3個必需的表。這時構建的FSA也叫做非確定性有限狀態機(Nondeterministic Finite State Automata: NFA),也就是說,當匹配過程中發生失配時,應根據fail表進行狀態回退,但具體回退到哪個狀態為止,是不確定的,需要一次或多次循環回溯。可以預見,NFA會影響模式匹配的效率。可以通過構建確定性有限狀態機(Deterministic Finite State Automata: DFA)來彌補這個缺陷,它的原理是,在當前狀態下,對於任意條件,都可以確定地給出下一狀態,而不需要進行循環。

設當前狀態是S,條件為C,問題是如何求得下一個確定狀態S‘。如果goto(S, C)成功,則S’ = goto(S, C);否則,令S‘ = goto(fail(S), C),如果S’有效且不為0,則S‘就是那個確定狀態,此時,應把S’ = goto(S, C)這個關系添加到goto表中。可以預見,DFA會提高匹配速度,但由於向goto表添加了更多的條目,會導致存儲消耗增加。

做為例子,我們來求狀態1的確定狀態{S’}。首先fail(1)==0。當C==e或i時,S‘不變,分別是2,6。當C==h時,由於goto(0,h)==1,所以S’==1。當C==s時,由於goto(0, s)==3,所以S’==3。對於其他條件C,goto(0, C)==0。因此,我們需要往之前構建的goto表添加兩項:1=goto(1, h), 3=goto(1,s)。

 

C++簡單實現

下面,用簡單的C++代碼來實現前文所述的AC算法,包括NFA和DFA形式。

簡單介紹一下宏定義、數據結構和各函數的作用:

名稱 類型 說明
AC_FAIL_STATE 無效/失敗的跳轉狀態
AC_UNDEF_FAIL 未定義的fail表值
AC_CONT_SIZE 所有可能的條件,如果處理char字符,則范圍是0-255
ac_transition 結構 goto表項,不含當前狀態,因為它等於數組下標
FSAType 枚舉 FSA類型,fail表創建后變為NFA,然后可以轉換為DFA
ac_state_finder 函數對象 用於查找當前狀態下符合輸入條件的goto表項
ac_goto 函數 實現S'=goto(S, C)邏輯,用於goto表創建和模式匹配過程
ac_creat_goto_table 函數 創建goto表
ac_creat_fail_table 函數 創建fail表,完成后將實現NFA
ac_convert_to_DFA 函數 將NFA轉換為DFA。會向goto表添加新項。完成后不再需要fail表
ac_print_goto_table 函數 打印所有狀態及其對應的轉換表、fail值(僅限NFA)、output值
ac_search 函數 AC搜索函數

還有,NFA和DFA的創建過程中,需要用到類似圖的廣度優先搜索(Breadth-First-Search:BFS)算法,因此用到了隊列(std::queue)容器。

 

  1 // Author: 趙子清
  2 // Blog:   http://www.cnblogs.com/zzqcn
  3 
  4 #include <vector>
  5 #include <queue>
  6 #include <algorithm>
  7 #include <functional>
  8 #include <string>
  9 #include <cstdio>
 10 using namespace std;
 11 
 12 
 13 #define AC_FAIL_STATE   -1
 14 #define AC_UNDEF_FAIL   -1
 15 #define AC_CONT_SIZE    256
 16 
 17 
 18 struct  ac_transition
 19 {
 20     char  condition;
 21     int   next;
 22 };
 23 
 24 enum FSAType
 25 {
 26     FSA_NULL = 0,
 27     FSA_NFA,
 28     FSA_DFA
 29 };
 30 
 31 
 32 struct  ac_state_finder :
 33         public binary_function<ac_transition, char, bool>
 34 {
 35     bool  operator() (const ac_transition& t, char c) const
 36     {
 37         return  (t.condition == c);
 38     }
 39 };
 40 
 41 
 42 vector<vector<ac_transition>>   goto_table;
 43 vector<vector<string>>          output_table;
 44 vector<int>                     fail_table;
 45 FSAType  fsa_type = FSA_NULL;
 46 
 47 
 48 int  ac_goto(int _state, char _cont);
 49 int  ac_creat_goto_table(const vector<string>& _ptns);
 50 int  ac_creat_fail_table();
 51 int  ac_convert_to_DFA();
 52 int  ac_print_goto_table();
 53 int  ac_search(const string& _txt);
 54 
 55 
 56 int main(int argc, char** argv)
 57 {
 58     string ss[4] = {"he", "she", "his", "hers"};
 59     vector<string>  ptns(ss, ss+4);
 60     string txt = "ushers";
 61 
 62     ac_creat_goto_table(ptns);
 63     ac_creat_fail_table();
 64     ac_print_goto_table();
 65     ac_search(txt);
 66 
 67     ac_convert_to_DFA();
 68     ac_print_goto_table();
 69     ac_search(txt);
 70 
 71     return 0;
 72 }
 73 
 74 
 75 int  ac_goto(int _state, char _cont)
 76 {
 77     vector<ac_transition>::const_iterator ret = 
 78         find_if(goto_table[_state].begin(), goto_table[_state].end(),
 79         bind2nd(ac_state_finder(), _cont));
 80     if(goto_table[_state].end() == ret)
 81     {
 82         if(0 == _state)
 83             return 0;
 84         else
 85             return AC_FAIL_STATE;
 86     }
 87     else
 88         return ret->next;
 89 }
 90 
 91 
 92 int  ac_creat_goto_table(const vector<string>& _ptns)
 93 {
 94     int state = 0;
 95     int state_id = 0;
 96 
 97     ac_transition t;
 98     vector<ac_transition>  ts;
 99     vector<string>  ss;
100 
101     goto_table.push_back(ts);
102     output_table.push_back(ss);
103     state_id++;
104 
105     for(vector<string>::const_iterator i = _ptns.begin(); i != _ptns.end(); ++i)
106     {
107         state = 0;
108         for(string::const_iterator j=i->begin(); j<i->end(); ++j)
109         {
110             int next_state = ac_goto(state, *j);
111             if(0 == next_state || AC_FAIL_STATE == next_state)
112             {
113                 t.condition = *j;
114                 t.next = state_id++;
115                 goto_table[state].push_back(t);
116 
117                 goto_table.push_back(ts);
118                 output_table.push_back(ss);
119 
120                 state = t.next;
121             }
122             else
123                 state = next_state;
124         }
125         output_table[state].push_back(*i);
126     }
127   
128     return 0;
129 }
130 
131 
132 int  ac_creat_fail_table()
133 {
134     if(goto_table.empty())
135         return -1;
136 
137     fail_table.resize(goto_table.size());
138     for(size_t i=0; i<goto_table.size(); ++i)
139         fail_table[i] = AC_UNDEF_FAIL;
140 
141     queue<int>  q;
142     for(vector<ac_transition>::const_iterator i = goto_table[0].begin();
143         i != goto_table[0].end(); ++i)
144     {
145         fail_table[i->next] = 0;
146         q.push(i->next);
147     }
148 
149     int  state;
150     while(!q.empty())
151     {
152         state = q.front();  q.pop();
153 
154         for(vector<ac_transition>::const_iterator i = goto_table[state].begin();
155             i != goto_table[state].end(); ++i)
156         {
157             if(AC_UNDEF_FAIL != fail_table[i->next])
158                 continue;
159 
160             q.push(i->next);
161 
162             int  prev_state = state, ret;
163             do 
164             {
165                 prev_state = fail_table[prev_state];
166                 ret = ac_goto(prev_state, i->condition);
167             } while (AC_FAIL_STATE == ret);
168 
169             fail_table[i->next] = ret;
170             
171             for(vector<string>::const_iterator j = output_table[ret].begin();
172                 j != output_table[ret].end(); ++j)
173             {
174                 vector<string>::const_iterator sret =
175                     find(output_table[i->next].begin(), output_table[i->next].end(), *j);
176                 if(output_table[i->next].end() == sret)
177                     output_table[i->next].push_back(*j);
178             }
179         }
180     }
181 
182     fsa_type = FSA_NFA;
183 
184     return 0;
185 }
186 
187 
188 int  ac_convert_to_DFA()
189 {
190     if(fsa_type != FSA_NFA)
191         return -1;
192 
193     if(goto_table.empty() || fail_table.empty())
194         return -1;
195 
196     queue<int>  q;
197     for(vector<ac_transition>::const_iterator i = goto_table[0].begin();
198         i != goto_table[0].end(); ++i)
199     { q.push(i->next); }
200 
201     int  state;
202     while(!q.empty())
203     {
204         state = q.front();  q.pop();
205 
206         for(size_t c=0; c<AC_CONT_SIZE; ++c)
207         {
208             int  next_state = ac_goto(state, c);
209             if(next_state != AC_FAIL_STATE && next_state != 0)
210                 q.push(next_state);
211             else
212             {
213                 next_state = ac_goto(fail_table[state], c);
214                 if(next_state != AC_FAIL_STATE && next_state != 0)
215                 {
216                     ac_transition t;
217                     t.condition = c;
218                     t.next = next_state;
219                     goto_table[state].push_back(t);
220                 }
221             }
222         }
223     }
224 
225     fail_table.clear();
226     fsa_type = FSA_DFA;
227 
228     return 0;
229 }
230 
231 
232 void  OutputMatch(int _state, size_t _pos)
233 {
234     for(vector<string>::const_iterator i = output_table[_state].begin();
235         i != output_table[_state].end(); ++i)
236     {
237         printf("%d %s : %d\n", _state, i->c_str(), _pos - i->length());
238     }
239 }
240 
241 int  ac_search(const string& _txt)
242 {
243     if(goto_table.empty() || FSA_NULL == fsa_type)
244         return -1;
245 
246     int  state = 0;
247     string::size_type i;
248     for(i=0; i<_txt.length(); ++i)
249     {
250         char c = _txt[i];
251         if(output_table[state].size() > 0)
252             OutputMatch(state, i);
253 
254         if(FSA_NFA == fsa_type)
255         {
256             while(AC_FAIL_STATE == ac_goto(state, c))
257                 state = fail_table[state];
258             state = ac_goto(state, c);
259         }
260         else if(FSA_DFA == fsa_type)
261         {
262             state = ac_goto(state, c);
263             if(AC_FAIL_STATE == state)
264                 state = 0;
265         }
266     }
267 
268     if(output_table[state].size() > 0)
269         OutputMatch(state, i);
270 
271     return 0;
272 }
273 
274 
275 int  ac_print_goto_table()
276 {
277     if(goto_table.empty())
278         return -1;
279 
280     int  state_id = 0;
281     for(vector<vector<ac_transition>>::const_iterator i = goto_table.begin();
282         i != goto_table.end(); ++i, ++state_id)
283     {
284         printf("%d: ", state_id);
285 
286         if(FSA_NFA == fsa_type)
287             printf("%d ", fail_table[state_id]);
288 
289         for(vector<ac_transition>::const_iterator j = i->begin();
290             j != i->end(); ++j)
291         {
292             printf("%c->%d ", j->condition, j->next);
293         }
294 
295         for(vector<string>::const_iterator j = output_table[state_id].begin();
296             j != output_table[state_id].end(); ++j)
297         {
298             printf("(%s) ", j->c_str());
299         }
300         printf("\n");
301     }
302     printf("\n");
303 
304     return 0;
305 }

 


輸出結果:

0: -1 h->1 s->3
1: 0 e->2 i->6
2: 0 r->8 (he)
3: 0 h->4
4: 1 e->5
5: 2 (she) (he)
6: 0 s->7
7: 3 (his)
8: 0 s->9
9: 3 (hers)

5 she : 1
5 he : 2
9 hers : 2

0: h->1 s->3 1: e->2 i->6 h->1 s->3 2: r->8 h->1 s->3 (he) 3: h->4 s->3 4: e->5 h->1 i->6 s->3 5: h->1 r->8 s->3 (she) (he) 6: s->7 h->1 7: h->4 s->3 (his) 8: s->9 h->1 9: h->4 s->3 (hers) 5 she : 1 5 he : 2 9 hers : 2

 

參考資料:

【1】《Efficient String Matching: An Aid to Bibliographic Search》 - Alfred V. Aho & Margaret J. Corasick, 貝爾實驗室


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM