最近看了一些神犇的博客,对kmp有了初步的一点认识,简单地写一下。
kmp是用来在一个字符串A中查找一个子串B的,用传统的方法是从A的第一位开始和B的第一位比较,若相等则比对下一位,B的某一位与对应的A的字符不匹配,则回过头,将A的第二位同B的第一位比较,重复之前的操作,时间效率为O(m*n),我们可以知道此方法效率不高,主要原因是做了许多多余的操作,因为一些位置不需要继续判断,可以直接跳过的,kmp算法就是去掉了多余的操作,本身求解时间效率O(n),预处理时间效率O(m),故总时间效率为O(m+n).
kmp算法与朴素算法不同的是,当发现不匹配时,朴素算法A的指针将回溯到本次匹配第一个字符的位置,然后加一,而kmp算法则会避免这种回溯。
我们通过下面这个例子来了解一下。
A ababbababa
B ababa
在A中查B,从第一位开始比对,直到发现最后一位不匹配,如果是朴素算法,就会将A的指针回溯到第二个字符的位置,B的指针回到1位置继续比较,然而我们会发现此时A[2]=B[2],而B[1]<>B[2],则必定A[2]<>B[1],则个操作就没必要了。而使用kmp时,我们可以将B的指针放在2的位置(绿色部分),此时A[3..4]=B[1..2],指针加一继续比较下一位即可。
我们如何知道kmp算法中怎么指针移动呢,我们用f数组来保存对于B的每一位字符在匹配不成功时所要退回到的位置,换一种说法就是f[i]表示B[1..i]这个字符串中最大前缀与后缀相同的长度,可通过递推快速得出f数组所保存的值。
kmp匹配代码
procedure kmp(a,b:ansistring);
var p,q,lena,lenb:longint;
begin
p:=1; q:=0; ans:=0; lena:=length(a); lenb:=length(b);
while p<=lena do
begin
while (q>0)and(b[q+1]<>a[p]) do q:=f[q];
if b[q+1]=a[p] then q:=q+1;
if q=lenb then
begin
ans:=ans+1; q:=f[q];//后面这个赋值是用于统计A中总共出现B几次的,如果只判断是否出现过则可以删去
end; p:=p+1;
end;
end;
求f数组的预处理代码
procedure work(a,b:ansistring);
var p,q,lena,lenb:longint;
begin
f[1]:=0; p:=2; q:=0; lena:=length(a); lenb:=length(b);
while p<=lenb do
begin
while (q>0)and(b[q+1]<>b[p]) do q:=f[q];
if b[q+1]=b[p] then q:=q+1;
f[p]:=q; p:=p+1;
end;
end;