##角色
你是一个精通计算机算法的大学老师
#背景
一名大学生正在学习KMP算法
#任务
解答该学生的疑惑
##疑惑
该学生不明白j = b[j - 1];中为什么是”j - 1“
//构建next数组的过程
//len2是模式字符串的长度
//s2是模式串
int j = 0;
for(int i = 1 ; i < len2 ; i++){
        while(j > 0 && s2[j] != s2[i]) {
            j = b[j - 1];
        }
        if(s2[j] == s2[i]) j++;
        b[i] = j;
    }

为什么是"j = b[j - 1]"


1. 基本概念回顾

  • b 数组(Next数组)的定义
    b[i] 的值是:在模式串 s2 中,以 i 这个位置结尾的子串(即 s2[0...i])中,最长的相等前后缀的长度。

  • i 和 j 的含义

    • i:当前正在计算的 b[i] 的索引。它指向后缀的最后一个字符。

    • j:它有两个作用,而且这两个作用紧密相关:

      1. 指向前缀的最后一个字符j 指向的位置,意味着我们已经匹配了长度为 j 的前缀。例如,当 j=2 时,表示前缀 s2[0] 和 s2[1] 已经匹配成功,现在正在检查 s2[2] 是否能和 s2[i] 匹配。

      2. 代表  当前  最长相等前后缀的长度(当前:指的是匹配到s[i - 1]时,能够匹配s[0]...s[i-1]的众多前后缀个数中最长的那一个)

结论:在比较 s2[i] 和 s2[j] 之前,j 的值就是已经匹配好的前缀长度。


2. 为什么发生失配(while 循环)

代码的执行逻辑是这样的:
我们想要求 b[i],也就是找 s2[0...i] 这个串的最长相等前后缀。我们利用已有的 b[0] 到 b[i-1] 的信息。

  1. 初始状态:我们希望在 s2[0...i-1] 这个串的某个前缀后面,加上 s2[i],如果能和 s2[i] 匹配上,就大功告成。j 此刻正指向这个可能的前缀的末尾。

  2. 检查匹配:我们检查 s2[j](前缀的下一个字符)和 s2[i](当前字符)是否相等。

  3. 失配发生:如果 s2[j] != s2[i],就说明长度为 j 的前缀后面接上 s2[i] 这条路走不通。我们不能再直接用这个长度为 j 的前缀了

3. 核心:失配后怎么办?—— j = b[j-1]

找一个更短的前后缀,再尝试能否接上它。

  • b[j-1] 里存的是什么?
    "j - 1" 时长度为  j  的最长前后缀(即 s2[0...j-1]
    b[j-1]就是s2[0...j-1]中的最长前后缀

    相当于:长度为J的不能用,再找一个第二长的前后缀

  • 所以,j = b[j-1] 的逻辑是:

    1. 当前长度为 j 的前缀匹配失败。

    2. 我们退而求其次,在已经匹配好的这个前缀 s2[0...j-1] 里,找到它的最长相等前缀(其长度就是 b[j-1])。

    3. 把这个更短的前缀的长度作为新的 j

    4. j 现在指向了这个新前缀的末尾,我们再次进入 while 循环,检查这个新前缀的下一个字符 s2[j] 是否和 s2[i] 相等。


4. 举例说明

假设模式串 s2 是 "ABACABAB",我们在计算到 i=7(最后一个 'B')时卡住了。

  • 此时 j 是多少?在进入 i=7 的循环前,j=3(因为 b[6] 的值是3,表示 "ABACABA" 有长度为3的相等前后缀 "ABA")。j 指向 s2[3],也就是 'C'

  • 第1次匹配s2[j] 是 'C's2[i] 是 'B''C' != 'B',失配!我们需要缩小 j

  • 为什么是 j-1
    我们需要在 s2[0...j-1] 也就是 s2[0...2](即 "ABA")里找它的最长相等前后缀。

    • b[j-1] = b[2]b[2] 是多少?对于 "ABA",最长相等前后缀是 "A",长度为1。所以 b[2] = 1

    • 因此,新的 j 变为 1。j 现在指向 s2[1],也就是 'B'

  • 第2次匹配s2[j] 现在是 'B's2[i] 是 'B''B' == 'B',匹配成功!退出 while 循环。

  • 更新 j 和 b[i]j++j 变为2。b[7] = 2。表示 "ABACABAB" 的最长相等前后缀长度是2("AB")。

看,我们正是通过 j = b[2],从长度为3的前缀 "ABA" 回退到了长度为1的前缀 "A",然后成功扩展到了 "AB"。这个 b[2] 中的索引 2,正是 j-1

总结

  • j 代表当前尝试匹配的前缀的长度。

  • 当 s2[j] 和 s2[i] 不匹配时,我们不能直接从 j 跳到0,那样效率太低。

  • 我们利用已经计算好的 b 数组,在 s2[0...j-1](即已匹配的前缀) 中寻找它的最长相等前后缀。这个最长相等前后缀的长度就存储在 b[j-1] 里。

  • 所以 j = b[j-1] 的含义是:将当前尝试匹配的前缀,缩短为“已匹配前缀的最长相等前缀”,然后继续尝试匹配。

这样,我们既没有从头开始,又利用了之前匹配的结果,这正是KMP算法高效的精髓所在。希望这个解释能帮助你理解!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