一個有限自動機 M 是一個5元組(Q, q0,A, Σ, δ),其中:
ios
Q 是全部狀態的有限集合;算法
q0 ∈ Q (屬於)是初始狀態;函數
A ⊆ Q (子集)是接受狀態的集合;(對應於多模式?)測試
Σ 是有限輸入字母表;spa
δ 是從Q * Σ的轉移函數,稱爲有限自動機M的轉移函數;debug
記號與術語:
Σ* 表示用字母表Σ中全部字符造成的全部有限長度的字符串集合.
n 輸入字符串(input string)的長度.
m 模式字符串(pattern string)的長度;也稱做終態m,當狀態爲m時表示,m長度的模式串匹配成功.code
|x| : 字符串x的長度, 如示符號記法.遞歸
: 字符串w 是字符串x 的前綴,如示符號記法.字符串
: 字符串w 是字符串x 的後綴,如示符號記法.(注意前綴/後綴均遵循傳遞規則)input
ε:表示空字符串,是全部字符串的後綴,前綴. (ε讀做 epsilon )
a : 下文中的字符a泛指全部字符(a∈Σ),不特指字符'a'.
轉移函數δ(transition function) ( δ 讀做"delta",對應大寫爲 Δ )
有限自動機開始於初始狀態q0,每次讀入輸入字符串的一個字符,若是有限自動機在狀態q是讀入字符'a',
則M狀態從q變成 δ(q, a);
終態函數 Φ(finite state function) ( Φ 讀做"fai", 對應小寫爲 φ )
是從 Σ* 到 Q 的函數,Φ(w)是永動機M 掃描字符串 w 終止後的狀態;M 接受字符串w 當且僅當Φ(w)∈A, 函數Φ有下列遞歸關係定義:
φ(ε) = q0;(空字符串 ε 的終態爲q0)
φ(wa) = δ(φ(w),a) (其中w∈Σ*,a∈Σ)
輔助函數,後綴函數σ 對應於模式字串P ( σ 讀做 "sigma", 對應大寫爲 Σ )
是從Σ* 到{0,1, ..., m}上的映射,σ(x)是字符串x的後綴同時是P的前綴的最大長度;
σ(x) = max{k: Pk ⊐ x }
有P0 = ε是全部全部字符串的後綴;
* 後綴函數的主要意義的是求出當前匹配失敗時,求出已經匹配過的部分字串x是不是待匹配模式字串P的前綴,即匹配能夠跳過x中部分長度( σ(x) ),能夠用於實現轉移過程;同時也代表在接受輸入字符串x後的狀態(終態),即也用於實現終態函數。
下圖是依據模式串 P="ababaca" 構建的自動機圖表:
上圖(a)是一個自動機的狀態轉換圖表,接受全部以字符串"ababaca"結尾的字符串。其中狀態0是初始狀態,狀態7是惟一接受狀態(單模式匹配).
1) 從狀態i到狀態j的帶箭頭的有向邊表示轉移過程: δ(i, a) = j(a∈Σ).
2) 右向邊組成了自動機的主要"骨架",圖中粗線部分,對應於輸入字符同模式字串匹配成功的轉移過程。左向邊對應於匹配失敗的轉移過程(跳轉,主要是計算已經匹配的部分字串 的後綴子串同時是模式串P的前綴的最大長度).部分匹配失敗的過程沒有標示出來。
3) 圖中部分狀態i在接受某字符a(a∈Σ)時,沒有標示出對應有向邊的狀況代表其轉移過程爲: δ(i, a) = 0(a∈Σ),根據下面字符串模式匹配自動機定義,知當前已經匹配子串沒有後綴字串是模式串P的前綴。如在狀態3時,輸入字符爲'c',即在已經匹配了"aba"這時接受字符'c',知當前已匹配字串爲"abac",對應模式字串P="ababaca",可知這時匹配失敗,進行失敗跳轉求"abac"後綴子串同時是模式串P前綴的最大長度,可知爲0.
4) 匹配成功的轉移過程(對應狀態,以及對應輸入字符)均標示爲灰色,
5) 表(c)是自動機在處理(接受)輸入文本T="abababacaba"的最終狀態表。當輸入字符T[i]時,此時字串T[0...i]對應的的最終狀態φ(T[0...i]) 同表(c)最後一列一一對應。有T["abababaca"] = P.length = 7(惟一接受狀態),即這時候在T串中匹配成功模式串P,結束位置爲9,起始位置爲(9-P.length+1)=3。
一、字符串匹配有限自動機定義:
給定模式(pattern)字符串 P[1...m],其對應的字符串匹配有限自動機定義以下:
一、狀態集Q = {0,1,...m},開始狀態q0 是狀態0,state m 是惟一的接受狀態;
二、轉移函數δ 能夠用後綴函數來表示 (這個很重要, 由於狀態轉移函數是個抽象概念,然後綴函數能夠用code表示) :
δ(q,a) = σ(Pq,a) <等式一>
假設當前已經讀入的字符串爲T,爲了讓T的字串(以T[i]爲結尾) 能匹配模式字串Pj,必須知足Pj是Ti的後綴;同時假設q = φ(Ti),說明讀取字串Ti後自動機M 狀態變成q;同時根據轉移函數<等式一>可知q是模式字串P最大長度的前綴,同時是Ti的後綴;所以在狀態q,有Pq ⊐ Ti 和 q = σ(Ti) (當q 等於m 時,說明模式字串P整個是Ti的後綴,也意味着匹配查找成功了),所以有σ(Ti)= q,得出永動機也支持下面的等式(終態函數也是抽象的,轉化爲後綴函數表達式後,能夠用code表示):
φ(Ti) = σ(Ti)(i = 0,1,...n) <等式二>
二、同時有兩個引理(具體證實能夠參考算法導論):
引理一、後綴函數不等式:
σ(xa) ≤ σ(x) + 1 (對於任何字符串x,以及字母a)
引理二、後綴函數遞歸引理:
對於任何字符串x,以及字母a,若是q = σ(x),有:
σ(xa) = σ(Pqa)
<等式二> 能夠用數學概括法證實,具體以下:
一、當i = 0,由於T0 = ε,所以有φ(Ti) = 0 = σ(Ti)
二、假設φ(Ti) = σ(Ti),證實φ(Ti+1) = σ(Ti+1),用q 表示φ(Ti),用字母a表示T[i+1],有:
φ(Ti+1) = φ(Tia) (Ti+1 == Tia)
= δ(φ(Ti),a) (根據終態函數的定義)
= δ(q,a) (根據q的定義)
= σ(Pqa) (根據等式一)
= σ(Tia) (根據引理二)
= σ(Ti+1) (Ti+1 == Tia)
從上面能夠知道當讀入T i 的終態(亦即讀入T[i]後轉移函數狀態)等於模式長度,就匹配成功了,下面是有限自動機機匹配算法僞代碼:
下面就是根據<等式一>來實現轉移函數的僞代碼:
下面是我本身實現的code:
#include <iostream> #include <string.h> using namespace std; #define MIN(X,Y) ((X)<=(Y) ? (X) : (Y)) #define MAX_NEEDLE_LEN 0xFF #define MAX_ALPHABET_NUM 0XFF /* check if needle[0~k-1] is suffix of needle[0~q-1 'ch'] */ int is_suffix(const char * needle, int k, int q, unsigned char ch) { int i = 0; int suffix_len = k; if(NULL == needle || suffix_len < 0 || suffix_len > 7) { return -1; } if(needle[suffix_len - 1] != ch) { return -1; } if(1 == suffix_len) { return 0; } for(i=0; (suffix_len > 1)&&(i < suffix_len) && (i < q); i++) { if(needle[suffix_len - 2 - i] != needle[q - 1 - i]) { break; } } if(i >= q) { return 0; } return -1; } unsigned int delta[MAX_NEEDLE_LEN][MAX_ALPHABET_NUM] = {0}; void compute_transition_func(const char *haystack, const char *needle) { int q = 0, k = 0, j = 0; int n = strlen(haystack); int m = strlen(needle); for(q = 0; q < m; q++) { for(j = 0; j < n; j++) { k = MIN(m+1,q+2); do { k--; if(0 == is_suffix(needle, k, q, haystack[j])) { break; } }while(k > 0); delta[q][haystack[j]] = k; } } } void finite_automaton_matcher(const char *haystack, const char *needle) { unsigned int n = strlen(haystack); unsigned int m = strlen(needle); int q = 0, i =0, j = 0; int reCheck_Pos_array[0xff] = {0}; unsigned int reCheck_Index = 0; unsigned need_reCheck = 0; unsigned reOccurence = 0; if(needle[0] == needle[1]) { need_reCheck = 1; } for(i = 0; i< n; i++) { //printf("q=[%d], haystack[%d]=[%c] delta[%d][%c]=[%d]\n", q, i, haystack[i], q, haystack[i], delta[q][haystack[i]]); q = delta[q][haystack[i]]; if(q == m) { printf("\nSuccess find at haystack[%d] !\n", i-m+1); } if(needle[0] == haystack[i]) { reOccurence++; if(reOccurence >=2) reCheck_Pos_array[reCheck_Index++] = i-1; } else { reOccurence = 0; } } if(0 == need_reCheck) return; printf("Need recheck ,and reCheck times [%d]\n", reCheck_Index); for(i = 0; i< reCheck_Index; i++) { q = 0; for(j = 0; j< m; j++) { //printf("q=[%d], haystack[%d]=[%c] delta[%d][%c]=[%d]\n", q, reCheck_Pos_array[i] + j, haystack[reCheck_Pos_array[i] + j], q, haystack[reCheck_Pos_array[i] + j], delta[q][haystack[reCheck_Pos_array[i] + j]]); q = delta[q][ haystack[ reCheck_Pos_array[i] + j ] ]; if(q == m) { printf("Recheck RSuccess find at haystack[%d] !\n", reCheck_Pos_array[i]); } } } } void main() { char needle[] = "aabab"; char haystack[] = "aaababaabaababaab"; //compute delta compute_transition_func((const char *)haystack, (const char *)needle); finite_automaton_matcher((const char *)haystack, (const char *)needle); }
後面在測試的時候,發如今「aaababaabaababaab」裏面查找「aabab」會有遺漏,你們能夠debug看下,我針對這種狀況做了處理,部分要回過頭重複檢查, 會影響效率,沒想到好方法,歡迎你們指點~~
ps : 2014-08-06日,本身回顧下有限自動機,發現部分錯漏,本身從新看了遍算法導論,修正了一遍,以此記錄。經過回顧,本身概括下字符串匹配自動機主要是在某個字符匹配失敗時,利用已知匹配失敗的部分,看是否有部分是模式串P的前綴,以便跳過部分以節省時間。關鍵是後綴函數的理解, 固然後面幾篇模式匹配算法也有用到其餘方法,也有幾種方法結合的,具體要看業務須要。
參考:
一、《Introduction to algorithms》