KMP算法、next数组与前缀中的周期(相关题目:Power strings, poj2406)

在一个大的字符串S中查找字符串T,naive的算法时间复杂度为O(s * t)(这里s与t代表S的长度与T的长度);而应用KMP,时间复杂度为O(s + t)。

KMP算法的核心在于next数组。next数组只与字符串T有关,与S无关。

next数组的核心思想是存储字符串T的内容的相似性信息,具体而言:

next数组记录了T的每一个前缀子串M(m>=2)中,(M的)相等的前缀真子串与后缀真子串的最大长度。换句话说,就是M的首部和M的尾部最多有多少个字母完全相同。

举个例子,若T = agctagcagctagctg,求其next数组。
《KMP算法、next数组与前缀中的周期(相关题目:Power strings, poj2406)》

例如,前缀子串M = agctagcagct,长度为11,首部的agct与尾部的agct是一样的,长度为4,所以next[11 -1] = next[10] = 4。

那么,如何
编程求字符串T的next数组呢?Here is a bit tricky.

举个例子,T的前缀子串M = (a)(b)(a)…(a)(b)(a)(X),我们已经计算出: next0 = 0 next1 = 0 next2 = 1 next3 = 1 next4 = 2 next5 = 3 现在要计算next6即字符X的next数组值。

先看X的前一个字符的next数组值next5,为3,说明X前面的3个字符和开头的3个字符已经相同了:

如果X与第4个字符相同,即X == a,于是M的前4个字符与后4个字符相同,所以next5 = 4;

如果X与第4个字符不相同,比如X == b,于是需要找短一点的相同字串,长度小于next5 = 3的。于是下一步我们考察next[next[5]],也就是next[3]的值,next3=1。
这里面的逻辑是:看子串M,因为next3=1,所以1号a与2号a相同,又因为next5=3,所以aba与aba相同,这意味着1号a与3号a相同,2号a与4号a相同,综上,1号a与4号a相同。所以,只要比较X(4号a的下一个字符)与1号a的下一个字符是否相同即可。这里X == b,与M的第二个字符相同,所以next6 = 2;就这样一直递归查找下去;

如果以上情况都不符合,next6 = 0。

下面是代码:

void get_next(const string T, int next[])
{
    int len = strlen(T);
    int i, k;    

    next[0] = 0;    //显然,next数组的第一个元素总是0

    for (i=1; i<len; ++i)
    {
        k = next[i-1];    //取得前一个字符的next值
        while (T[i] != T[k] && k != 0) k = next[k-1];    //如果不等,继续递归
        
        if (T[i] == T[k]) next[i] = k+1;    //直接延续前一个字符的对称性,或者找到稍小一些的对称性
        else next[i] = 0;
    }
}

接下来,考察
next数组的一个性质: 字符串T的长度为len,若len % (len – next[len-1]) == 0(其中next[len-1] != 0),则T有长度为(len – next[len-1])的
循环节(长度最小的重复单位)。

例如,若T = aabaabaabaab,next[11] = 9,12 % (12 – 9) == 0,则T有长度为3的循环节。脑补一下以aab为单位的子串比较过程即可理解。用这个性质可解决poj 2406。

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/ditian1027/article/details/19973819
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