2019.8.6 拓展kmp算法

首先感谢2014年一位博主写的博客

kmp

写的很详细

不过有一点小小的错误

我在评论区改正了

我就在此基础上用图来表示

我把大家视为啥都不懂的小白

现在我们有一个字符串A

我们有一天突然想要知道对于A的每一位开始

最多能和多长的A的前缀完全相等?

比如我们可爱的ywwyww字符串
就是这样的一个数组:6 0 0 3 0 0

第0位开始和原来的字符串完全相同,所以是6
第1位开始就和y不同
第2位开始就和y不同
第3位开始和原来的前缀yww相同,所以是3
……

现在搞懂了我们想要搞懂的问题

我们该怎么在线性时间里把这个数组求出来呢?

首先我们探求一下这个数组的性质

假设这个数组名字叫做next(编码的时候不要用next命名,因为next在stl里有定义)

另t=next[i]

那么next[i]的意义就是说A[0]到A[t-1] 和 A[i]到A[i+t-1] 的字符完全相同
比如上面的栗子就是i等于3时next[3]=3。代表A[0]到A[2] 和 A[3]到A[5]的字符完全相同。

我们完成了第一步

第二步我们开始探求递推关系

首先next[0]肯定是A数组的长度啦

next[1]可以通过下方代码求出

int a=0,Tlen=strlen(A),next[0]=Tlen;
while(a<Tlen-1&&T[a]==T[a+1]) a++;
next[1]=a;

下面就是我们的假设环节

假设我们已经知道了next[0]到next[k-1]的数值

0到k-1呢存在一个数a

使得0<=a<=k-1条件下a+next[a]-1最大

稍微想一下就知道max(a+next[a]-1)就是我们的前缀能够匹配的最大距离

我们令p=max(a+next[a]-1)

好的,现在我们想求next[k]

咋办呢?

我们画一个数轴,把所有已知量给写出来

在这里插入图片描述

纵方向相对的就是相同的字符

理解这个后

我们引入L=next[k-a]这个量

可能你现在觉得这个什么鬼量啊,等会你就清楚了

我们简单分析一下

next[k-a]代表什么呢?

代表A[0]到A[L-1] 和 A[k-a]到A[k-a+L-1]的所有字符相同~

现在我们想一想k-a+L-1在数轴里什么位置呢?

我们重画一下

第一种情况:k-a+L-1<=p-a,就是 k+L-1<=p

在这里插入图片描述

数轴变成了这样

又由我们刚才得到的next[k-a]的作用:A[0]到A[L-1] 和 A[k-a]到A[k-a+L-1]的所有字符相同~

而通过数轴我们看出A[k]到A[k+L-1] 和 A[k-a]到A[k-a+L-1] 的所有字符相同!

所以得出结论:A[0]到A[L-1] 和 A[k]到A[k+L-1] 的所有字符相同~

所以我们的next[k]的范围肯定>=L,对吧

现在想想next[k]有可能大于L吗??

思考一下如果next[k]>L,那么A[k+L]就要和A[k-a+L]相同

那么A[L]就要和A[k-a+L]相同!!!

那么我们的next[k-a]就要变大了

这显然不可能

所以我们的next[k]就定调了

当k+L-1<=p时,next[k]=L。

如果看懂了前面的

我们进入第二种情况:k-a+L-1>p-a,就是 k+L-1>p

此时数轴是这样的

在这里插入图片描述

那么我们同样有这个结论:A[0]到A[L-1] 和 A[k]到A[k+L-1] 的所有字符相同~
L含义同上

此刻我们分析一下由于k+L-1>p,所以p-k<=L-1

诶?此刻我们再分析一下

由于p-k<=L-1
A[0]到A[p-k]是不是和 A[k]到A[p]相同了?

是不是很惊喜?

我们的next[k]的范围不就是>=p-k+1了嘛

此时想想

能不能大于p-k+1呢?

答案是可以的

因为从A[p+1]开始我们就没有再匹配过了

也就无从知晓是否匹配

于是我们开始最笨的方法

一个一个匹配

最终一个不匹配或者超出了Tlen范围

那么next[k]就出来了

在这个过程中,a,p,L是一直在维护的

这个真的搞了我一天。。。

附上代码

void pre_kmp(char s[]){
	memset(nextv,0,sizeof(nextv));
	int len=strlen(s);
	nextv[0]=len;
	int j=0;
	while(j<len&&s[j]==s[j+1])j++;
	nextv[1]=j;
	int a=1;
	for(int k=2;k<len;k++){//要从2开始 
		int L=nextv[k-a],p=a+nextv[a]-1;
		if(k+L-1<p){//注意等于的时候就要重新匹配 
			nextv[k]=L;
		}else {
			j=max(0,p-k+1);
			while(k+j<len&&s[k+j]==s[j])j++;//是k+j<len 
			nextv[k]=j;
			a=k;
		}
	}
}

那么我们有两个字符串A和B的话,对于A的每一位开始

最多能和多长的B的前缀完全相等?

我们是不是只需要预处理B的next数组,就可以求出这个问题了?

void GetExtend(char *S,char *T)
{
    int a=0;
    GetNext(T);
    int Slen=strlen(S);
    int Tlen=strlen(T);
    int MinLen=Slen<Tlen? Slen:Tlen;
    while(a<MinLen&&S[a]==T[a]) a++;
    extend[0]=a;
    a=0;
    for(int k=1;k<Slen;k++)
    {
        int p=a+extend[a]-1,L=next[k-a];
        if((k-1)+L>=p)
        {
            int j=(p-k+1)>0? p-k+1:0;
            while(k+j<Slen&&j<Tlen&&S[k+j]==T[j]) j++;
            extend[k]=j;
            a=k;
        }
        else extend[k]=L;
    }
}

小结:我看到几乎所有博客都是直接讲extend数组,不去讲next数组,所以我一直犯迷糊,所以我以next数组讲,希望对大家有所帮助

真搞了我一天,jio的好走一波点赞关注?

发布了61 篇原创文章 · 获赞 8 · 访问量 2457

猜你喜欢

转载自blog.csdn.net/weixin_43982216/article/details/98655060