KMP算法探讨

KMP是关于字符串匹配的问题,具体描述是在一个主串中寻找有没有sub(子)串,要求时间复杂度尽可能的低

对于字符串匹配我们有最暴力的解法——朴素解法,朴素解法的思想是两个指针,一个指向主串每次操作的位置,另一个指向sub串的操作位置,进行逐个匹配操作,每次匹配失败主串下标只挪动一位,sub串下标指针则会置为0,这种操作很费时费力,时间复杂度达到了主串sub串,假设主串长n,子串长m,则朴素查找的时间复杂度为nm
而我们的KMP算法可以达到n+m的复杂度,优化不少。

朴素算法的代码例子
int BF(const char* str,const char* sub,int pos)
{
	int i = pos;
	int j = 0;
	int StrLen = strlen(str);
	int SubLen = strlen(sub);
	while(i < StrLen && j < SubLen)
	{
		if(str[i] == sub[j])
		{
			i++;j++;
		}
		else
		{
			i = i -j +1;
			j = 0;//回到0
		}
	}
	if(j >= SubLen)return i-j;
	else
		return -1;
}

KMP是在其上进行优化的产物

分析KMP的优化步骤

①朴素查找在两个指针上没有做到优化,很容易出现多次操作同一个字符的问题
假设主串叫str,指向str的指针被称为i,子串叫sub,指向sub的指针被称为j
i在一次遍历结束之后是指向了开始遍历之前的下一个位置,j在一次遍历结束后是指向了起始位置,相当于每次都重置了他的位置
经过观察字符串匹配的特性,我们可以做到i不往后回退,下次遍历开始的位置就在上次遍历结束的位置
为什么可以做到i点不后退?
我们实践得知,每次出现失配时,失配线左边的都是匹配成功的,而右边则是匹配失败的,如果i点回到上次的下一个点,j回到0,这样会造成重复查找相同的数据,如果我们预见了失配点的数据之前的数据是匹配的,那我们可以从sub串中找到从失配线开始往右看的数据是否和sub串从0开始的数据相同,有相同的则表示可以跳过,我们移动的k位置就是可以跳过的数据
总结:
可以看做是在已适配字符串中找到连个最大子串,这个最大子串是有限定的

  1. 一个子串必须从sub串的0位开始
  2. 另一个子串必须以失配线作为结束,
  3. 两个子串可以重叠,但是不可以是字符串本身

②下来我们看sub串
j后退到某个位置可以有计算得出,不必每次都回到0位置,这个后退的位置确定就是KMP的精髓所在,也就是next数组
next串的计算方式,我先用伪代码的方式展示
针对next串可以使用额外的函数先行生成
for循环,假设在每一位都会出现失配,根据数组特性可知,0,1位固定位-1,0(第一位没法回退,第二位只能回到0)
注意,我们说的失配线指的是当前数字之前,假设1 2 3 3位出现了失配,那么我们进行比较的是1 2 这两个,和3没有关系,
声明j和k,j是sub操作的下标,k是下次遍历时开始的位置
初始值k = 0,j = 1,next数组的0 1位也默认为 -1, 0寻找在sub串中,sub[j] == sub[k] || k == 0的情况,符合这种条件时next[++j] = ++k;
当sub[j] == sub[k]的时候,说明按照我们的约束所遍历的字符相同,此时给next数组进行自加操作,同时如果下一次操作中匹配仍然成功,那么数值则是递增的,也就是k值在累加。如果k在一个很高的情况下,下次为匹配成功,也就是sub[k] != sub[j],这时我们需要将k改为0,使用 k = next[k];next以k为下标的数据是和sub失配时相同的字符,当然,如果k == 0时也是需要next[++j] = ++k操作,相当于上次遍历中没有我们约束的最大子串,需要重新开始查找,这在next数组中体现为next[k] = 0;
给出关键代码:

int len = strlen(sub);
	int *next = new int[len*sizeof(int)];
	int *nextval = new int[len*sizeof(int)];
	next[0] = -1;
	next[1] = 0;

	int j = 1;//sub的下标
	int k = 0;//每次希望j回退的位置
while(j + 1 < len)
	{
		if(k == 0 || sub[k] == sub[j])
		{
			next[++j] = ++k;
		}
		else
			k = next[k];
	}

③再说说next数组的优化,给出几个next&nextval数组的例子
在这里插入图片描述
以第二行第一个为例,描述nextval数组的生成过程:
我们已经通过前面的方法获取到了next数组,接下来通过sub数组和next数组生成nextval数组。next[0]位的-1无法跳到更前面的位置,所以设置为x,next[3]处失配会回到next[0]位,可以发现此时0位也是字符a,此次替换无意义,可以直接将下标直接移动next[0]的位置,也就是-1,next[4]的值为1,可见k值回到next[1]时,sub[4] 和sub[1]是相等的,无意义的替换,这时直接将nextval[4]的值设置为next[1]的值,同理,当next[7]回到next[2]时,sub[7] == sub[2],此替换也是无意义的,可以直接将nextval[7] = next[1],少做一次替换。
给出关键代码:

nextval[0] = -1;
	for(int i = 1;i <len;++i)
	{
		if(sub[i] == sub[next[i]])//当j下标回到指定位置时发现指向的数据和i指向数据相同
			//那么可以忽略此次移动,直接将此时的j下标改为nextval[next[i]]所在的位置,这个一般是固定值
				//x,也就是nextval[0]的值-1;
					//表示在这里你需要直接将j移动到开始的位置
			nextval[i] = nextval[next[i]];
		else
			nextval[i] = next[i];//当子串的next[i]下标的数值不等于当前数值时
		//可以直接将下标拷贝一份,这个就是k值
	}

这样我们写出KMP算法就很简单了

int KMP(const char* str,const char* sub,int pos)
{
	int i = pos;
	int j = 0;
	int StrLen = strlen(str);
	int SubLen = strlen(sub);
	int* next = GetNextVal(sub);

	while(i < StrLen && j < SubLen)
	{
		if(j == -1 || str[i] == sub[j])
		{
			i++;j++;
		}
		else
		{
			j = next[j];
		}
		delete []next;
		if(j >= SubLen)return i - j;
		else
			return -1;
	}
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值