KMP算法


在这里插入图片描述

1.什么是KMP算法?

  • KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt三位大佬提出,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个数组实现,数组包含了模式串的局部匹配信息。
  • 与BF算法不同的是,当模式串与目标串匹配失败后,目标串并不是一定从头再来,模式串也不一定从起始位置的下一个位置再匹配

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

所以,j 具体再从什么地方开始匹配,取决于我们目标串的next[],那么该数组怎么求呢?

2.如何求next数组?

kmp算法的精髓就在于next数组,从而达到跳跃式匹配的高效模式。
next数组中的值是代表着字符串的前缀与后缀相同的最大长度(不能包括自身)。

  • 求next[]数组的规则如下:
    ①找以0下标位置字符开始,以j-1下标位置字符结尾的两个串
    ②找的两个串对应位置相等

下面我们以字符串"ababcabcdabcde"为例,求它的next数组:

  1. j从0下标开始,很显然 j-1 没有元素,所以我们把它设置为-1,j++向后移

在这里插入图片描述
2. j来到1下标,j-1=0,那么就是以a开头,以a结尾的字符串,但是不能是同一个串,很显然没有,所以next[1]=0, j++向后移

在这里插入图片描述
3. j来到2下标,j-1=1,那么就是以a开头,以b结尾的字符串(不能是同一个串)很显然是没有的,那么next[2]=0, j++向后移

在这里插入图片描述
4. j来到3下标,j-1=2,那么就是以a开始,以a结尾的串,串的长度为1,所以next[3]=1,然后j++向后移。

在这里插入图片描述
5. j来到4下标,j-1=3,那么就是以a开始,以b结尾的串,串的长度为2,所以next[4]=2,然后j++向后移。

在这里插入图片描述
6. j来到5下标,j-1=4,那么就是以a开始,以c结尾的串,很显然没有,所以next[5]=0,然后j++向后移。

在这里插入图片描述
7. j来到6下标,j-1=5,那么就是以a开始,以a结尾的串,串的长度为1,所以next[6]=1,然后j++向后移。
在这里插入图片描述
8. j来到7下标,j-1=6,那么就是以a开始,以b结尾的串,串的长度为2,所以next[7]=2,然后j++向后移
在这里插入图片描述
9. j来到8下标,j-1=7,那么就是以a开始,以c结尾的串,很显然没有,所以next[8]=0,然后j++向后移

在这里插入图片描述
10. j来到9下标,j-1=8,那么就是以a开始,以d结尾的串,很显然没有,所以next[9]=0,然后j++向后移
在这里插入图片描述
11. j来到10下标,j-1=9,那么就是以a开始,以a结尾的串,串的长度为1,所以next[10]=1,然后j++向后移
在这里插入图片描述
12. j来到11下标,j-1=10,那么就是以a开始,以b结尾的串,串的长度为2,所以next[11]=2,然后j++向后移
在这里插入图片描述
13. j来到12下标,j-1=11,那么就是以a开始,以c结尾的串,很显然没有,所以next[12]=0,然后j++向后移
在这里插入图片描述
14. j来到13下标,j-1=12,那么就是以a开始,以d结尾的串,很显然没有,所以next[13]=0,然后j++向后移
在这里插入图片描述
在求得过程中,我们可以发现,next是逐步增加的,不可能跳着增加。
到此,我们的next数组就求完了。我们来验证一下
红色×位置,i与j不相等,j继续回退
绿色×位置,i与j不相等,j继续回退
蓝色×位置,i与j不相等,j继续回退,此时j为-1,已经再无法回退了,因此下一次j因该从j+1位置,也就是字符串的头重新开始匹配
在这里插入图片描述

  • 接下来的问题就是,在最开始时,我们已经知道已知next[0]=-1;next[1] = 0,怎么求next[2]?我们通过肉眼可以很清楚的看出,但是如何让机器知道呢?
  • 如果我们能通过next[i]的值,经过一系列的转换得到next[i+1]的值,那我们就能实现这部分了。

在这里插入图片描述

在这里插入图片描述

3.代码实现

在这里插入图片描述

  • 与上面所讲的不同的是:上面我们是知道了next[j]求next[j+1]。
  • 现在我们求的是next[j],所以应该用next[j-1]与next[k]比较
  • 其中k代表的是:前一项的k
  • 前缀与后缀的求法就是:
    • 两串从指定位置开始,若对应两字符相等,则都++,然后仅比较最后一个字符
    • 若最后一个字符不相等,k回退置某一个位置,再比较
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>

void GetNext(int* next, char* t)
{
	next[0] = -1;
	next[1] = 0;
	int len = strlen(t);
	int k = 0;//前一项的k
	int j = 2;
	while (j < len)
	{
		if (k == -1 || t[j - 1] == t[k])
		{
			next[j] = k + 1;
			j++;
			k++;
		}
		else
		{
			k = next[k];//不相等,k回退
		}
	}
}

int KMP(char* s, char* t)
{
	assert(s && t);
	int lent = strlen(t);
	int lens = strlen(s);
	int* next = (int*)malloc(sizeof(int) * lent);//为目标串的next数组开辟空间
	assert(next != NULL);
	GetNext(next,t);
	int i = 0;
	int j = 0;//目标串
	while (i < lens && j < lent)
	{
		if (j == -1 ||s[i] == t[j]) //两串相等,后移
		{
			i++;
			j++;
		}
		else
		{
			j = next[j]; //两串不相等,目标串回到next数组中的指定位置
		}
	}
	if (j >= lent) //目标串以遍历完,找到了
	{
		return i - j;
	}
	return -1;
}

4.next数组的优化

为什么要优化next数组呢?

  • 如下图,假如模式串在5下标与目标串匹配失败了,那它是不是要回退到4下标,然而4下标也和目标串不一样,还得回退,3…2…1…0…-1,那这么一步步回退是不是很麻烦呢?能不能一步回退到位呢?所以我们就引入nextval数组来改进它。

在这里插入图片描述

  • nextval数组怎么求呢?
    ①回退到的位置和当前位置字符一样,那就写回退的那个位置的nextval的值
    在这里插入图片描述

②如果回退到的位置和当前字符不一样,就写当前字符的next值(也就是k的值)
在这里插入图片描述

void GetNextVal(char* t,int* nextval)
{
	nextval[0] = -1;
	nextval[1] = 0;
	int lent = strlen(t);
	int k = 0;
	int j = 2;
	while (j < lent)
	{
		if (k == -1 || t[j - 1] == t[k])
		{
			if (t[j] == t[k])
			{
				nextval[j] = nextval[k];//当前字符与要回退位置的字符相等,则写回退位置的nextval
			}
			else
			{
				nextval[j] = k;//当前字符与要回退位置的字符不相等,则写自己的next
			}
			//两字符相等,后移
			j++;
			k++;
		}
		else
		{
			k = nextval[k];
		}
	}
}
  • 59
    点赞
  • 66
    收藏
    觉得还不错? 一键收藏
  • 52
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值