KMP算法相关

辣鸡算法这么难理解
每一个字都有他存在的必要,要是略过的话很容易懵逼哦

KMP用途

字符串匹配。
给出一个主串s和另一个串f,问f是否s的子串。

暴力的话最坏复杂度要到O(mn),而用KMP的话通过一个O(m)的预处理就可以O(n)的 处理出结果。
因为只需要处理子串f,所以特别适合于给出一大堆s串,叫你求子串的问题(公共子串问题O( n2m ))

主要思想

它利用之前已经部分匹配这个有效信息,假如已经在下一个位置匹配失败,那就不用移动原先起点再重新匹配,直接让f串后移,令f串中尽量多字符在已匹配部分中某个位置 到 已匹配部分的最后一个位置 完整匹配,然后从上次匹配失败的位置上继续匹配。

匹配部分


恩,首先是有子串f和主串s,黑色部分是已经匹配成功了的部分。
但下一个字符f和s就不匹配了,这时候,暴力的做法是将f重置,从这一次起点的下一个位置再进行匹配。但KMP的做法是,找出原黑色部分的最大相等前后缀,然后将前缀移动到后缀的位置,再继续匹配。如图所示,其中黑色部分是不需要重新匹配的相同部分,直接从黑色的下一个位置开始匹配。

为什么要取最长的前后缀呢?
假如选择一个长度较小的相等前后缀,那显然我们s中起点移动的步数k就会比选择最长相等前后缀的k要大,就会直接略掉最大的这种可能,但没准正确匹配就是从选择最大的开始呢?

CODE

j = 0
i = -1
for (; j < m; j++) {
        while (N[i+1] != M[j] && i >= 0) i = max[i]; //如果不匹配,那就回到最大前后缀中前缀的结尾位置。
        if (N[i+1] == M[j]) i++  //如果匹配就黑色部分长度+1 
        if (i == n-1) {match.push(j-i); i = max[i]}  //继续匹配
    }

最长相等前后缀的计算

定义

max(m)指f的前m个字符中最长相等前后缀

max函数求法


假设我们已经知道了max[i-1]的值,前缀=后缀,现在要在原来的基础上再加第i个位置f[i]。
若f[max[i-1]+1]=f[i],那显然就直接拼上去,max[i]=max[i-1]+1;
那要是不等于呢!?
那合并后的新前缀肯定是从原前缀中再取一个前缀+f[i]
同理,新后缀肯定是从原后缀中取一个后缀再+f[i]。

那我们再对原前后缀做一个最大相等前后缀,然后取原前缀的前缀和原后缀的后缀,显然他们是相同的。

那又为什么要长度最长呢? 与前面同理。

即这样

图打错了,next[i-1]替换为max[i-1].
然后就判断一下新前缀的下一个是否等于f[i],如果不等那么在新前后缀的基础上重复上述分割步骤,直到长度为0。
j:=max[i-1];//即原前缀最后位置
while (j>0)and(f[i]<>f[j+1]) do j:=max[j]{将原前缀变为新前缀};
其实这样就已经包括了

f[max[i-1]+1]=f[i],那显然就直接拼上去,max[i]=max[i-1]+1;

的情况了,所以删去那一步。
好了,现在我们就愉快的得出了max数组的值

CODE

    i = -1  
    for (; j < n; j++) {
        i = next[j-1]
        while (N[i+1] != N[j] && i >= 0) i = next[i]
        if (N[i+1] == N[j]) next[j] = i+1     
        else next[j] = -1                    
    }

有没有感觉和上面匹配部分的代码神似? 其实就是一个自我匹配的过程。

时间复杂度分析

分析求max的那一段,流程为:

for i=2..m
…. 如果当前位置无法与f串下一位置匹配,则将f串位置后移;
…. 如果现在可以匹配了就将f已匹配位置+1
…. 判断是否匹配完毕

显然在整个循环中,已匹配位置最多增加m-1次(因为只有m-1次循环啊笨)
那每一次while将f串位置后移,已匹配位置最少也会减去1 (aaaaa的情况,一次后移一位)
那已匹配位置不就是最多减去m-1次1,也就是while循环最多执行m-1次。
所以这整个for循环的的时间复杂度就是m-1次for循环+m-1次while循环也就是O(m)。
匹配部分也同理,最多O(n),所以整个时间复杂度就是O(n+m)

完整CODE

var
    p,s:ansistring;
    f:array[0..5000] of longint;
    i,j,k:longint;
procedure getf;
var j:longint;
begin
    f[0]:=-1;
    f[1]:=0;j:=0; i:=1;
    for i:=2 to length(p) do
    begin
        while (j>0)and(p[i]<>p[j+1]) do j:=f[j];
        if (p[i]=p[j+1]) then inc(j);
        f[i]:=j;
    end;

end;

begin
    readln(p); readln(s);
    getf();
    j:=0;
    for i:=1 to length(s) do
    begin
        while (p[j+1]<>s[i])and(j>0) do j:=f[j];
        if p[j+1]=s[i] then inc(j);
        if j=length(p) then
        begin
            writeln(i-j+1);
            break;
        end;
    end;

end.

你可能感兴趣的:(KMP算法相关)