经典算法与数据结构(1)— KMP算法

经典算法的第一篇,记录一下KMP算法好了,因为比较经典巧妙而且我考研的时候忘看了没做出来???。KMP算法是三个人一起发明的,三个人的名字首字母分别是K、M、P,所以叫KMP~

问题定义

给定一个字符串S, 以及另一个字符串P, 判断P是否为S的子串,如果是的话输出第一个匹配到的位置。

暴力解法

暴力解法比较容易想到,就是遍历S,如果找到和P的首字母相匹配的元素,就继续逐个比对,发生失配时就继续遍历S,直到找到完全匹配的子串。如果遍历完了还找不到,就返回false。过程如下(本篇笔记里的所有动图都是从另一篇博客搬过来的,博客地址是这个):
在这里插入图片描述

优化空间

暴力解法中可以进一步进行改进的地方在于,当在S中遇到一个与 P[0] 匹配的字符,逐个比对的时候,已经匹配了若干个字符,获得了一些P的信息,一旦发生失配就彻底丢失这些信息,遍历到S的下一个字符开始从头匹配。

暴力解法忽略了一种情况,就是P中已知的信息是可以对下一步的遍历进行指导的,具体来说也就是:

当P中前k个已经匹配成功时,我们可以观察这k个元素内部,有没有首尾相同的元素,比如:如果这K个元素是 {a, b, c, d} 这样的元素(首尾没有重复的元素),那么便没有必要继续在S中 {b, c, d} 这k-1个元素的地方进行匹配了。从反面情况来解释,当我们有必要在这k个元素里以其中某一个(设为ki)为首挨个匹配时,说明从ki到kk(k个元素里的最后一个)跟P的前 k-i+1 个元素都是匹配的,又因为P的前 k-i+1个元素等于K中的k1,…,kk-i+1,所以也就是:ki,…, kk = k1,…,kk-i+1,即首尾重叠。而首尾无重叠的时候,可以预见,没有必要在这k-1个元素里再比对。

动图更加直观,看下面???
在这里插入图片描述

自然可以想到,每次发生失配的时候,计算出此时已匹配的K个元素里的最大首尾重叠元素数d,将模式串P右移K-d个位置再次从上次失配的地方开始匹配就行。

在KMP算法里,用了一个next数组来实现。next数组的长度是模式串P的长度,next[j]存储的是第j个元素之前的字符串里的最大首尾重叠元素数。当S[i]与P[j]处发生失配时,这个时候,S[i]前面的d个元素和P[next[j]]前面的d个元素是一样的,令j = next[j], i不变,再继续挨个匹配就行。注意next[0] = -1,因为如果设next[0]为0的话,如果第一个元素就失配,i不变,j=0,相当于永远就停在这个地方不往前走了。使next[0] = -1并且当j ==-1时 i++,j++,这样就会继续遍历。

KMP的代码如下,还是比较简洁的:

int KmpSearch(char* s, char* p)
{
	int i = 0;
	int j = 0;
	int sLen = strlen(s);
	int pLen = strlen(p);
	while (i < sLen && j < pLen)
	{
		//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++    
		if (j == -1 || s[i] == p[j])
		{
			i++;
			j++;
		}
		else
		{
			//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]    
			//next[j]即为j所对应的next值      
			j = next[j];
		}
	}
	if (j == pLen)
		return i - j;
	else
		return -1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值