【KMP算法】

KMP算法核心剖析:

关于KMP算法,建议先了解 BF算法

KMP算法是用来解决字符串匹配问题的高级算法,看完这篇文章,你应该能理解KMP算法。

KMP算法和BF算法唯一的区别在于:主串的i 并不会回退,子串的j也不会回退到0位置。

KMP算法的核心在于求出子串的next数组

所谓的next数组,其实存放的就是子串回退的位置的下标。

下面来演示如何求出一个子串的next 数组
假设有一个字符串:“ababcabcdab”, 求该字符串的next数组:

规定:next[0] = -1 , next[1] = 0

下面就来求next[2] ,next[3]…

在这里插入图片描述
如上图:
那么next[2]如何求呢?

来看下面:
规则:

1、规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0字符开始,另一个以j - 1下标字符结尾。
2、不管什么数据next[0] =-1;next[1]= 0;在这里,我们以下标来开始,而说到的第几个第几个是从1开始;

第二点已经说过,那么第一点是什么意思呢?
看下面:
对于p数组:该数组存有上面举例的字符串: ababcabcdab ,
p[2] = a, 求这个位置的next 值,根据规则1,找到匹配成功部分的两个相等的真子串,一个下标0 字符开始, 另一个是j-1 结尾

意思就是一个串从下标0开始,另一个串从下标1结尾,对于p[2] , p[0] = a, p[2-1] = b,没有两个相等的真子串,即长度为0 ,故next[2] = 0。

看不懂没关系,继续看下面:
求next[3]:,p[0] = a,p[3-1] = a,所以该子串是以a开始,另一个串以a结尾,所以只有a 和 a 这两个串匹配,长度为1 ,故next[3] = 1;
在这里插入图片描述

求next[4]:,p[0]= a,p[4-1] = b,该两串以a开始,以b结尾,故有两个串是ab和ab匹配,长度为2,所以next[4] = 2;

在这里插入图片描述

求next[5]:,p[0] = a,p[5-1] = c,该两串一个以a开始,一个以c结尾,找不出相等的两子串,故长度为0,所以next[5] = 0.

求next[6]:,p[0]= a,p[6-1] = a,该两串以a开始,另一个以a结尾,然而串1往后走,走到以a结尾是 aba, 串2往回走,走到以a开始是
abca ,两者不同,故长度只有a 和 a 两个串,长度为1,即next[6] = 1

在这里插入图片描述

求next[7]:,p[0] = a,p[7-1] = b,该串以a开始,以b结尾,有这样的长度的,只有串1 为ab,串2 为ab ,如下图:
在这里插入图片描述

求next[8],p[0] = a,p[8-1] = c ,由于c是新增的,故长度为0,没有相同的两串是以a开始,另一个以c结尾。next[8] = 0

求next[9]: p[0] = a , p[9-1] = d ,由于d也是新增的,没有相同的两串是一个以a开始,一个以d结尾,故长度为0 .
求next[10] ,p[0] = a,p[10-1] = a,一串以a开始,一串以a结尾,故只有这两个串a和a,故长度为1. next[10] = 1.
在这里插入图片描述
求完next数组后, KMP算法核心就结束了。

有一条规律:next[i]往后走的时候,后一个数如果比前一个数大,那一定是大一位 , 如果比前一个数小,可能小1,2,…
重点是后一个数如果比前一个数大,那一定是大一位
由此我们可以得到另一条结论:
在这里插入图片描述

先看上图:假设i 和 k 的位置如上图:(k是如果在i的位置与主串不匹配,那么子串回退到的位置:就是k下标的位置。)
我们求next数组的时候,是肉眼来求的,但是当我们写代码的时候,next数组是一个个往后求的,所以这里,就是已知 i ,求 i+1的next数组,
情况1>>如果p[i] == p[k],那么next[i+1] = k+1(注意:i和k都是下标)
所以next[i+1] = 1+1 = 2。

情况2>> 如果p[i] !=p[k] , 那么k就不是我们要找的, 所以k需要继续回退,k回退到他所在的next[k] 位置,即 k = next[k],next[k] = 0,
所以k继续回退到0位置,此时 p[i] == p[k] ,所以next[i+1] = k+1 = 0+1 = 1.

情况2其实就是继续回退,找到情况1。

上面这两条结论,就是我们要写代码时 要用到的结论,
但是我们正常来求next数组,是用肉眼来求的,不需要这些结论。

总结:

写代码求next数组时用到的两条结论:

情况1>>如果p[i] == p[k],那么next[i+1] = k+1(注意:i和k都是下标)
所以next[i+1] = 1+1 = 2。

情况2>> 如果p[i] !=p[k] , 那么k就不是我们要找的, 所以k需要继续回退,k回退到他所在的next[k] 位置,即 k = next[k]

下面就可以开始写代码了,

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>

void GetNext(const char* sub, int*next,int len_sub)
{
	assert(sub);
	next[0] = -1;
	next[1] = 0;
	int j = 2;//j是下标->下标从0开始
	int k = 0;//k是下标
	while (j < len_sub)//所以这里没有等号
	{
		if (k==-1 || sub[j-1] == sub[k]) // 情况1
		{ // k==-1也就是极端情况,一开始匹配就不相等,既然不相等,长度就为0,next对应的长度就为0
			next[j] = k + 1;
			j++;
			k++;
		}
		else //情况2
		{
			k = next[k];
		}
	}
}

先来趁热打铁,求next数组。
在这里插入图片描述

看不懂代码先不急,看看上图,
我们求的是j 所对应的next数组的值,现在知道了 j-1 了,故从j-1开始比较,(也因为j 是下标,下标从0开始 )

会求next数组,也就成功了%60

int KMP(const char* str, const char* sub , int pos)
{
	assert(str && sub);
	int len_str = strlen(str);
	int len_sub = strlen(sub);
	if (len_str == 0)
	{
		return -1;//主串长度为0,找不到
	}
	if (len_sub == 0)
	{
		return 0;//子串长度为0,返回主串起始位置
	}
	if (pos<0 || pos>=len_sub)
	{
		return -1;//参数不在主串长度的有效范围内
	}
	int* next = (int*)malloc(sizeof(int) * len_sub);
	GetNext(sub,next,len_sub);
	//求子串的next数组

	int i = pos;//记录主串位置
	int j = 0;//记录子串位置

	while (i < len_str && j< len_sub)
	{
		if (j==-1 || str[i] == sub[j])
		{//j==-1是极端情况,即一开始匹配就不相等,
			i++;
			j++;
		}
		else
		{
			j = next[j];//j回退到next[j]的位置
		}
	}
	if (j >= len_sub)
	{
		return i - j;//子串找完了,找到了
	}
	return -1;//退出循环只有两者情况,不是上面那种,就是找完主串都找不到
}


int main()
{
	printf("%d\n", KMP("abbbcdef", "bbc", 0));
	return 0;
}

这是剩下的%40,情况与BF算法类似,你可以尝试着运行,看看结果正确与否。

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

邓富民

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

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

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

打赏作者

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

抵扣说明:

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

余额充值