为了实习面试,复习算法和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时写的代码比现在复习时写的代码简洁多了,代码能力退化得太厉害了,伤不起啊