KMP算法简单分析
定義問(wèn)題
字符串匹配是這樣一個(gè)問(wèn)題: 對(duì)于兩個(gè)包含且僅包含字母表∑中的字母的串P,T,計(jì)算出所有有效的**移進(jìn)**s使得P[1..|P|] = T[s+1..s+|P|]。(|P|為P的長(zhǎng)度)。
或者說(shuō):求出在什么位置P被T完全包含。
為了表達(dá)方便,定義m = |P|, n = |T|。P稱為模式串,T稱為匹配串。
樸素算法
樸素算法是一種顯然的方法。直接給出偽代碼:
Naive-Match (P, T)m = |P|, n = |T|for i = 1..n doif P[1..m] == T thenprint i" " 樸素算法可以看成模式串緊貼匹配串滑動(dòng),嘗試移進(jìn)s = 1..n時(shí)能否匹配。大多數(shù)情況下,樸素算法已經(jīng)可以解決問(wèn)題。但是當(dāng)數(shù)據(jù)極大(例如在很長(zhǎng)的基因串中尋找一組基因)時(shí),樸素算法的效率就顯得差了。因此,科學(xué)家尋找到許多種優(yōu)秀的匹配算法。這是一個(gè)常用算法時(shí)間對(duì)照表。
| 算法 | 預(yù)處理 | 匹配 |
|---|---|---|
| 樸素算法 | 0 | O((n-m+1)m) |
| Rabin-Karp | Θ(m) | O((n-m+1)m) |
| 有限自動(dòng)機(jī) | Θ(m∑) | Θ(n) |
| Knuth-Morris-Pratt | Θ(m) | Θ(n) |
所有的字符串算法都很麻煩(畢竟蒟蒻)。其中KMP用處比較廣。在《算法導(dǎo)論》里KMP的介紹是以有限自動(dòng)機(jī)為基礎(chǔ)的,然而我又看不懂,gedao了半天才大致明白KMP的思想。
KMP算法
Quote:來(lái)自 zrO matrix67 Orz
假如,A=”abababaababacb”,B=”ababacb”,我們來(lái)看看KMP是怎么工作的。我們用兩個(gè)指針i和j分別表示,A[i-j+ 1..i]與B[1..j]完全相等。也就是說(shuō),i是不斷增加的,隨著i的增加j相應(yīng)地變化,且j滿足以A[i]結(jié)尾的長(zhǎng)度為j的字符串正好匹配B串的前 j個(gè)字符(j當(dāng)然越大越好),現(xiàn)在需要檢驗(yàn)A[i+1]和B[j+1]的關(guān)系。
- 當(dāng)A[i+1]=B[j+1]時(shí),i和j各加一;什么時(shí)候j=m了,我們就說(shuō)B是A的子串(B串已經(jīng)整完了),并且可以根據(jù)這時(shí)的i值算出匹配的位置。
- 當(dāng)A[i+1]<>B[j+1],KMP的策略是調(diào)整j的位置(減小j值)使得A[i-j+1..i]與B[1..j]保持匹配且新的B[j+1]恰好與A[i+1]匹配(從而使得i和j能繼續(xù)增加)。我們看一看當(dāng) i=j=5時(shí)的情況。
詳細(xì)內(nèi)容參見(jiàn) http://www.matrix67.com/blog/archives/115
個(gè)人理解
我是自己推導(dǎo)之后才看到上面大牛的解釋,真的非常通俗。所以看不懂的同學(xué)可以去哪里膜拜一下。kmp算法實(shí)在比較惡心,雖然代碼秘制煎蛋,不習(xí)慣推導(dǎo)的童鞋直接背下來(lái)就可以了。:(語(yǔ)言表達(dá)能力捉急):。
ps:這里并沒(méi)有使用圖形輔助理解,個(gè)人認(rèn)為這樣更有利于理解kmp匹配原理。
kmp基于一個(gè)函數(shù)π,π表示有最大的t < i使P[1..t] = P[m-t+1..m],則t = π。或者形式化地:
π[i] = max{t | P[1..t] = P[m-t+1..m] 且 t < i} 證明一個(gè)結(jié)論,對(duì)于任意T[k-i+1..k] = P[1..i],有:
π[i] = max{t | P[1..t] = T[k-t+1..k] 且 t < i}
用反證法,假設(shè)有π[i] < x < i使得P[1..x] = T[k-x+1..k]∵ P[1..i] = T[k-i+1..k]∴ T[k-x+1..k] = P[m-x+1..m]又 P[1..t] = T[k-x+1..k]∴ P[1..x] = P[m-x+1..m]∵ x > π[i], 根據(jù)定義,矛盾
原命題得證。 這個(gè)結(jié)論將說(shuō)明kmp不會(huì)錯(cuò)過(guò)正確解。
以及:
如果有s使T[k-s+1..k] = P[1..s],
那么有T[k-π[s]+1..k] = P[1..π[s]]。
證明很簡(jiǎn)單,根據(jù)定義等量代換即可。 這個(gè)結(jié)論將說(shuō)明kmp不會(huì)找到錯(cuò)誤解。
這些結(jié)論并不足以證明kmp的正確性,但是基本可以看出主要思想了。事實(shí)上,通過(guò)π可以省略許多無(wú)用的比較(基于第二個(gè)結(jié)論)。kmp匹配算法代碼如下:
void kmp_match(int l) {// l是T的長(zhǎng)度,pL是P的長(zhǎng)度int q = 0;// 匹配的長(zhǎng)度for (int i=1; i<=l; i++) {while (q > 0 && P[q+1] != T[i])q = pie[q];// 無(wú)法匹配下一位,找到可以部分匹配的最大部分,或者沒(méi)有可以匹配if (P[q+1] == T[i])q++;// 下一位可以匹配if (q == pL) {// 找到printf("Shift %d >>> ", i-pL);q = pie[q];// 找下一個(gè)匹配位置}}
} 計(jì)算匹配函數(shù)π的方法:
void kmp_init() {int k = 0;pie[1] = pie[0] = 0;// 第一位不可能找到匹配for (int i=2; i<=pL; i++) {while (k > 0 && P[k+1] != P[i])k = pie[k];// 同上,自己匹配自己罷了if (P[k+1] == P[i])k++;pie[i] = k;// 記錄最長(zhǎng)匹配}
} 所謂自己匹配自己,就是π就是找到一對(duì)最大且相等的前綴和后綴,記錄前綴出現(xiàn)位置。(基于定義)
kmp大概就是這樣了,多思考就可以想通。。
kmp時(shí)間復(fù)雜度分析
kmp的復(fù)雜度為Θ(n)-Θ(m),這里用攤還分析中的聚合分析法給出一個(gè)kmp_init復(fù)雜度分析例子。我們?cè)噲D證明while循環(huán)的執(zhí)行次數(shù)為O(n)。
k的初值為0,而k的值增長(zhǎng)有且只有一個(gè)途徑:10行的k++。由于for循環(huán)一次k最多加一,n-1次循環(huán)之后k最多為n-1呢。由于π < i,因此while循環(huán)只會(huì)使k減少,且一次至少減少1。而k < n-1,所以while的循環(huán)次數(shù)為O(n)。不難得出kmp_init的復(fù)雜度為Θ(n)。用這種方法也可以得出kmp_match的復(fù)雜度為Θ(m)。
linux下裝逼代碼
裝逼專用,僅售998,到linux上看看效果吧。
#include <iostream>
#include <cstdio>
#include <cctype>
using namespace std;
char P[10005], T[10005];
int pL;
int pie[10005];
int readfln(char *str) {char c;int i = 0;str[0] = '\"';while (c = getchar()) {if (c!= '\n')str[++i] = c;else break;}return i;
}
void printfln(int shift,int l) {int beg = shift-5;if (shift <= 5)beg = 0;elseprintf("...");for (int i=beg+1; i<=shift; i++)putchar(T[i]);printf("\033[33m");printf("%s", P+1);printf("\033[0m");int end = shift+pL+5;if (shift+pL+5 > l)end = l;for (int i=shift+pL+1; i<=end; i++)putchar(T[i]);if (shift+pL+5 < l)printf("...");printf("\n");
}
void kmp_init() {int k = 0;pie[1] = pie[0] = 0;for (int i=2; i<=pL; i++) {while (k > 0 && P[k+1] != P[i])k = pie[k];if (P[k+1] == P[i])k++;pie[i] = k;}
}
void kmp_match(int l) {int q = 0;for (int i=1; i<=l; i++) {while (q > 0 && P[q+1] != T[i])q = pie[q];if (P[q+1] == T[i])q++;if (q == pL) {printf("Shift %d >>> ", i-pL);printfln(i-pL,l);q = pie[q];}}
}
int main() {pL = readfln(P);kmp_init();int l;while (l = readfln(T))kmp_match(l);return 0;
} 參考資料:《算法導(dǎo)論》
轉(zhuǎn)載于:https://www.cnblogs.com/ljt12138/p/6684390.html
總結(jié)
- 上一篇: 爱上一个大我十岁的女子
- 下一篇: 团队