字符串匹配:看毛片算法及其它
很久就久仰过KMP算法的大名,也学了不少时间,但是都是一知半解。最近重新拿起CLRS翻翻,发现基本能看懂了,顺便把第32章:字符串匹配给翻了一下。
朴素的字符串匹配就不用多讲了……
Rabin-Karp算法的思想是产生数,把字符映射为一个数字,然后一个子串就相当于一个整数,如果两个整数值相等就是match成功了。当然,字符太多会溢出,那么就用整数模一个素数的值来比较,但是又会存在碰撞。所以当找到一个命中点时,要用朴素的算法来验证。
有限状态自动机有点复杂,看的半懂不懂的,但是总算知道有限自动机是什么东西了。反正就是不断接受变换状态吧,网上还没有找到详细的实现代码。我们这个县级市的图书馆里的计算机图书大多是Window XP入门,Office办公、Visual C++从入门到精通等很SB的书,但是竟然有这本如此NB的书。不过据说翻译不好,而且除了对应研究方向的Ph.D和master,我想看的人不多吧。
最后就是KMP算法了,得益于中国网民的输入习惯,拼音输入法打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 19930317 char 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.