【POJ3461】KMP算法理解 for 初学者

来源:互联网 发布:linux查看磁盘空间 du 编辑:程序博客网 时间:2024/06/09 18:28

KMP算法

 

用途:求多字符串最长连续公共子串。或言用模式串b在字符串a中进行匹配。

 

例:a串:abbabcabababc

b串:ababc

最长连续公共子串即为a串最末的ababc

经典求法:

枚举a串的每个字符,以其为开始节点,枚举能匹配的最长长度,即比较a[i+j-1]是否等于b[j],相等则j++,不相等则跳出循环,记录f[i]=j-1得到以bi为开始在a串中能匹配的最长长度,但这样的时间复杂度可以粗估计为Olena*lenb);

这种枚举法简单易懂好实现,但是却会有很多冗余的操作。

比如以a串为abababcb串为ababc,以a[1]为开始节点枚举到a[5]时,一看’a’!=’c’,失配!那这个时候如果按照经典算法就需要记录f[1]=4,然后以a[2]为开始节点重新枚举,可是这样有必要么?没有!我们完全可以把当前状态当作以a[3]为开始节点,b串才刚刚匹配到第三个,这样是不是快了很多呢?这就是KMP算法的意义,KMP又是如何将其实现的呢?

 

首先,由本应该由a[2]开始的枚举被缩进到了a[3],甚至我们都不需要从a[3]开始枚举,而是可以直接枚举a[5]b[3]是否匹配,这个缩进过程就是kmp的精髓所在.而它为何可以直接从if(a[5]==b[3])开始呢?

仔细观察,b[1]==b[3],b[2]==b[4]也就是说我们匹配到b[4]==a[4],实际上有a[4]==b[4]==b[2]&&a[3]==b[3]==b[1],这样一旦b[5]!=a[5],我们仍然有b[1]==a[3],b[2]==a[4],使得若b[3]==a[5],那么到a[5],依然可以有以其为末尾的公共连续子串,从另一方面讲,这相当于b串以a[3]为开头,a中寻找公共连续子串,找到了3个的效果,只是快了很多.

但是这一切都是建立在我们知道b[1]==b[3],b[2]==b[4]的基础之上的,而这一步骤又是如何实现的呢?

 

我们可以通过建立一个数组pre,pre[i]表示模式串b在长度为i时的最长前缀长度,而这个前缀长度满足b的前pre[i]个和bi倒数pre[i]个这么多个字符严格有序匹配.

比如ababxabapre数组为{00120123,为什么不是{12345678}呢?哦,我忘了说了,这个前缀还需要满足其为b串在长度为i时的真子串。

那么这个数组是怎么得到的呢?首先我们int一个“fix”表示目前前缀长度,若匹配成功,即b[fix+1]==b[i],则fix++没疑问吧?但是真正的精髓在于它如何处理失配情况:

while(fix&&b[fix+1]!b[i])fix=pre[fix];

而整个处理过程则为

for(fix=0,i=2;b[i]!=’\0’;i++){while(fix&&b[fix+1]!b[i])fix=pre[fix];if(b[fix+1]!b[i])fix++;pre[i]=fix;}


这个东西你手动模拟一下,再试试将while行语句和if行语句换个位置,感受一下代码的错误原因,理解就深刻多了。

比如defdekdefxdefdekdefdefi枚举到最后一个d时,fix=9,此时失配,于是fix=pre[fix]=3,匹配上了,所以pre[i]=3+1=4;即此时能匹配4个,那这是为什么呢?我们分析若这个‘d==该匹配到的‘x’,那么前缀的defdekdefx==后缀的defdekdefd,但是失配了,而原来的最长前缀defdekdef中有最长前缀def==其后缀def==defdekdefxdefdekdef中最后的def,所以此时def依然既是模式串b的前缀,又是其后缀,所以若b[4]==’d’,那么依然是defdekdefxdefdekdefd中的最长前缀。

如此以来,我们就求得了模式串b的前缀数组。

前缀数组模板题为poj2752

Kmp裸题为poj3461

Poj3461题意,在目标串中求模式串出现最大次数,因为fix是目前匹配到的最长长度,所以只需要在fix==模式串长度时ans++即可,代码如下:

#include<cstdio>#include<cstring>#include<algorithm>#define N 1000using namespace std;int pre[N];char s[N];int main(){freopen("test.in","r",stdin);int n,i,len;scanf("%s",s+1);n=1;memset(pre,0,sizeof(pre));while(s[n]!='\0')n++;/*pre[1]=0,len=0;*/for(i=2;i<n;i++){while(len&&s[i]!=s[len+1])len=pre[len];if(s[i]==s[len+1])len++;pre[i]=len;}return 0;}/*两组利于理解的求自匹配前缀的模式串!efgdekxefgdefgenddefdekdefxdefdekdefdef*/
PS:手调有助于深刻理解。

0 0