KMP学习困惑点,自学自闭自问自答

这两天在看KMP算法,也搜了各种解释和博客、视频来看。
发现似乎大家的实现方式都不太一样,而且大多没讲到关键点上。
(我现在还不是很懂啊哈哈哈哈哈哈哈哈哈尬笑)
【经典算法】——KMP,深入讲解next数组的求解

先来看道题

HDOJ Problem-1686
求文本T中单词W的出现次数

AC代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char w[10005],t[1000005];
int nex[10005];
void getnext(char *B,int n)
{
	nex[0]=0;
	for(int i=1,j=0;i<n;i++)/* n为B串长度 */
	{
//		j=nex[i-1];j为待计算位前一位对应的最大公共串的下一位(或前一位对应的最大公共串长度)
		while(B[j]!=B[i]&&j>0)//若匹配不上,尝试缩小公共子串,因为B串i之前的j个字符(B[i-j]~B[i-1])与开头的j个(B[0]~B[j-1])相同,直接在前面j个字符里找
			j=nex[j-1];//找最大公共子串的下一位与B[i]相同的最大公共子串,不能用j--,保证B[0]到B[i-1]这段目前匹配前缀后缀始终相同
		if(B[j]==B[i])//公共串下一位字符相同,公共最大长+1
			j++;
		nex[i]=j;//j为0~i最大公共串长度,若最终B[j]!=B[i],j=0
	}
}
int kmp(int m,int n)//m为A串长度,n为B串长度
{
	int ans=0,cmp=0;
	for(int i=0;i<m;i++){
		while(cmp>0&&t[i]!=w[cmp])//第cmp个位置匹配失败
			cmp=nex[cmp-1];//下一轮从匹配成功串的最大公共串的下一位开始匹配
		if(t[i]==w[cmp])//当前字符匹配成功,继续下一位
			cmp++;
		if(cmp==n){//子串匹配成功
			ans++;
			cmp=nex[cmp-1];//本题母串分割各部分间可以有公共部分
		}
	}
	return ans;
}
int main()
{
	int n;
	cin>>n;
	getchar();
	while(n--){
		gets(w);
		gets(t);
		getnext(w,strlen(w));
		printf("%d\n",kmp(strlen(t),strlen(w)));
	}
	return 0;
}

求next数组的疑点解释

一个难点就是求next数组

void getnext(char *B,int n)
{
	nex[0]=0;
	for(int i=1,j=0;i<n;i++)/* n为B串长度 */
	{
//		j=nex[i-1];j为待计算位前一位对应的最大公共串的下一位(或前一位对应的最大公共串长度)
		while(B[j]!=B[i]&&j>0)//若匹配不上,尝试缩小公共子串,因为B串i之前的j个字符(B[i-j]~B[i-1])与开头的j个(B[0]~B[j-1])相同,直接在前面j个字符里找
			j=nex[j-1];//找最大公共子串的下一位与B[i]相同的最大公共子串,不能用j--,保证B[0]到B[i-1]这段目前匹配前缀后缀始终相同
		if(B[j]==B[i])//公共串下一位字符相同,公共最大长+1
			j++;
		nex[i]=j;//j为0~i最大公共串长度,若最终B[j]!=B[i],j=0
	}
}

next数组存储的数值为截止到该下标的子串的最大公共子串的长度,因为字符串从0开始,该数值也为该子串最大公共子串的前缀部分下一个字符对应的下标。
用递推的方法来求str对应的next数组,求next[i]时,next[0]~next[i-1]都已经求出,令j为next[i-1]即str[i-1]对应的最大公共串的下一位。
注意:i之前的j个字符(str[i-j]~ str[i-1])与开头的j个(str[0]~str[j-1])相同
①如果str[j]==str[i]的话,说明尾部加入的字符使得上一轮最大公共子串向后延了一位,next[i]=j+1(此时j=next[i-1])

②若str[j]!=str[i],说明加入的字符不能接在上一轮最大公共子串后面,尝试缩小公共子串,所以不断获取最大公共子串的最大公共子串,直至最大公共子串前缀串后面的字符与str[i]匹配上或j==0(str[i]这一段没有最大公共子串),那么就退出循环

注意:不能使用j - -

while(B[j]!=B[i]&&j>0)
	j=nex[j-1];//不能换成j--

KMP算法最浅显理解——一看就明白
这篇博客里提到了,但并没有说为什么。
因为使用j - -求出的不保证是str[0]到str[i-1]这一段字符串的公共子串,而是str[0]到str[i-1]这一段字符串的前缀后缀最大公共部分的前XX个字符,新加入的str[i]字符不可以接在这一段后面

