KMP算法 求next数组的原理

本文详细解析了KMP算法中next数组的求解过程,通过实例展示了如何利用已知的前后缀匹配来计算未知的next值,深入理解KMP算法的核心逻辑。

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

在kmp算法中,最难理解的那部分就是next数组的求解原理,今天看懂了一点点,现在记录一下

我们假设有这么一个字符串(模式串):abac......abab.......        
为方便起见,我们用数组 S 称呼这个字符串

下标0123................56789..............
abac................abab ..............
  k1 k    jj+1 


如上图所示,我们可以知道:当 j=8 的时候,next[j]=3(因为S[0]S[1]S[2] = S[5]S[6]S[7]),现在我们来求next[j+1]

  1. 如果S[k] = S[j],即S[3] = S[8],  那么 next[9] = next[j+1] = next[j] + 1 = k + 1 = 4
  2. 如果S[k] != S[j],即如上图所示的S[3] != S[8],那该怎么办呢?    下面着重讨论这个问题
由表格中的数据我们可知,由于S[k] != S[j],所以next[j+1]不可能为k+1这么简单,也就是说 next[j+1] 的长度不可能比 next[j] 再长了,所以我们只能寻找更短的相同前后缀

现在我们让 k1 = next[k](本来在代码中应该是:k = next[k],从而构成一个循环,但这里为了方便描述故而采用 k1 的说法,千万不要被误导),由于 k = 3,所以新的 k1 = next[3] = 1(前提是 next[k] 真的有相同的前后缀)(因为S[0] = S[2]),下面重点来了:

因为 S[0]S[1]S[2] = S[5]S[6]S[7] , 所以S[2] = S[7], 又因为有 S[0] = S[2],所以 S[0] = S[7],此时如果有S[k1] = S[j],那么就有:S[0]S[k1] = S[7]S[j],那么就可推断出 next[j+1] = next[k] + 1

最后我们发现 S[k1] = S[1] = b,S[j] = S[8] = b, 所以S[k1] = S[j] , 所以next[j+1] = next[k] + 1  ==> next[9] = 2

(2)上面的情况是 S[k1] = S[j],所以才有 next[j+1] = next[k] + 1, 那要是 S[k1] != S[j] 呢?又会是个什么情况

下标0123................56789..............
abac................abad ..............
 k2k1 k    jj+1 

如上面表格所示:此时 k1 = 1, j = 8, S[k1]  !=  S[j]  即:b != d, 这个时候我们继续让 k2 = next[k1] = next[1] = 0, 由于S[k2] != S[j], 即:S[0]  != S[8], 所以继续让 k3 = next[k2] = next[0] = -1

这个时候 k3 = -1,说明 next[j+1] 没有相同的前后缀,所以我们就让 next[j+1] = 0,并且重新开始计算 next[j+2] 的相同前后缀,对应的代码就是:j++; k++; next[j] = k; (因为 k 自增之后才为0,j 自增刚好用于计算下一个字符的相同前后缀)


说明:我这里只是描述了其中一种情况而已,但是已经足够用来说明next数组的求解原理了,表格中的红色字体部分可以替换成更长的字符序列,都没有任何问题,因为原理都是一样的


下面是KMP算法的完整代码:

    /**
     * KMP算法
     * @param source 源字符串
     * @param target 模式字符串
     * @return 匹配到的第一个下标
     */
    public static int kmp(String source, String target) {
        char[] s = source.toCharArray();
        char[] t = target.toCharArray();
        //计算next数组
        int[] next = getNext(target);
        int i = 0, j = 0;
        while (i < s.length && j < t.length) {
            if (j == -1 || s[i] == t[j]) {
                i++;
                j++;

            } else {
                j = next[j];
            }
        }
        // 匹配成功,返回第一个匹配到的下标,否则返回-1
        if (j == t.length) {
            return i - j;
        }
        return -1;
    }

    /**
     * 计算next数组
     * @param target 模式串
     * @return next数组
     */
    public static int[] getNext(String target) {
        char[] t = target.toCharArray();
        int[] next = new int[t.length];
        //需要单独给next[0]赋值为-1,因为后面的循环最多只能给next[1]赋值,无法循环到next[0]
        next[0] = -1;
        int j = 0, k = -1;
        while (j < t.length - 1) {
            if (k == -1 || t[j] == t[k]) {
                j++;
                k++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
        return next;
    }






### KMP算法next数组解方法详解 KMP算法的核心在于减少不必要的字符比较次数,从而提高模式匹配效率。其中的关键之一是`next`数组的构建过程。以下是关于如何计算`next`数组的具体方法。 #### `next`数组的概念 `next[i]`表示模式串中以第`i`个字符结尾的子串的最大相同前后缀长度减一[^1]。换句话说,它是当前字符之前的部分中能够重复利用的信息量。例如,在模式串`ababaaababaa`中: | 下标 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | |------|----|----|----|----|----|----|----|----|----|----|----| | 字符 | a | b | a | b | a | a | a | b | a | b | a | a | | next | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | #### 计算`next`数组的过程 为了更清晰地说明`next`数组解方式,可以分为以下几个方面进行解释: ##### 初始化 通常情况下,`next[0]=-1`或者`next[0]=0`取决于具体实现。这里我们采用`next[0]=-1`作为初始条件[^4]。 ##### 遍历模式串并更新`next`值 假设已知`next[j-1]`,则可以通过以下逻辑推导出`next[j]`: - 如果模式串中的`P[j]==P[next[j-1]]`,那么可以直接得出`next[j]=next[j-1]+1`。 - 否则需要回溯到`next[next[j-1]]`的位置继续判断,直到满足条件或回到起始位置为止。 下面是基于C语言的一个典型例子来展示该过程[^3]: ```c void getNext(char *p, int *next) { int j = 0; // 主指针指向当前位置 int k = -1; // 辅助指针用于追踪最大公共前后缀结束位置 next[0] = -1; while (p[j] != '\0') { // 循环直至到达字符串末尾 if (k == -1 || p[j] == p[k]) { ++j; ++k; next[j] = k; // 更新next数组 } else { k = next[k]; // 不匹配时回退至合适位置重新尝试匹配 } } } ``` 以上代码片段展示了如何通过双重循环结构逐步填充整个`next`数组。值得注意的是,当遇到不匹配的情况时,并不会盲目地将索引重置为零,而是借助先前记录下来的数据快速跳转到可能存在的新起点处再次试探是否存在潜在的机会完成进一步扩展操作。 --- #### 实际案例分析 考虑模式串`"ABABCABAA"`: | i/j | A | B | A | B | C | A | B | A | A | |-----|----|----|----|----|----|----|----|----|----| | Next| -1 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 1 | 从表可以看出每当发生失配现象之后都会依据已有信息迅速定位新的候选区域而非单纯依靠逐一遍历来查找目标项所在之处;如此这般便实现了高效检索的目的。 --- ### 总结 综上所述,通过对`next`数组的理解以及其构造机制的学习可以帮助开发者更好地掌握KMP算法精髓所在——即充分利用历史数据避免冗余运算达到加速效果的目标。同时也可以看出合理运用辅助工具如递归函数或是迭代语句均能有效简化复杂度较高的程序设计难题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值