KMP算法(C语言)——next数组的求解

KMP算法的核心在于next数组的求解,它避免了主串指针的回溯,提高匹配效率。next数组记录了模式串中每个位置的最大相等前后缀长度,初始化时i=0,j=1,next[0]=0。当前后缀相等时更新next数组,不等时i回退到next[i-1]的位置,确保能从正确位置重新匹配。
摘要由CSDN通过智能技术生成

 简介:KMP算法是D.E.Knuth、J,H,Morris 和 V.R.Pratt 发表的一个模式匹配算法,可以大大避免重复遍历的情况(即消除了主串指针回溯的),从而提高算法效率

next数组的求解

        我认为KMP算法最难理解的就是next数组的求解,理解了next数组的求解KMP算法就了解的差不多了。

        在此之前,我们需要了解几个概念:

前缀:包含首字符不包含尾字符的全部子串

后缀:包含尾字符不包含首字符的全部子串
前缀表:它记录了模式串与主串 (文本串)不匹配的时候,模式串应该从哪里开始重新匹配。即所有包含首字符的子串的最大相等前后缀的长度所构成的数组,即next数组(也有人叫做Prefix)

 例如下图,黄色标出的就是一个前缀子串,绿色标出的就是一个后缀子串,黄色或绿色的长度就是 j 指针运动到字符时最大相等前后缀的长度。

下文中  i  表示前缀子串的末尾,同时 i+1 还代表着最大相等前后缀的长度,j 表示后缀子串的末尾

现在可以实现next数组了,next数组的求解需要做以下几件事:

        1.初始化

        2.前后缀相等时,更新next数组

        3.前后缀不相等时, i 回退

void getNext(char* ModeString,int* next)
{
    //初始化
	int i = 0, j = 1;
	next[0] = 0;
	while (j < strlen(ModeString))
	{
        //当前后缀相等时,和当i指向数组头时,就要更新next[j]的值
		if (i == 0 || ModeString[j] == ModeString[i])
		{
			if (ModeString[j] == ModeString[i])
				i++;
			next[j] = i;
			j++;
		}
        //前后缀不相等时,i回退
        //这行代码最难理解,回退的位置是next数组求解的关键
		else i = next[i - 1];
	}
}

1.初始化

        使 i 指向第一个元素,j 指向第二个元素,因为当字符串只有一个元素时,既没有前缀也没有后缀,所以其最大相等前后缀长度为0,因此将next[0]赋值为0

        注:有些next求解的实现会将 i 赋值为-1,j 赋值为0,next[0]赋值为-1,这就是将next数组整个往回移动了一位,与本文的方法大同小异

2.前后缀相同时,更新next数组

    首先我们要理解当ModeString[i]==ModeString[j]时,一定有ModeString[i-1]==ModeString[j-1],ModeString[i-2]==ModeString[j-2]...ModeString[0]==ModeString[j-i],因为当前后缀不相同时,i 会回退,所以 i  走过的位置一定满足ModeString[i]==ModeString[j],因此 i+1 还可以可以代表最大相等前后缀的长度,所以当ModeString[i]==ModeString[j]时就将 i+1 赋值给next[j]即可。

     但还有一种特殊情况,就是当 i 退无可退的时候(即i == 0时),当 i 退无可退时,说明该此时 j 所指向的字符的最大相等前后缀长度为0,此时就不需要在给 i +1 了直接将 赋值给next[j]就可以了,所以在 i++ 前加一个判断语句,当i==0&&ModeString[i]!=ModeString[j],i 不做递增操作。

3.前后缀不相同时,i 回退

        i = next[i-1]第一次看到这行代码一定会很懵,为什么 i 要回退到 next[i-1]的位置,而不是一步一步递减回去呢?

        首先,我们要知道 i 的含义不仅仅代表着前缀子串的末尾,还代表着最大相等前后缀的长度-1,所以 i 的值不能随便的加减,会使前后缀的匹配出现错误,例如下图:

i 不匹配时,如果 i 回到 i-1 的位置

 这时,ModeString[i]==ModeString[j],i == 2,于是将next[j]赋值为 3,最长前后缀变为下图的样子

但很明显aab和abb并不相等,所以回退的位置不能是 i-1。我们应该让 i 回退到一个位置,这个位置可以保证模式串中 0 ~ i-1 的字符和 j-i ~ j-1 的字符相等

i 赋值为next[i-1]就满足这样的条件,下面是证明过程:

前文我们已经知道,当ModeString[i]==ModeString[j]时,一定会有字符0 ~ i(j-i)~ j都相等,并且next[i]位置记录的是,i 位置的后缀子串,所对应的最大相等前缀子串的长度。所以有下图:

因为 i 走过的的位置一定满足ModeString[i]==ModeString[j],所以当ModeString[i]!=ModeString[j]时,0 ~ i-1 j-i ~ j-1的字符相等,即上图标出的前后缀是完全相同的。next[i-1]位置的记录的就是前缀子串的最长相等前后缀的长度,我们假设next[i-1]的值为3(这个值可以是任意非负数),我们很容易得出前缀是下图这样的(红色标出的子串是相等的)

 这样很容易可以推出整个模式串是这样的

 这样 i 要回退的位置就一目了然了,就是下图绿色标出的位置,而这个位置的下标不正好就是前缀子串的最大相等前后缀的长度嘛,也就是next[i-1]位置的值

如果该位置的字符依然不匹配,就继续回退。


以上就是我对KMP算法中求解next数组的理解,有错误的地方,还请不吝赐教。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IdlePerson.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值