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

来源:互联网 发布:淘宝优惠券的使用规则 编辑:程序博客网 时间:2024/06/08 13:46

为了实习面试,复习算法和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 ababacblillianxiong liabscddbbscdd scdd*/
7.练习题:http://cstest.scu.edu.cn/soj/problem.action?id=2652(裸的KMP)

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


原创粉丝点击