您的位置:首页 > 其它

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

2014-08-28 17:57 190 查看
KMP算法



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



例:a串:abbabcabababc

b串:ababc

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

经典求法:

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

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

比如以a串为abababc,b串为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]个和b从i倒数pre[i]个这么多个字符严格有序匹配.

比如ababxaba,pre数组为{0,0,1,2,0,1,2,3},为什么不是{1,2,3,4,5,6,7,8}呢?哦,我忘了说了,这个前缀还需要满足其为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行语句换个位置,感受一下代码的错误原因,理解就深刻多了。

比如defdekdefxdefdekdefdef,i枚举到最后一个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 1000
using namespace std;
int pre
;
char s
;
int main()
{
	freopen("test.in","r",stdin);
	int n,i,len;
	scanf("%s",s+1);n=1;
	memset(pre,0,sizeof(pre));
	while(s
!='\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;
}
/*
两组利于理解的求自匹配前缀的模式串!
efgdekxefgdefgend
defdekdefxdefdekdefdef
*/
PS:手调有助于深刻理解。

复制去Google翻译翻译结果
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: