【字符串】KMP字符串匹配算法

为了实习面试,复习算法和coding练手感。

OK.正题。

1.什么是KMP.

KMP是K M P这三个人发现的一种字符串匹配算法。具体来说,设目标串str,模式串pat. kmp可以在O(n+m)时间内找出str中的pat。KMP快的原因是每次匹配到某个字符失配时,不是回到开始匹配的位置的下一个字符重新匹配,而是通过预处理模式串pat,找出失配时,下一次应该从设么位置开始继续匹配。这样一来,失配时目标串的指针位置不需要动,只需要改变模式串的指针位置。

2.举个例子

位置:    1 2 3 4 5 6 7 8 9

目标串:a b a b a b c a b 

模式串:a b a b c

                1 2 3 4 5

第一轮匹配,当到达 位置5时失配,如果暴力匹配的话,会回到目标串的位置2和模式串的位置1开始新一轮的匹配;而KMP则是利用先前匹配的结果来减少无用匹配。即:此时不改变目标串指针位置(依然停留在位置5)而使模式串位置指针退回到位置3(为什么?),这样一来,第二轮匹配过程变为:

位置:    1 2 3 4 5 6 7 8 9

目标串:a b a b a b c a b 

模式串:      a b a b c

                      1 2 3 4 5

于是在目标串中匹配除了模式串,匹配开始的位置是目标串中的位置3开始。

3.上例中,第一次失配后,为什么是退回到位置3?

由于第一轮匹配中,已经匹配到了位置5,即str[1-4] = pat[1-4](1),如果此时直接按上述暴力方法退回到str的位置2开始重新匹配,则没有利用好(1)式。(1)式表明,假设pat[1,i] = pat[4-i+1,4],那pat[i,i]一定是与str[4-i+1,4]匹配的。这样一来,我们只需要找出pat的前缀中pat[1,4]的最长后缀,再从这个后缀位置往后匹配目标串,而失配时目标传位置不需要后退。这个最长后缀的位置就是失配时需要跳转过去的位置,即pi[ ]数组。

4.怎样求pi数组

pi数组其实是对模式串pat的预处理过程求的。通过递推的方式,可以轻松求出。

例如:(此处字符串下标从0开始算起)

位置:  0  1  2  3  4

串pat:a  b  a  b  c

pi:      -1 -1 ???

很显然,pi[0] = -1.表示没有前缀串是它的后缀,需要从头开始匹配,即失配时,需要对pat串从头扫瞄检测匹配。对位置2字符b,怎样看它的pi值,可以看它前面一个位置(即位置1)的pi值是多少(设为j),此时若有pat[j+1] == pat[cur], 那么对cur位置的字符来说,它的最长后缀就是它前一个字符的最长后缀+1,即pi[cur] = pi[j]+1,否则不存在最长后缀 pi[cur] = -1,表示此位置失配时需从头开始匹配。

按此法,可计算得上例问号处的值分别为:

位置:  0  1  2  3  4

串pat:a  b  a  b  c

pi:      -1 -1 0  1 -1

现在就清楚了为什么是退回到位置3。

5.貌似KMP就这样讲完了。。。

6.代码:

int *getp(const char *pat)//求失配时跳转位置的函数, 存在pi[ ]里
{
	int len = strlen(pat);
	int *pi = new int[len + 1];
	assert(pat && pi);
	pi[0] = -1;
	for(int i=1;i<len;i++)
	{
		int j=i-1;
		while(j>=0)
		{
			int pre = pi[j];
			if(pat[i] == pat[pre+1])
			{
				pi[i]=pre + 1;
				break;
			}
			j = pi[j] ;
		}
		if(j<0) pi[i] = -1;
	}
	// for(int i=0;i<len;i++)
		// printf("%d ",pi[i]);
	// cout << endl;
	return pi;	
}

vector<int> kmp(const char *str, const char *pat)
{
	assert(pat && str);
	vector<int>v;
	int *pi = getp(pat);
	int len1 = strlen(str);
	int len2 = strlen(pat);
	int j=0;
	int st=-1;
	for(int i=0;i<len1;i++)
	{
		while(j<len2 && i<len1)
		{
			//cout << "lixiong\n";
			if(str[i] == pat[j])
			{
				if(st==-1) st = i - j;
				j++;
				i++;
			}
			else
			{
				if(j==0) i++;
				else j = pi[j-1]+1;
				st = -1;
			}
			
		}
		if(j==len2)
		{
			v.push_back(st);
			i--;
			j = pi[j-1]+1;
			st = -1;
		}
	}
	return v;		
}

/*
test case:
abababacb ababacb
lillianxiong li
abscddbbscdd scdd
*/
7.练习题: http://cstest.scu.edu.cn/soj/problem.action?id=2652(裸的KMP)

8.感叹一句,去年学KMP时写的代码比现在复习时写的代码简洁多了,代码能力退化得太厉害了,伤不起啊


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值