关于KMP算法代码中核心代码j = next[j - 1]的解释

关于KMP算法代码中的核心代码 j = next[j - 1] 的解释

首先,阅读本文需要读者已知KMP算法基本思想,如果并不了解,可以先观看出自尚硅谷韩顺平老师的视频https://www.bilibili.com/video/BV1E4411H73v?p=161,代码也是出自于此,但是韩老师在视频中并未解释代码中的 j=next[j-1] 部分,故本文针对其核心代码 j=next[j-1] 部分进行讲解,该代码用到的编程语言为Java,完整代码如下:

package Algorithm.kmp;

import java.util.Arrays;

public class KMPAlgorithm {
    public static void main(String[] args) {
        String str1 = "BBC ABCDAB ABCDABCDABDE";
        String str2 = "ABCDABD";
        int[] next = kmpNext(str2);
        System.out.println(Arrays.toString(next));

        int index = kmpSerach(str1,str2,next);
        System.out.println(index);

    }
 
    /**
     * KMP匹配算法
     * @param str1 源字符串
     * @param str2 目标字符串
     * @param next 目标字符串的部分匹配值表
     * @return 返回-1则说明没有匹配成功,否则返回第一个匹配的位置
     */
    public static int kmpSerach( String str1, String str2, int[] next ) {
        for ( int i = 0, j = 0; i < str1.length(); i++ ) {
            //KMP算法核心点:处理 str1.charAt(i) != str2.charAt(j)
            while ( j > 0 && str1.charAt(i) != str2.charAt(j) ) {
                j = next[j - 1];
            }

            if ( str1.charAt(i) == str2.charAt(j) ) {
                j++;
            }

            if ( j == str2.length() ) {
                return i - j + 1;
            }
        }
        return -1;
    }
 
    /**
     * 获取到一个子串的部分匹配值表
     * @param dest 目标字符串
     * @return 返回一个整形数组,里面的数字代表当前子串的前缀子串和后缀子串的最长公共串
     */
    public static int[] kmpNext(String dest) {
        //创建一个next数组来保存部分匹配值
        int[] next = new int[dest.length()];

        next[0] = 0;//字符串长度为1时,它的部分匹配值为0

        for ( int i = 1, j = 0; i < dest.length(); i++ ) {

            while ( j > 0 && dest.charAt(i) != dest.charAt(j) ) {
                j = next[j - 1]; //KMP算法的核心
            }

            if ( dest.charAt(i) == dest.charAt(j) ) {
                j++;
            }
            next[i] = j;
        }
        return next;
	}
}

阅读代码可以发现,无论是在求next数组时,还是KMP算法实现时都会用到 j=next[j-1] 这段代码,那么先解释一下这行代码在求next数组时的作用,next数组的作用是储存该字符串所匹配到的子串的最大公共前后缀,即next[j]存储的是该字符串长度为j的子串的最大公共前后缀,并且在求next数组时也用到了KMP算法的核心思想:在进行字符串比较时, 主串的指针 i 不进行回溯,仅对子串的指针 j 进行回溯,并且 j 指针也并不需要每次都从头开始(取决于 n e x t 数组)。 \color{red}{主串的指针i不进行回溯,仅对子串的指针j进行回溯,并且j指针也并不需要每次都从头开始(取决于next数组)。} 主串的指针i不进行回溯,仅对子串的指针j进行回溯,并且j指针也并不需要每次都从头开始(取决于next数组)。以上这点请读者始终牢记。

我们不妨来看这个例子,假设字符串为 AACDAAD ,那么在求其next数组时是其和自身的子串所比较时,如图:
在这里插入图片描述

此时i指针所指元素与j指针所指元素相同,因此,j指针所指元素的next数组值为1,因为此时比较的字符串为 AA ,其最大公共前后缀为1,其值存储到next数组中,然后i、j指针同时后移一位,如图:

在这里插入图片描述

此时发现i指针与j指针所指元素不同,于是要对j指针进行回溯, 但是! \color{red}{但是!} 但是!重点来了,此时我们已经是比较过主串与子串的前两位,并且前两位是相同的,而且前两位的最大公共前后缀为1!这说明 主串的前两位的最后一位与子串的前两位的第一位是一样的 \color{red}{主串的前两位的最后一位与子串的前两位的第一位是一样的} 主串的前两位的最后一位与子串的前两位的第一位是一样的,还有一个点,从代码以及KMP算法原理可以看出,无论比较是否成功,i指针永远会往后移动一位,同时请记住此时 j 指针的值为 2 \color{red}{j指针的值为2} j指针的值为2,于是下次比较时两个指针位置如下图所示:

在这里插入图片描述

我们发现,此时j指针的值为1,而这个1 正是 n e x t [ j − 1 ] 的值, j 移动前的值为 2 ,所以也正是 n e x t [ 1 ] 的值 \color{red}{正是next[j-1]的值,j移动前的值为2,所以也正是next[1]的值} 正是next[j1]的值,j移动前的值为2,所以也正是next[1]的值,因为在比较失败时,已经比较的长度为j,需要对j指针回溯,而next[j-1]储存的是j-1长度的子串的最大公共前后缀, 因为! j − 1 长度的子串的最大公共前后缀为 1 ,它最后一位与第一位一样 \color{red}{因为!j-1长度的子串的最大公共前后缀为1,它最后一位与第一位一样} 因为!j1长度的子串的最大公共前后缀为1,它最后一位与第一位一样,所以省去了上图红线之前的字符的比较,此时有没有有豁然开朗的感觉?没有也关系,我们再来看看这个思想在KMP算法中的体现:

在这里插入图片描述

该图为KMP算法匹配时出现情况,此时主串与子串已经匹配了6位,而i指针所指的字符与j指针所指的第7位字符并不一样,因为需要对j指针进行回溯,那么我们应该观察的是已经匹配的 长度为 6 的子串 A A C D A A \color{red}{长度为6的子串 AACDAA } 长度为6的子串AACDAA,可以发现这个子串的最大公共前后缀为2(也就是此时next[j-1]的值)已经我们可以将j指针回溯到以下位置,如图:

在这里插入图片描述

此时再开始新一轮的比较,而这个回溯的位置正是next[j-1]的值。介绍至此,相信大伙应该都有点感觉了,其实不了解该段代码意义的很大原因,还是对KMP算法本身所需要的一些基础知识了解的不够透彻,因为笔者在学习时也是因为对最大公共前后缀这个概念了解不够,所以才苦思许久,希望这篇文章对大伙有所帮助。
不了解该段代码意义的很大原因,还是对KMP算法本身所需要的一些基础知识了解的不够透彻,因为笔者在学习时也是因为对最大公共前后缀这个概念了解不够,所以才苦思许久,希望这篇文章对大伙有所帮助。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值