KMP算法详解

前言

在之前的学习中我们曾讲到过库函数strstr他的作用是判断一个字符串是否是目标字符串的子串,如果是返回第一次查找到子串的地址,如果没有找到返回NULL。参数是两个字符指针,返回值类型是字符指针。在之前的模拟实现中,我们使用了BF算法,也就是暴力算法,我们觉得这样虽然可以解决问题,但是效率不高,我们想要优化我们的算法,所以今天我要向大家介绍一下KMP算法。

BF算法

首先我们来回顾一下BF算法如何模拟实现strstr库函数的,我们来看代码。

char* my_strstr(char* str1, const char* str2)
{
	assert(str1 && str2);

	const char* s1 = str1;
	const char* s2 = str2;
	const char* p = str1;

	if (*s2 == '\0')
	{
		return NULL;
	}

	while (*p)
	{
		s2 = str2;
		s1 = p;

		while ((*s1 == *s2) && *s1 != '\0' && *s2 != '\0')
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return (char*)p;
		}
		p++;
	}

	return NULL;
}

int main()
{
	char arr[20] = "adedefgh";
	char ch[] = "def";

	char* ret = my_strstr(arr, ch);

	if (ret == NULL)
	{
		printf("找不到!!\n");

	}

	else
	{
		printf("%s", ret);
	}

	return 0;
}

我们暴力算法的思路是什么呢?
在这里插入图片描述

我们每次查找时,i向后走,如果找到一样的字符,i++ j++,如果找到不一样的i j回到最开始的位置,然后i++向前走一步。
在这里插入图片描述

i位置与j位置的字符不相同,所以让i返回最开始匹配的位置并且i++j返回起点再次比较
在这里插入图片描述

直到源字符串遍历完,说明源字符串是目标字符串的子串
在这里插入图片描述

KMP算法

BF算法虽然可以解决问题,但是有太多不必要的过程。例如:

在这里插入图片描述
类似这样的源字符串和子串,我们使用BF算法会浪费很多时间,我们让i++向后找寻子串,但是当i = 1时,根本就不可能匹配成功,所以这一步就浪费了时间,再让i++i = 2时,同样不可能匹配成功,又浪费了时间,所以我们需要对我们的算法进行改进。这样我们就引出了KMP算法,我们说BF算法的思想是:i回退到开始匹配的位置并让i++j回退到起点。而KMP算法的核心思想是:i不回退,让j回退到某个特定的位置。例如:
在这里插入图片描述
当我们匹配到i = 5时发现,两个字符串的字符不相等,这时我们让i不动,j回退到一个特定的位置继续匹配。那么j回退到哪一个位置呢?我们来观察观察
在这里插入图片描述

我们看红色区域的字符串跟黄色区域的字符串是否相等,答案是肯定相等的,如果不相等,也不会第一次匹配时匹配到他后面的区域,那黄色区域的字符串跟绿色区域的字符串是不是也是相等的呢。那么我们可以说红色区域内的字符串和绿色区域的的字符串相等,那么我们让j回退到绿色区域的后一个字符继续匹配是不是就可以了呢?所以当jj = 5时匹配失败,我们让i不动,j回退到j = 2位置处继续匹配,我们将j = 2称为k值。我们是不是可以设计一个函数求出所有源字符串对应位置匹配失败时j需要回退到的位置k,之后我们再将所有k值放在一个next数组里面,当我们匹配失败时就可以回退到对应位置了。那么该如何取求呢?我们先手动求一下上面这个源字符串的next数组。
在这里插入图片描述
我们每次匹配失败,只需要让j返回到对应k的位置就可以了,那么我们到底如何求得k的呢?下面我们来说一说规则:

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

例如:j = 5时匹配失败,我们寻找两个匹配成功部分的真子串,一个从0下标开始到1下标结束,一个从3下标开始到j - 1下标结束,长度为2,所以next[5] = 2
我们来两个题训练训练吧:

