串匹配問題是解決許多應用(文字編輯器,資料庫檢索,c++模板匹配,模式識別等等)的重要技術。
這個問題有兩個輸入,第乙個是文字(text),第二個是模式(pattern),目的是要在文字中尋找模式。通常而言文字要遠大於模式。
t : now is the time for all goodpeopleto come (長度為n)
p :people(長度為m)
串匹配問題可分為四種型別:
顯然,解決location是最重要的,如果監測到了,就表明出現了(detection),出現多少次,只要將未比較的字串根據同樣的方法求得下一次首次出現的位置,直到整個文字結束,出現在**只要記錄位置做標記即可。
下面開始介紹串匹配演算法。
思想是自左而右,以字元為單位,依次移動模式串,直到某個位置發生匹配。
這個演算法最好的情況是第一次就比對成功,最好情況的上邊界則是每次比對時,第乙個字元都不匹配,這樣就移動一格,最好情況的複雜度就等於\(\omega(n)\), n為文字的長度。最壞的情況是每次比較模式最後乙個字元的時候才發現不匹配,這樣就會導致最壞情況,時間複雜度為\(\mathcal(n \cdot m)\).
c++實現版本1:
int match(string p, string t) // 若匹配,則轉到下一對字元
else // 否則,t回退,p復位
return i - j;
}
c++實現版本2:
int match(string p, string t)
return i;
}
兩個實現版本的返回值都是位置資訊,當i等於n - m + 1的時候說明未找到模式,否則就是找到了。
暴力匹配演算法存在著冗餘的問題,當最壞情況時,最後乙個字元匹配失敗,模式串和文字串的指標都要發生回退。
kmp演算法的原理是利用pattern構建乙個查詢表,根據查詢表進行來指導移動位數,並且文字的索引不需要回退。理解這種演算法我推薦阮一峰老師的kmp部落格(真心推薦看看),講得非常清晰,非常直觀。
假設你看過阮老師的部落格知道原理了,現在來看next表的構建**:
vectorbuildnext(string p)
else
t = n[t]; //失配,根據前面得到的next,來看應該從那裡開始比較,比如下面的匹配等於4的時候,e不等於c,查表知e所在的位置為0,也就是沒有相同的前字尾,所以從0開始繼續匹配,如果大於0,說明有共同前字尾,此時應該不從0開始,因為有共同前字尾,可以避開節省時間。
return n;
}
這裡需要注意的一點是,阮一峰老師的部落格中當前next表是代表當前j的公共最大前字尾的長度,而這個實現中當前next表是代表j-1的公共最大前字尾的長度。
關於t = n[t]可以見下圖,當x不匹配y的時候,此時我們根據next表,由當前next表的值知,p[0, t)和p[j - t, j)是相同的,此時應該移動j-t,也就是從第t位開始比較,也就是n(t)的長度。有一種特殊情況需要考慮,當n(t)等於0時,此時從0開始比較,如果第0位也不等於當前j,根據性質,t此時就等於-1了,此時就進入0>t的條件,自增j,自增t,當前j沒有共同前字尾。這裡開始設n[0]等於-1以及t等於-1,有兩層作用,第一層是為了首輪比較時,需要隔開一位比較。第二層作用是為了防止後面與第一位不相等時,可以根據-1這個條件進入if條件,防止卡死。很是巧妙。
下面有乙個事例:
有了next表的構造方法,接下來就是根據next表進行匹配了。匹配**如下:
int match(string p, string t)
else j = next[j];
return i - j;
}
理解了next表的構造原理,其實就理解了匹配過程,next構造過程就是模式串的自我匹配。當失配時,如果next表的值大於0,說明有公共的前字尾,那麼就不需要從0開始比較,直接從公共前字尾的後乙個字元與當前文字的第j個字元開始比較。
考慮下面這個情況,明知t[4]不等於p[4]且p[1] = p[2] = p[3] = p[4],還要比對剩餘的p[1], p[2], p[3], 這是沒有必要的,這是需要改進next表。
改進只需要把next中的n[j] = t
換成n[j] = ( p[++j] != p[++t] ? t : n[t] )
即可。如下所示:
因為相同,所以可以直接跳過他們,更快。
kmp演算法的時間複雜度是\(o(m + n)\), 空間複雜度是\(o(m+n)\). 匹配過程令k = 2i- j,k每次迴圈至少加1,判斷為真則i加1,判斷為假,j至少減1,所以k <= 2n - 1; 同理next過程也是如此。
kmp小結:
對於bm演算法的介紹,我同樣推薦看阮一峰老師的bm部落格(真心推薦看看),講的十分清楚。同樣假設你看過部落格知道原理了,就知道bm演算法有兩個next表,乙個是壞字元(bad character)bc表,另乙個是好字尾(good suffix)gs表,現在來看看如何構造這兩個表。
對於壞字元表,構造起來很簡單,它是記錄模式串中每種字元最後出現的位置,**如下:
vectorbuildbc(string p)
基於bm-dc的演算法最好情況就是\(o(n/m)\), 最壞情況是\(o(m*n)\)。
最好情況:
最壞情況:
相比於bc表,gs表就很不好構造了。首先來看看乙個概念,最大匹配字尾長度表,通過它來構建ss(suffix size)表,然後通過ss表來構造gs表。
最大匹配字尾長度的意思是在p[0,j)的所有綴中,與p的某一字尾匹配最長者。例如下面的p[0, 3) = ice, 與末尾的ice最長匹配,則p[0, 3)的末尾就為最長匹配長度3,rice同理。(ss表的值就等於最大匹配長度)
ss表末尾的值就是整個模式串的長度,簡單的想法是遍歷每乙個字元向後遞減,與字尾開始一一比較(暴力搜尋),這樣做的複雜度為\(o(m^2)\), 很好的做法是下面的**(從後往前遍歷),時間複雜度只有\(o(m)\)。
vectorbuildss ( string p )
return ss;
}
知道ss表後,gs錶可有ss表推導出,有兩種情況:
對應的**如下:
vectorbuildgs ( string p )
知道了bc表和gs表,接下來就是匹配過程了,與阮老師的部落格上說的一致,取兩個表的最大值。**如下:
int match ( string p, string t )
return i;
}
基於bm_bc+gs演算法最好情況是\(o(n/m)\),最壞情況由於有了gs表,變為了\(o(m+n)\).
各種模式匹配演算法的時間複雜度如下所示:
資料結構鄧俊輝
阮一峰的kmp演算法部落格
阮一峰的bm演算法部落格
KMP串匹配演算法
串的模式匹配是串處理系統中的最重要操作之一,普通匹配演算法的時間複雜度為o m n 然而,kmp演算法的演算法時間複雜度為o m n 其主要改進是 當出現比較不等時,不需回溯指標,而是利用已經得到的部分匹配的結果將模式向後滑動盡可能遠的距離。kmp演算法的本質是基於有限自動機理論,它簡化了有限自動機...
串匹配 bf kmp bm演算法
問題描述 給定乙個文字,在該文字中查詢並定位任意給定字串 給定兩個串 1 bf演算法 蠻力法對主串與模式串乙個乙個進行比較,若不匹配,則模式串從第乙個字元開始,主串往後乙個字元,再進行下一趟比較 若匹配,則模式串與主串字元往後進行比較。直到主串s所剩字元長度小於模式串t長度或模式串所有字元比較完畢。...
串 樸素匹配演算法
c語言極簡版 include include include 返回子串t在主串s中第pos個字元之後的位置。若不存在,則函式返回值為0。intindex char s,char t,int pos else 指標後退重新開始匹配 if j 1 j超出模式串的長度 return i 2 2為模式串的長...