关于KMP算法中next数组的理解

本文讲解了个人在跟随b站up主 董晓算法 学习KMP算法中碰到的疑惑。原视频链接:http://【F03【模板】KMP 算法】 https://www.bilibili.com/video/BV1Ag411o7US/?share_source=copy_web&vd_source=e8efef5ed935b98788b215fdb8024747

 next数组的构建无疑是KMP算法的难点,本人的疑惑大多都是在试图理解如何构建next数组的过程中产生的。

一、关于j = 0的必要性。

j初始化为0是为了方便 回跳 和 对ne[i]进行赋值(此模板的ne[i]是指在模板串中第1位到第i位的最长相等前后缀)如果j初始化为1(此处指同时修改构造时的逻辑保证数组能正常构造),在回跳过程中需使j = ne[ j - 1 ],在逻辑上不太方便理解(在我看来,正推是比逆推简单的)。

二、关于回跳的思想形成

回跳是为了节省ne[]数组的构建时间,使用KMP算法的目的就是降低时间复杂度,能节约时间的解法无疑更优。能回跳是因为在之前的判断中符合前后缀相等,而回跳的原因是因为j+1位于第i位不符合,所以可以回到可匹配的前缀位置,再对前缀的下一位和第i位进行比较。(这段看不懂的可以看图解)

上图描述的是结束了 i = 8 的循环的状态,在进行i = 9的循环时,我们发现 j + 1与 i 不相同:

难道要让 j = 0,重新计算吗? 显然,重新计算太浪费时间了,让我们来观察一下,我们可以发现上图框起来的两个字母是等效的

再往前推,我们还发现了一个等效的a。既然中间的a不符合 j+1 = i 的要求,我们就可以回跳至之前的a判断。根据递推的思想,我们回跳至的等效位置一定是最靠近的一个等效位置,所以这样判断就可以节约一定的时间。

在解决完next数组的构成后,KMP算法就很简单了。

完整代码如下:欢迎大家在评论区讨论

#include<iostream>
using namespace std;

char model[1000] = { 0 };
char str[1000] = { 0 };
int lenMod, lenStr;
int ne[1000] = { 0 };

void getNext() {//创建next数组
	int i, j;//双指针对模式串进行遍历,j为前缀,i为后缀
	ne[1] = 0;
	for (i = 2, j = 0; i <= lenMod; i++) {
		//j初始化为0是为了方便 回跳 和 对ne[i]进行赋值,此模板的ne[i]是指在模板串中第1位到第i位的最长相等前后缀
		while (j != 0 && model[i] != model[j + 1]) {//不匹配则回跳至上一段最长的相等前后缀,直到可再次进行比较或回到0位
			//能回跳是因为在之前的判断中符合前后缀相等,而回跳的原因是因为j+1位于第i位不符合,所以可以回到可匹配的前缀位置,再对前缀的下一位和第i位进行比较
			j = ne[j];
		}
		if (model[j + 1] == model[i]) {//当i向后移一位时,最长相等前后缀最多+1
			j++;
		}
		ne[i] = j;//找到最长相等前后缀之后便用ne[i]进行记录
	}//完成了next数组的创建
}

int main(void)
{
	int i, j;
	cin >> str + 1 >> model + 1;//保证下标与字符串位置对应
	lenStr = strlen(str + 1);
	lenMod = strlen(model + 1);

	getNext();

	for (i = 1, j = 0; i <= lenStr; i++) {//i扫描主串,j扫描模式串
		while (j != 0 && str[i] != model[j + 1]) {//不匹配则回跳
			j = ne[j];
		}
		if (str[i] == model[j + 1]) {
			j++;//匹配则判断下一位
		}
		if (j == lenMod) {
			cout << i - lenMod + 1 << endl;
			j = ne[j];//防止主串中含多个模式串导致j>lenMod
		}
	}

	return 0;
}

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值