很久就久仰過KMP演算法的大名,也學了不少時間,但是都是一知半解。最近重新拿起CLRS翻翻,發現基本能看懂了,順便把第32章:字串匹配給翻了一下。
樸素的字串匹配就不用多講了……
Rabin-Karp演算法的思想是產生數,把字元對應表為一個數字,然後一個子串就相當於一個整數,如果兩個整數值相等就是match成功了。當然,字元太多會溢出,那麼就用整數模一個素數的值來比較,但是又會存在碰撞。所以當找到一個命中點時,要用樸素的演算法來驗證。
有限狀態自動機有點複雜,看的半懂不懂的,但是總算知道有限自動機是什麼東西了。反正就是不斷接受變換狀態吧,網上還沒有找到詳細的實現代碼。我們這個縣級市的圖書館裡的電腦圖書大多是Window XP入門,Office辦公、Visual C++從入門到精通等很SB的書,但是竟然有這本如此NB的書。不過據說翻譯不好,而且除了對應研究方向的Ph.D和master,我想看的人不多吧。
最後就是KMP演算法了,得益於中國網民的輸入習慣,拼音IME打KMP出來的是看毛片……於是又稱看毛片演算法。
關於KMP,Matrix67處有很經典的教程。不過我最後理解還是根據CLRS,畢竟對著網頁和對著書學習的效果是不一樣的。
個人覺得精髓在於首碼函數next[q] = max{k: k<q && Pk是Pq的首碼}。
在匹配的過程中減少了判斷的冗(這個字讀rong 第三聲,打long找了好久……)餘,根據DD,看到CLRS上說KMP其實就是自動機實現的一種吧。
KMP厲害在時間、空間基本最優,而且兩個函數基本一樣,編程複雜度基本只比樸素的多幾行,而且也很好理解。
POJ3461是入門題,就是求子串出現的數目,裸的KMP。用C打了好久,開始是沒完全理解KMP,後來又是==達成=,s打成t之類的低級錯誤,還有就是C的數組下標從0開始,要改不少地方。
C代碼:
#include <stdio.h>#include <string.h>#define MAXN 19930317char s[MAXN],t[MAXN];int next[MAXN];int num,i,j,k,ans,lens,lent;void calc_next(){ j = -1; next[0] = -1; for (i = 1; i < lent; i++) { while ((j != -1) && (t[j+1] != t[i])) j = next[j]; if (t[j+1] == t[i]) j++; next[i] = j; }}void KMP(){ j = -1; for (i = 0; i < lens; i++) { while ((j != -1) && (t[j+1] != s[i])) j = next[j]; if (s[i] == t[j+1]) j++; if (j == lent - 1) { ans++; j = next[j]; } }}int main(){ scanf("%d", &num); while (num--) { scanf("%s%s", t, s); lent = strlen(t); lens = strlen(s); calc_next(); ans = 0; KMP(); printf("%d\n", ans); } return 0;}
覺得還是PASCAL的好點,因為還不習慣C的數組從0開始的方式:
var s,t:ansistring; i,j,k,lens,lent,ans:longint; num:longint; next:array[0..19930317] of longint;procedure calc_next;begin j:=0; next[1]:=0; for i:=2 to lent do begin while ((j>0)and(t[j+1]<>t[i])) do j:=next[j]; if t[j+1]=t[i] then j:=j+1; next[i]:=j; end;end;function kmp:longint;begin j:=0; kmp:=0; for i:=1 to lens do begin while (j>0)and(t[j+1]<>s[i]) do j:=next[j]; if t[j+1]=s[i] then j:=j+1; if j=lent then begin j:=next[j]; inc(kmp); end; end;end;begin readln(num); for k:=1 to num do begin readln(t); readln(s); lent:=length(t); lens:=length(s); calc_next; writeln(kmp); end;end.