KMP算法指的是字符串模式匹配算法,问题是:在主串T中找到第一次出现完整子串P时的起始位置。该算法是三位大牛:D.E.Knuth、J.H.Morris和V.R.Pratt同时发现的,以其名字首字母命名。
先看看暴力法:
可以看到主串第i的位置和模式串的第j位置,匹配失败,对于暴力法则进行下面的比对。
总结下来就是,比对失败就将模式串回退到首个字符然后继续和主字符串比对失败的那个字符继续进行对比。
具体代码可以参看我的另一篇文章:BF算法匹配字符串
KMP算法操作过程如下,i不回退,只操作模式串的位置。这样就大大提高了效率。
接下来再看看
此时模式串应该从’C’开始和主串的第i个元素比较。
通过各种例子最终总结了一个规律:
找到最大前缀和后缀的公共子串,然后将前缀移动到和后缀位置相同的地方。
举例说明如下:
在“aba”中,前缀集就是除掉最后一个字符’a’后的子串集合{a,ab},同理后缀集为除掉最前一个字符a后的子串集合{a,ba},那么两者最长的公共子串就是a,k=1;
在“ababa”中,前缀集是{a,ab,aba,abab},后缀集是{a,ba,aba,baba},二者最长公共子串是aba,k=3;
在“abcabcdabc”中,前缀集是{a,ab,abc,abca,abcab,abcabc,abcabcd,abcabcda,abcabcdab},后缀集是{c,bc,abc,dabc,cdabc,bcdabc,abcdabc,cabcdabc,bcabcdabc},二者最长公共子串是“abc”,k=3;
一般如果从0开始比较的话,那么k就是下一次模式串和主串比较的位置。
其实说白了KMP的算法主要核心就是求解模式串中每个字符位置对应的最大前后公共缀的长度k。这个搞明白了KMP的算法就七七八八了。
其实这也就是著名的next数组的求解。
接下来看看图。
模式串为 j 的时候最大公共子串长度为 k 也就是2,现在模式串的位置要后移一位,则为右图的位置 j+1 ,也就是说主串和子串第j+1的字符比较时匹配失败。看j+1之前的最大公共子串。
因为j的时候,k == 2;
那j+1的时候只需比较仅接着的一位,注意看右边黄色的最后一位‘c’和灰绿色的最后一位‘c’,可以看到他们相等。那这样就总结了一个公式:
1):最后一个字符相等的情况(p[k] == p[j])
if(next[j] == next[k])
{
next[j+1] = k+1;// = next[j] + 1;
}
也就是说当最后一个字符匹配成功时,就是上一次最大长度加1。
2):最后一个字符不相等的情况(p[k] != p[j]),如图:
也就是
贴上代码:
void GetNext(char* p, int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
//这一句非常不好理解。查阅了很多博客最终才理解,这一句理解了也就意味着都理解了。
k = next[k];
}
}
}
k = next[k];图解
我想应该你能理解了吧。根据肉眼看到next[j+1] = 2;
验证:
k = next[3] = 1;
对于:if (k == -1 || p[j] == p[k])
则有p[j] == ‘B’,p[k] = p[1] = ‘B’
满足条件,则next[j+1] = k+1 = 1+1 =2;
现在你应该知道为什么要k = next[k]了吧!像上边的例子,我们已经不可能找到[ A,B,A,B ]这个最长的后缀串了,但我们还是可能找到[ A,B ]、[ B ]这样的前缀串的。所以这个过程像不像在定位[ A,B,A,C ]这个串,当C和主串不一样了(也就是k位置不一样了),那当然是把指针移动到next[k]啦。
接下来看看完整代码:
void GetNext(char* p, int next[])
{
int pLen = strlen(p);
next[0] = -1;
int k = -1;
int j = 0;
while (j < pLen - 1)
{
//p[k]表示前缀,p[j]表示后缀
if (k == -1 || p[j] == p[k])
{
++k;
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
const int nNextSize = 20;
int KMP(char* s, char* p)
{
int next[nNextSize] = { 0 };
GetNext(p, next);
int j = 0;//模式串的起始位置
int i = 0;//主串的起始位置
int nslen = strlen(s);
int nplen = strlen(p);
while (i<nslen && j < nplen)
{
if (-1 == j || s[i] == p[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (j<strlen(p))
{
//没有匹配到模式串末尾,主串结束了
return -1;
}
if (j == strlen(p))
{
return i - j;
}
}
int main()
{
int nflag = KMP("xfffabcxabcabce", "abcabc");//应该为8
cout <<"'abcabc'在'xfffabcxabcabce'中的位置:"<< nflag << endl;
nflag = KMP("I Love Ty,and you?", "Ty");//应该为7
cout << "'Ty'在'I Love Ty,and you?'中的位置:" << nflag << endl;
system("pause");
return 0;
}
结果;