现在的位置: 首页 > 综合 > 正文

kmp

2018年04月29日 ⁄ 综合 ⁄ 共 1441字 ⁄ 字号 评论关闭

简而言之

用途:

kmp算法用于在【长串】中找【模式串】,并返回【配准的位置】,速度上优于暴力的算法。


暴力法简述:

长串A,短串B,从头(i = j = 0)开始逐个(++)比较,如果A[i] != B[j],j退回0,i退回本次匹配 i 初始位置,并i++,如此往复。如果在脑中模拟这一过程,好比【短串在长串中
滑动(向右),每滑一步,把短串中的内容从头到尾刷一遍<最坏情况>】,因此时间复杂度为 O(M * N)


kmp 法简述:

1.我看到的所有讲kmp的blog,都提到kmp 的核心,在于【不回溯长串】,意思是说kmp进行匹配时,i是没有上面暴力法提到的【i退回本次匹配 i 初始位置,并i++】这一步,只增不减。这一下就使得时间复杂度降到了线性,O(M)。

2.如何做到这点,灵魂在于使用了【next数组】。而所谓next数组,是一个关注并记录【前缀后缀】位置信息的数组。用下图来解释,图中S代表长串,P代表短串。

ps:该图来自july博客。

想一个问题,如果已知 

(1)P0 P1 ... Pk-1 == Pj-k Pj-k+1 ... Pj-1;

(2)Pj-k Pj-k+1 ... Pj-1 == Si-k Si-k+1 ... Si-1 且 Pj != Si


是否意味着两件事:

1、匹配位置不是Si-k,得继续去找配准位置;

2、已经确知【P0 P1 ... Pk-1 == Si-k Si-k+1 ... Si-1】,不需要再去比较了,只需要看 Pk 是否等于 Si就可以了。省掉了多次比较。脑中模拟这一次匹配,感觉就像【长串不动,短串很自信地向右狠狠滑了一下,并且从此以后从S0到Si-1都不需要再看了】。


那么,这个信心是谁给它的呢?答案就是上面提到的【next数组】。

也说过了,next数组里存放的信息,关注【前缀后缀】。那么什么是前缀后缀,又关注了些什么呢?

1)简而言之,一个字符串abcdefghijklmn,对于d来说,所谓前缀,即从头开始到d为止的字串:abc;所谓后缀,即从尾倒着往前数直到前缀长度为止的字串:lmn

2)对于n后的下一个字符x,next[x] == d

3)最后要注意到图中所说的【最长】二字

计算这个next数组的时间复杂度同样是线性的,O(N)。

故总的kmp时间复杂度为 O(M+N)。

附个完整代码如下:

#include <iostream>
using namespace std;

void get_next(char *a, int *next)
{
	int len = strlen(a);
	int i = 0, j = -1;
	next[0] = -1;
	
	while(i < len - 1)
	{
		if(j == -1 || a[i] == a[j])
		{
			++i; ++j;
			if(a[i] != a[j])        //优化 
				next[i] = j;
			else
				next[i] = next[j];
		}
		else
		{
			j = next[j];
		}
	}	
}

int kmp(char *src, char *tag)
{
	int next[255];
	get_next(tag, next);
	
	int lens = strlen(src), lent = strlen(tag);
	int i = 0, j = 0;
	
	while(i < lens && j < lent)
	{
		if(src[i] == tag[j] || j == -1)
		{
			++i; ++j;
		}	
		else
		{
			j = next[j];
		}	
	}
	if(j == lent)
		return i - j;
	else
		return -1;
}

int main(void)
{
	char src[] = "DABCDABDE";
	char tag[] = "ABD";

	cout << kmp(src, tag) << endl;
		
	return 0;
}
【上篇】
【下篇】

抱歉!评论已关闭.