先抛出要求解的问题:已知一个文本串
“BBC ABCDAB ABCDABCDABDE”和模式串“ABCDABD”,现在求解模式串出现在文本串中的位置。
最直观的就是使用暴力的方式求解。假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有:
如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。
不匹配时S串,自动往右走。
当遇到匹配的时候,S串和P串都往右走,i++,j++。
S串和P串一直往右走,直到遇到不匹配的:
那么S串往回退,P串直接退到0处,i = i - (j - 1),j = 0:
暴力查找的代码如下:
int MatchSub(char *s,char *p){
int sLen=strlen(s);
int pLen=strlen(p);
int i=0,j=0;
while(i<sLen && j<pLen){
if(s[i]==p[j]){
++i;
++j;
}
else{
i=i-j+1;
j=0;
}
}
if(j==pLen){
return i-j;
}
else
return -1;
}
这中暴力匹配的方法因为这其中存在着重复计算的原因所以其中存在着可以优化的部分。那么当不匹配(即S[i]! = P[j])时,能否可以使i和j不回退那么多呢?有,这就是大名鼎鼎的KMP算法解决的问题了。
KMP算法的主旨是利用之前已经部分匹配这个有效信息,保持i 不回溯,通过修改j 的位置,让模式串尽量地移动到有效的位置。
KMP算法对暴力匹配方法做了如下更改:
假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符; 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值。
而next数组中代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k的相同前缀后缀。
匹配如下所示:
与暴力匹配不同,此时i不变,j移动到next[j],如下所示:
从上面可以看出其实问题的关键是next数组中,那么问题来了:next数组是怎么来的呢?
next数组保存的是模式串当前字符前(不包含当前字符)值相等的最长前缀和后缀。若模式串当前下标为j,对于P = p0 p1 …pj-1 pj,寻找模式串P中长度最大且相等的前缀和后缀。如果存在p0 p1 …pk-1 = pj- k pj-k+1…pj-1,那么在包含pj的模式串中有最大长度为k的相同前缀后缀。
由于前缀和后缀是相同的因此当不匹配(即S[i] != P[j])的时候就可以将j回退到当前值的相同前缀的下一个值,即j = next[j](注意:如果j移动之后P[j]还是和当前的S[i]不匹配那么,j需要继续往前回归)。
上文中的模式串“ABCDABD”的next数组为:
下面给出完整的KMP代码。
首先是计算next数组:
void getNext(char *p, int next[]){
int pLen = strlen(p);
next[0]=-1;
int k=-1,j=0;
while(j<pLen){
if(k==-1 || p[k]==p[j]){
++k;
++j;
next[j]=k;
}
else
k=next[k];
}
}
然后是KMP算法:
void KMP(char *s,char *p, int next[]){
int sLen = strlen(s);
int pLen = strlen(p);
int i=0,j=0;
while(i<sLen && j<pLen){
if(j==-1 || s[i]==p[j]){
++i;
++j;
}
else{
j=next[j];
}
}
if(j==pLen)
return i-j;
else
return -1;
}
后记:有一个很奇怪的现象,当KMP()代码中写成如下形式的时候:
int strStrKMP(string haystack, string needle, int next[]){
int i=0,j=0;
//int hLen = haystack.length(),nLen=needle.length();
while(i<haystack.length() && j<needle.length()){
if(j==-1 || haystack[i]==needle[j]){
++i;
++j;
}
else
j=next[j];
cout<<needle.length()<<endl;
}
的时候只能输出-1,不能输出其他的值,但是当把while的条件改成这样时:
int hLen = haystack.length(),nLen=needle.length();
while(i<hLen && j<nLen){
......
}
代码又能很好的运行了。是不是中遍历数组haystack的时候,再对它求长度的时候就会出错?!