从头至尾彻底理解扩展KMP

从头到尾彻底理解扩展KMP

1. 扩展KMP问题:

求字符串S的所有后缀和字符串T的最长公共前缀。

扩展KMP可以用来解决很多字符串问题,如求一个字符串的最长回文子串和最长重复子串。

2. 拓展KMP的next[]数组怎么计算?

在解上面这个问题前我们要先解决一个类似的问题:
字符串s所有后缀字符串s本身的最长公共前缀;
我们用next[]数组保存这些值;

现在我们假设要求next[x],并且next[i](0<i<x)的值都已经求出;
我们设 p=k+next[k]1k 是使 p 最大的 i(0<i<x)

如图所示:
从头至尾彻底理解扩展KMP

现在整理一下问题:

已知:s[k..p]=s[0..next[k]1],求s[x..n1]s[0..n1]的最长公共前缀;

解:
s[k..p] =s[0..next[k]1] 得:
s[x..p] = s[xk..next[k]1] …………………….(1) 这个是显然的

并设 L1=px+1
因为 xk<x,所以 L2=next[xk]是已知的,得:
s[0..L21]=s[xk...xk+L21] …………………….(2)

通过等式(1),(2)可以推出s[0..k1]=s[x..k2]

L1L2时,如下图所示:
从头至尾彻底理解扩展KMP

表示s[0..L11]=s[x..x+L11],但不能确定蓝色部分是否相等,所以需要继续比下去。

L1>L2时,如下图所示:
从头至尾彻底理解扩展KMP

表示s[0..L21]=s[x..x+L21]
而且因为L2=next[xk],使得s[L2]s[x+L2]
所以next[x]=L2

证明:
假设s[L2]=s[x+L2]
又因为s[x+L2]==s[xk+L2]…………由(1)推出
所以s[L2]=s[xk+L2]
所以next[xk]=L2+1next[xk]=L2矛盾

next[]数组的代码如下:

void getNext(char *s, int next[]) {
    int lenS = strlen(s);

    next[0] = lenS;
    int p = 0;
    while (p+1 < lenS && s[p] == s[p+1]) p++;

    next[1] = p;
    int k = 1, L;
    for (int i = 2; i < lenS; i++) {
        p = k + next[k] - 1, L = next[i-k];
        if (i + L <= p)
            next[i] = L;
        else {
            int j = max(p-i+1, 0);
            while (i + j < lenS && s[i+j] == s[j]) j++;
            next[i] = j, k = i;
        } 
    }
}

3. 如何计算extend[]数组的值

回到原来的问题
此时已经求出next[],我们用extend[]保存字符串S的所有后缀和字符串T的最长公共前缀的值

求解extend[]数组的方法 和 求解next[]数组的方法类似,重复上面的过程:

假设要求extend[x],并且extend[i](0<i<x)的值都已经求出;
我们设p=k+extend[k]1, k是使p最大的 i(0<i<x)

如图所示:
从头至尾彻底理解扩展KMP

整理一下问题:

已知:s[k..p]=T[0..extend[k]1],求s[x..n1]T[0..m1]的最长公共前缀;

解:
s[k..p] =T[0..extend[k]1] 得:
s[x..p] = T[xk..extend[k]1] …………………….(1)

并设 L1=px+1
因为 xk<x,所以 L2=next[xk]是已知的,得:
T[0..L21]=T[xk...xk+L21] …………………….(2)

通过等式(1),(2)可以推出T[0..k1]=s[x..k2]

L1L2时,如下图所示:
从头至尾彻底理解扩展KMP

表示T[0..L11]=s[x..x+L11],但不能确定蓝色部分是否相等,所以需要继续比下去。

L1>L2时,如下图所示:
从头至尾彻底理解扩展KMP

表示T[0..L21]=s[x..x+L21]
而且因为L2=extend[xk],使得T[L2]s[x+L2]
所以extend[x]=L2

证明:
假设T[L2]=s[x+L2]
又因为s[x+L2]=T[xk+L2]…………由(1)推出
所以T[L2]==s[xk+L2]
所以extend[xk]=L2+1extend[xk]=L2矛盾

extend[]数组的代码如下:

void getExtend(char *s, char *T, int extend[], int next[]) {
    int lenS = strlen(s) ,lenT = strlen(T);
    getNext(s,next);

    int p = 0;
    while(p < lenS && s[p] == T[p]) p++;

    extend[0] = p;
    int k = 0, L;
    for(int i = 1; i < lenS; i++) {
        p = k + extend[k] - 1, L = next[i - k];
        if(i + L <= p)
            extend[i] = L;
        else {
            int j = max(p-i+1, 0);
            while (i + j < lenS && s[i + j] == T[j]) j++;
            extend[i] = j; k = i;
        } 
    }
}

4. 时间复杂度分析:

对于s串,计算next[]数组的时间是O(n),计算extend[]数组的时间是O(m)的,总算法复杂度是O(n+m);

此博文转自:http://www.cnblogs.com/Rlemon/archive/2013/06/26/3157574.html