其他疑问

KMP的原理正确性?

如何更好的理解和掌握 KMP 算法? - 咸鱼白的回答 - 知乎

这篇回答为什么要求new数组?

KMP的其他优化形式

扩展KMP算法

拓展kmp算法总结
扩展 KMP 算法
这篇配图很清晰,容易理解
辅助数组next[i]表示字串T[i,m-1]和T的最长公共前缀长度
extend[i]表示T与S[i,n-1]的最长公共前缀

板子题

Problem-6629 string matching

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char s[1000005];
int nex[1000005],extend[1000005];
void GetNext(char T[],int m,int nex[])//先计算模式串的next数组
{//之前匹配过程中所达到的最远位置为p(匹配串最后一个字符为T[p-1]),并且以T[a]为起始
    int a=0,p=0;//边界情况,此时不能令p=m
    nex[0]=m;//与自身完全匹配
    //T[i]串和T[i-a]串对齐,来求nex[i]
    for(int i=1;i<m;i++)//计算子串T[i]...T[m-1]的匹配长
	{
        if(i>=p||i+nex[i-a]>=p)//最右匹配串需要更新
		{
			if(i>=p)
				p=i;//i<p时T[i]~T[p-1]与T[0]~T[p-i-1]相同
            while(p<m&&T[p]==T[p-i])
                p++;//更新最右值,T[p]与T[p-i]匹配失败则跳出
            nex[i]=p-i;//最大匹配长T[0]~T[p-i-1]
            a=i;//更新最右匹配串起始位置
        }
        else//在之前的最长匹配串中,所以T[i]==T[i-a],T[i+nex[i-a]]==T[i-a+nex[i-a]],且T[i-a]串在T[nex[i-a]]处截断(相当于T[0+nex[i-a]]!=T[i-a+nex[i-a]])
			nex[i]=nex[i-a];//所以T[i]也在T[nex[i-a]]处截断(T[i+nex[i-a]]!=T[0+nex[i-a]]),即为匹配串长度
    }
}
void GetExtend(char S[],int n,char T[],int m,int extend[],int next[])
{//注意:比较过程中T[0]始终与S[a]对齐
	int a=0,p=0;//a,p为母串S上的标记,p为之前匹配过程中所达到的最远位置
	GetNext(T,m,next);
	for(int i=0;i<n;i++)//计算子串S[i]...S[n-1]与T的匹配长
    {//i一定≥a,所以要求p尽量在最右,而不必特别关注a
        if(i>=p||i+next[i-a]>=p)//a和p都要更新
        {
            if(i>=p)//i>=p的作用:举个典型例子,S和T无一字符相同
                p=i;//i>=p时更新最右值p,i<p时S[i]~S[p-1]与T[0]~T[p-i-1]匹配
            while(p<n&&p-i<m&&S[p]==T[p-i])//
                p++;
            extend[i]=p-i;//S[i]~S[p-1],共p-i个字符匹配
            a=i;//更新最右匹配串起始位置,当i超出了p时,以i开头重新计算p
        }//但当p<i&&i+next[i-a]>=p时,a更不更新不影响
        else//未超出最右匹配串,即S[i]~S[i+nex[i-a]]==T[i-a]~T[i-a+nex[i-a]],T[0+nex[i-a]]!=T[i-a+nex[i-a]]
            extend[i]=next[i-a];//所以S[i+nex[i-a]]!=T[0+nex[i-a]],nex[i-a]即为匹配串长度
    }
}
int main()
{
	int t;
	cin>>t;
	while(t--){
		scanf("%s",s);
		int len=strlen(s);
		long long cnt=0;//果然是卡int...
		memset(nex,0,sizeof(nex));
//		memset(extend,0,sizeof(extend));
		GetNext(s,len,nex);
//		GetExtend(s,len,s,len,extend,nex);
//		{
//			 cout << "next:   ";
//			for (int i = 0; i < len; i++)
//				cout << nex[i] << " ";
//
//			// 打印 extend
//			cout << "\nextend: ";
//			for (int i = 0; i < len; i++)
//				cout << extend[i] << " ";
//
//			cout << endl << endl;
//		}
		for(int i=1;i<=len-1;i++){
			cnt+=nex[i];
			if(i+nex[i]<len)
				cnt++;
		}
//		for(int i=1;i<=len-1;i++){
//			cnt+=extend[i];
//			if(i+extend[i]<len)
//				cnt++;
//		}
//		printf("cnt=%d,cnt2=%d\n",cnt,cnt2);
//		cnt++;//最后一个字符
		cout<<cnt<<endl;
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值