KMP算法相关

来源:互联网 发布:java main函数 sleep 编辑:程序博客网 时间:2024/06/04 18:31

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 = 0i = -1for (; 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

ext[i]为主串S与子串T中,S的后缀s[i,n]与T的最长前缀长度。

目的

求出ext[1..n]

需要用到的内容

nxt[i]表示,子串T本身与子串T的一个后缀t[i..n]的最长前缀长度。

思想

与kmp大致相同,先通过自我匹配求出nxt,再去求ext,建议前置manacher。

具体算法

顺序求解每一个ext[i],对于当前的i,我们求一个mx=max(i+ext[i]1),也就是s串中最靠后的匹配成功位置,设这个位置是从x开始匹配达到的。
然后,因为s[x..mx]这一段与t[1..ext[x]]匹配,
那么s[i..mx]这一段也就是t[ix+1..ext[x]]
(读者可以画个图理解一下,这里就不放图了)
那么,我们只需要知道nxt[ix+1],也就是t[1..lent]t[ix+1..lent]的公共前缀长度,
就有ext[i]=nxt[ix+1].
其意义也就是,s[i..mx]t[1..nxt[ix+1]]匹配。

但还有一种情况,也就是nxt[i-x+1]是满的。那么我们无法得知,在S串的mx位置与T串的nxt[i-x+1]位置后,是否能继续匹配。那么我们可以暴力匹配并更新mx,这样保证mx是递增的,所以最终的时间复杂度是O(lens)

对于nxt的求解,与上述匹配方法类似,所以只作简单的流程叙述。
首先求出nxt[1..i-1],现在求解nxt[i],同样记最大匹配位置为mx,达到这个匹配的位置是x
那么i..mx这一段与i-x+1..nxt[x]是相同的。那么同理我们有nxt[i-x+1]=nxt[i]了。不要忘记扩展与更新mx.

3 0
原创粉丝点击