练习一:a b a b c a b c d a b c d e
练习二:a b c a b c a b c a b c d a b c d e

练习一的next数组为:-1 0 0 1 2 0 1 2 0 0 1 2 0 0
练习二的next数组为:-1 0 0 0 1 2 3 4 5 6 7 8 9 0 1 2 3 0

不知道大家算对了么?如果大家算对了,那么说明大家对于next数组如何求解已经掌握了,他的含义已经理解了。那么我们将进行下一步了,已知next[i] = k如何求next[i+1] = ? 我们举例说明:
在这里插入图片描述

如上图,我们已知next[6] = 3如何推出next[7] = 4呢?
我们假设next[i] = k,那么根据我们的规则是不是有这样的一个公式: p[0] -- p[k-1] == p[x] -- p[i -1]这是因为我们在匹配成功的部分,找到了两个相等的真子串。
既然是真子串那么他们的长度也一定相等,所以k-1 - 0 == i - 1 - x我们就可推出x = i - k
带入到之前的式子中就是p[0] -- p[k-1] == p[i - k] -- p[i -1]
如果p[k] == p[i]
我们是不是可以得出p[0] -- p[k] == p[i-k] -- p[i]
因为p[0] -- p[k-1] == p[i - k] -- p[i -1]可以推出next[i] = k
那么p[0] -- p[k] == p[i-k] -- p[i]是不是可以推出next[i+1] = k+1
所以当next[6] = 3时,并且p[6] = p[3],我们推出了next[7] = 4

以上就是我们的推理思路,那么如果p[i] != p[k]呢?该怎么办呢?我们在举一个例子:
在这里插入图片描述
这次我们的p[i] != p[k]我们该如何处理呢?我们需要继续回退直到p[i] == p[k],比如现在k = 2的位置,而 p[i] != p[k],所以我们继续回退,k = 2时对应的回退位置为0所以我们将k回退到0位置,此时p[i] == p[k],所以next[i+1] = k+1 此时i == 5 k == 0所以next[6] = 1

代码实现

在上面我们与大家讲解了有关KMP算法的思路,下面我们使用C语言来实现一下KMP算法

//KMP算法
#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
void get_next(char* sub, int* next,int len_sub)
{
	next[0] = -1;
	next[1] = 0;

	int i = 2;//当前的i
	int k = 0;//前一项的k

	while(i<len_sub)
	{
		if (k == -1||sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}

}



int KMP(char* str, char* sub, int pos)//str主串 sub子串 pos代表从主串的pos位置开始找
{
	assert(str != NULL && sub != NULL);

	int len_str = strlen(str);//主串长度
	int len_sub = strlen(sub);//子串长度
	if (len_str == 0 || len_sub == 0)
	{
		return -1;
	}
	if (pos < 0 || pos >= len_str)
	{
		return -1;
	}

	int* next = (int*)malloc(sizeof(int) * len_sub);
	assert(next != NULL);

	get_next(sub, next,len_sub);


	int i = pos;//遍历主串
	int j = 0;//遍历子串

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

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

KMP算法的优化

在这里插入图片描述
我们来看这个字符串,如果我们在5位置匹配失败,那么我们回退到4位置也一定是失败的,最后一步一步都要回退到-1位置,那么我们可不可以对他进行优化呢?让他一步到位,直接回退到-1位置,这样我们就引出了nextval[]
在这里插入图片描述
nextval[]计算规则如下

1.如果回退到的位置和当前字符一样,就写回退那个位置的nextval值。
2.如果回退到的位置和当前字符不一样,就写当前字符原来的next值。

例如:2位置匹配失败,回退到1位置,而2位置是字符'a',1位置也是字符'a',所以2位置的nextval等于1位置的nextval。7位置匹配失败,回退到6位置,而两个位置的字符不一样,所以7位置的nextval为他自己的next的值。
好了以上就是全部的KMP算法详解,大家可以理解理解,自己尝试着写一写代码,感谢大家的阅读。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

悲伤猪小猪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值