KMP算法

Posted TangguTae

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法相关的知识,希望对你有一定的参考价值。

场景:在字符串去寻找一个特定的子串

28. 实现 strStr() - 力扣(LeetCode)

暴力匹配(BF)方法

暴力匹配的方法不难想到,也很容易实现。本质上说就是双指针的方式,逐个字符进行比较。

 

KMP算法

上述暴力的方法给出一旦某个字符匹配失败,子串的索引会回到一开始,重头再来。这样没有利用到以前的遍历过程中的信息。

KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的(避免重复的比较),是一种典型的空间换时间的方法。

用KMP算法的过程如上图, 可以看到,他利用了之前比较的信息,当子串最后一个字符不匹配的时候,首先主串的指针不需要回到下标为1的位置,而是原来的位置保持不变。子串现在寻找新的开始位置。

这样就可以减少比较次数。

那么如何去记录应该从哪一个字母开始呢?

KMP算法的核心next数组

next数组的作用:找到子串回退的下标位置。

用刚才的例子对应的子串为

当前位置 i 的字符回退下标是:寻找以0开始的子串 i-1 结束的子串相等最大长度,就为当前位置的回退下标。具体过程如下图所示:

现在我们就知道上面的例子为什么会回退到字符 c 这里,也就是子字符串下标为2这里。

举个复杂一点的例子:子字符串为 abcabcabcabcdabcde

如何从前一个字符的回退下标 推导出 当前字符的回退值?

如图所示:

上图给出的是当 subStr[k] == subStr[i-1] 的时候,如果不相等即 subStr[k] != subStr[i-1]时

next数组隐含的特性:如果next[i+1] > next[i], 那么next[i+1] == next[i] + 1; 不存在超越大于1的情况,如果有,那一定是错的。

代码实现:

int KMP(const string& str,const string& subStr)

	if (subStr.empty())
		return -1;
	if (str.size() < subStr.size())
		return -1;
	vector<int> next(subStr.size());//next数组,存储回退值
	next[0] = -1;
	for (int i = 1; i < subStr.size(); i++)
	
		int pre = next[i - 1];//当前元素的回退值在0-(i-1)区间找
		while (pre != -1 && subStr[i - 1] != subStr[pre])//根据每个字符的回退值,先找到相等的字母
			pre = next[pre];
		if (pre == -1)//没找到,则回退值为0,代表需要重头开始
			next[i] = 0;
		else
		
			next[i] = pre + 1;//如果找到了,当前的回退下标就为找到位置的回退下标+1
		
	
	//开始匹配
	int point_str = 0;//主串的指针
	int point_sub = 0;//子串的指针
	while (point_str < str.size() && point_sub < subStr.size())
	
		if (point_sub == 0 && str[point_str] != subStr[point_sub])
			point_str++;
		else
		
			if (str[point_str] == subStr[point_sub])
			
				point_str++;
				point_sub++;
			
			else
				point_sub = next[point_sub];
		
	
	if (point_sub == subStr.size())//子串的指针走到最后,说明子串找到了
		return point_str - point_sub;
	return -1;

next数组的优化

举个例子

 具体比较过程

subStr[ next[i] ] = subStr[i]时候,会存在很多重复性的比较。所以为了避免重复性比较,引入nextval数组。

nextval数组就是如果出现上述情况,让回退的值一次到位,即当subStr[ next[i] ] == subStr[i]时候,nextval[i] = nextval[ next[i] ]; 否则还是和之前相同,nextval[i] = next[i];

具体实现 

int KMP(const string& str,const string& subStr)

	if (subStr.empty())
		return -1;
	if (str.size() < subStr.size())
		return -1;
	vector<int> next(subStr.size());//next数组,存储回退值
	next[0] = -1;
	for (int i = 1; i < subStr.size(); i++)
	
		int pre = next[i - 1];//当前元素的回退值在0-(i-1)区间找
		while (pre != -1 && subStr[i - 1] != subStr[pre])//根据每个字符的回退值,先找到相等的字母
			pre = next[pre];
		if (pre == -1)//没找到,则回退值为0,代表需要重头开始
			next[i] = 0;
		else
		
			next[i] = pre + 1;//如果找到了,当前的回退下标就为找到位置的回退下标+1
		
	
	//计算nextval数组
	vector<int> nextval(subStr.size());
	nextval[0] = -1;
	for (int i = 1; i < subStr.size(); i++)
		subStr[i] == subStr[next[i]] ? nextval[i] = nextval[next[i]] : nextval[i] = next[i];

	//开始匹配
	int point_str = 0;
	int point_sub = 0;
	while (point_str < str.size() && point_sub < subStr.size())
	
		if (point_sub == 0 && str[point_str] != subStr[point_sub])
			point_str++;
		else
		
			if (str[point_str] == subStr[point_sub])
			
				point_str++;
				point_sub++;
			
			else
				point_sub = nextval[point_sub] >= 0 ? nextval[point_sub] : 0;//如果为-1表示在第一个字符这
		
	
	if (point_sub == subStr.size())
		return point_str - point_sub;
	return -1;

 

以上是关于KMP算法的主要内容,如果未能解决你的问题,请参考以下文章

KMP算法

KMP算法

KMP算法

KMP算法详细理解

KMP算法

KMP算法