KMP 算法之得到next的代码

时间:2023-01-03 16:34:17

最近温习了一下KMP算法。现在谈谈我对KMP算法的理解。
KMP算法目的:尽量快的解决单字符串匹配的问题。

一、问题:
主字符串: ababcababababcab
模式串: abababab

判断在主串中是否存在模式串,如果存在,在哪个位置。

二、解决
解决办法很多,单谈KMP算法的next获取方法。
next数组的目标:在匹配失败的时候,将模式串的下标位置尽量的少的前移,使匹配速度最快。
进一步说就是:在模式串的每一个位置进行切割(不包括当前位置),前面一段字符串中,找出一个后缀(即不包括第一个字符),使得完全匹配自身的前缀。
可能有点绕口,看图!

1. 为了更直观起见,我将模式串进行放大并影分身!你看到的是两个模式串,但其实是一个(后面不再提醒)。

KMP 算法之得到next的代码

2. 那,这样我们得到一段字符串aba,它只存在一个后缀a,与其本身的前缀a匹配。如下图。
KMP 算法之得到next的代码
3. 则,这个位置的next值就为1。即next[3]=1;如下图。
KMP 算法之得到next的代码
4. 同理,如果我们在5位置切割,如下图,得到next[5] = 3;如下图。
KMP 算法之得到next的代码
5. 依次类推,所有的位置next值都可以得到,如下图。
KMP 算法之得到next的代码
那么next值是什么,就是在当前位置匹配失败的时候,下标应该改变到的位置,继续进行匹配。

三、编程
1. 在编程的时候,很多地方都会把next的第一个位置设置为-1。即表示主串下标后移一位,重新开始匹配模式串。
其实根据当前下标为0,且匹配失败这种情况,将主串下标后移也是可行的,不过书写起来比较麻烦而已。

注意:可能你会注意到,在没有优化的KMP算法中,总是有next[0] = -1; next[1] = 0;
原因是什么?看前面二中的图部分。多想想就知道了!

next获取的c代码,来源: http://blog.csdn.net/sowhat_ah/article/details/42168727
int get_next(const char *pstr, int *next){
int i = 0;
int k = -1;
int plen = strlen(pstr);
int mlen = plen - 1;

next[i] = k;
while(i < mlen)
{
if((-1 == k) || (pstr[i] == pstr[k]))
{
next[++ i] = ++ k;
}
else
{
k = next[k];
}
}

return plen;
}


2. 关于KMP的优化。就是优化next数组。
优化的原因:
a. 首先应该理解,next值的意思是将模式串的下标移到该位置,再与主串进行比较。
b. 那你想啊,如果模式串下标移动之后的位置,和当前下标指的字符是同一个字符,那么匹配肯定还是失败的。还要继续前移!

例如在位置4处匹配失败,根据next[4]=2;跳转!
KMP 算法之得到next的代码
匹配之后跳转到位置2的时候,还是a!!!肯定匹配不成功了!所以还要继续往前跳!!!如下图。
KMP 算法之得到next的代码
如果在建立next的时候,提前把这个“我再跳”的操作已经做好,一步跳到最前面!岂不是更好!!!如下图:
KMP 算法之得到next的代码
了解到这里,其实可以把next数组分两步走,
第1步:按照扇面1中的代码,建立next数组。
第2步:遍历next数组,如果跳转到的位置和当前的位置是相同的字符,则把当前的next值赋为跳转到的位置的next值。如下图。
(只遍历到3的位置,后面依次类推。)
KMP 算法之得到next的代码
最终得到的next如下图,阴影部分均为第2步的所述的原因。
KMP 算法之得到next的代码

最后奉上优化的代码(将第1步和第2步合并)。

//zh for zhuhong
int get_next_zh(const char *pstr, int *next)
{
int i = 0;
int k = -1;
int plen = strlen(pstr);
int mlen = plen - 1;

next[i] = k;
while(i < mlen)
{
if((-1 == k) || (pstr[i] == pstr[k]))
{
next[++ i] = ++ k;
// here is the modification
if(pstr[i] == pstr[k])
{
next[i] = next[k];
}
// end the modification
}
else
{
k = next[k];
}
}

return plen;
}