从头到尾彻底理解扩展KMP

本文详细介绍了扩展KMP算法,包括如何计算next[]数组和extend[]数组,用于求解字符串的最长公共前缀。文章通过实例解析了计算过程,并分析了时间复杂度为O(n+m)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 扩展KMP问题:

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

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

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

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

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

如图所示:
这里写图片描述

现在整理一下问题:

已知: 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 时,如下图所示:
这里写图片描述

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

L1>L2 时,如下图所示:
这里写图片描述

表示 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+1 next[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)

如图所示:
这里写图片描述

整理一下问题:

已知: 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 时,如下图所示:
这里写图片描述

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

L1>L2 时,如下图所示:
这里写图片描述

表示 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+1 extend[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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值