字符串匹配-扩展KMP(Extend-KMP)

首先还是来看看问题:
给出一个长为N的字符串S,再给出一个长为M的字符串T
求S的所有后缀中和T的最长公共前缀
显然可以想到暴力的做法,枚举S所有的后缀,然后和T做匹配,时间复杂度为 O(NM)
显然,这个方法和之前的暴力一样,都处理了太多的重复操作,那么可以用类似KMP的方法来处理吗?
答案是肯定的,也就是Extend-KMP算法
可以先用类似KMP的想法,用 next 数组保存 T[i..M1] T[0..M1] 的最长公共前缀,但是有一个特例: next[0]=len
接着设p为答案伸得最远的后缀的下标,也就是 T[p..M1] ,但是这个后缀不包括原串,
如果这两个串的公共前缀没有包括 T[i] ,那么就要去暴力求解,如果包括就可以尝试像 manacher 一样用前面已经求出的答案。
接下来举个栗子:
字符串 T=aaaabaaaa
那么 next=9,3,2,1,0,4,???
根据之前求出的,可以知道 T[0..3]=T[5..8] ,并且 T[1..3]=T[0..2] ,然后把 T[0..3] T[5..8] 各去掉相同位置的一位,仍然相等,也就是 T[1..3]=T[6..8]
根据之前的结果推导出 T[6..8]=T[1..3]=T[0..2] 因此, next[6] 应该至少为3
然后检查是否能够增加,发现 S[9]S[3] 所以 next[6] 不能增加,也就是3
用类似这样的方法,同样可以直接推导出extend的结果,那么就得到了答案。
以下为代码:

void get_next() {
    int len = strlen(T);
    _next[0] = len;
    int a = 0;

    while(a < len - 1 && T[a] == T[a + 1]) {
        ++a;
    }
    _next[1] = a;
    a = 1;

    for(int k = 2; k < len; ++k) {
        int p = a + _next[a] - 1;
        int L = _next[k - a];

        if(k - 1 + L >= p) {
            int j = (p - k + 1) > 0 ? (p - k + 1) : 0;

            while(k + j < len && T[k + j] == T[j]) {
                ++j;
            }

            _next[k] = j;
            a = k;
        } else {
            _next[k] = L;
        }
    }
}

void ExtendKMP() {
    int i = 0, j, po, lenS = strlen(S), lenT = strlen(T);
    get_next();

    while(S[i] == T[i] && i < lenT && i < lenS) {
        i++;
    }

    ex[0] = i;
    po = 0;

    for(i = 1; i < lenS; i++) {
        if(_next[i - po] + i < ex[po] + po) {
            ex[i] = _next[i - po];
        } else {
            j = ex[po] + po - i;

            if(j < 0) {
                j = 0;
            }

            while(i + j < lenS && j < lenT && S[j + i] == T[j]) {
                j++;
            }

            ex[i] = j;
            po = i;
        }
    }
}

代码有点长……
最后来看看时间复杂度:
先看求 next
因为内部while每运行一次,j至少加1,最多执行M次,再加上外层循环的语句,时间复杂度为 O(M)
extend 同理,所以总时间为 O(M+N)
但是相对于KMP来说,这个算法的常数比较差,所以实际运行时应该会慢一点,但对于不是线性时间求出答案的方法,是很快的。

你可能感兴趣的:(字符串)