当前位置 : 主页 > 编程语言 > 其它开发 >

"简单说"KMP算法(C语言编写)

来源:互联网 收集:自由互联 发布时间:2022-05-30
帮你简单捋捋KMP算法的精髓 KMP算法 今天自己琢磨了一下KMP算法,总算是有了一点头绪,现在我就给大伙讲解一下KMP算法的精髓。 1.什么叫做KMP算法? KMP算法是一种改进的字符串匹配算
"简单说"KMP算法(C语言编写) 帮你简单捋捋KMP算法的精髓 KMP算法

今天自己琢磨了一下KMP算法,总算是有了一点头绪,现在我就给大伙讲解一下KMP算法的精髓。

1.什么叫做KMP算法?

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。----百度百科
也就是说我们在库函数常用的一个函数strstr()它是时间复杂度比较大的一种求解方法,其实我们可以利用一些有用的信息来减少我们所需要匹配的次数,从而减少我们的时间复杂度。
现在我们要从一个主串里找到一个子串,假定主串叫major_str,i用来遍历主串,major_len用来记作主串的长度。子串叫sub_str,j用来遍历子串,sub_len用来记作子串的长度。往下我将用这些代号来说明我对KMP的剖析。

2. 算法过程

它的过程比较复杂,跟我们所说的BF算法有很大的不同,但是本质就是遍历主串的下标不会往后退,子串遍历我们按照一个特定的方式来确定下次从哪开始遍历,记下这个特定的位置就是我们重点要讲解的next数组。
现在最重要的就是我们要记下“遍历主串的下标不会往后退,子串遍历我们按照一个特定的方式来确定下次从哪开始遍历”这句话!
先来看个例子吧。

图一

图二

图三

3. next数组

它是我们主角,它要做的事就是将子串回到一个特定位置,下标代表了子串字符位置,0就是第一个字符,以次类推。这次匹配失败,下次回到哪匹配,就是数组的值,数组的值就是我刚刚说ab的长度。我说一下它的规则:

  1. 找到匹配成功的两个相等的最大长度真子串(不包含本身),开始是以0下标,结尾是j- 1为下标的真子串.这个真子串的长度就是数组要存的值;
  2. 我们记作next数组为next[]的形式,始终将next[0] = -1,next[1] = 0,next[2]..next[sub_len]就是我们要求得的数据;
如何求next数组值?

next数组值计算练习题(答案在最后)
  1. "abcabcabcdabacbeabca"的next数组为多少?
  2. "aaaaaab"的next数组为多少?
  3. "ababababcdabcdcd"的next数组为多少?
4. next数组为什么是这样算的?

之前给大伙手动求解,但是你要把它放到程序里,你还能用简单的方法把next数组值求出来吗?如果是按我们刚刚讲的,那这求解next数组不也变成了BF算法了,所以我们得另辟蹊径。
这个是个粗略的推导:

总结

判断当前字符与它回退到的next值的字符是否一致,如果一致下一个next值自加一,如果不一致则继续回退,退回到要么一致的情况,要么k == -1的情况(其实就是回到了开始位置);

5. 代码实现(C语言) 1. KMP的过程
//返回值是主串中第一次出现子串的位置,如果没有子串,则返回-1
int KMP(const char* major_str, const char* sub_str, int pos)//pos是指定一个开始位置匹配
{	
	assert(major_str);
	assert(sub_str);
	
	int major_len = strlen(major_str);
	int sub_len = strlen(sub_str);
	if (major_len == 0 || sub_len == 0)
		return -1;
	if (sub_len > major_len)//子串比主串还长,不用比了
		return -1;
	if (pos < 0)//开始比较的位置小于0,错误
		return -1;
	int* next = (int*)malloc(sizeof(int) * sub_len);
	assert(next);
	GetNext(sub_str, next, sub_len);
	//遍历下标
	int m = pos;//遍历主串
	int s = 0;//遍历子串
	while (m < major_len)
	{
		if ((s == -1) || (major_str[m] == sub_str[s]))//s == -1是刚开始就匹配失败
		{
			m++;
			s++;
		}
		else
		{
			s = *(next + s);//回到next记录的位置
		}
		if (s == sub_len)//如果遍历完了子串,说明匹配成功
			return (m - s);//返回主串匹配子串成功第一个的下标
	}
	
	return -1;
}
2. next数组的实现

j跟k的含义参考这张图

void GetNext(const char* sub_str, int* next, int sub_len)
{
	assert(sub_str);
	*next = -1;//初试化
	if (sub_len > 1)//不能越界访问
		*(next + 1) = 0;//初始化
	int j = 2;//第三个元素,也就是当前位置
	int k = 0;//从第二元素开始的下标,之前一个的位置
	while (j < sub_len)
	{
		if ((k == -1)||(sub_str[j - 1] == sub_str[k]))//k == -1的情况是回到起始位置
		{
			next[i] = k + 1;
			k++;
			j++;
		}
		else
		{
			k = *(next + k);//k要回退到sub_str[j - 1] == sub_str[k]的情况
		}
	}
}
总结
  1. next的求解就是一个迭代运算,规则比较奇怪;
  2. KMP算法关键就是遍历主串的下标不会往后退,子串遍历我们按照一个特定的方式来确定下次从哪开始遍历;
求next数组值练习题答案
网友评论