关于KMP算法的个人理解

145 阅读2分钟

KMP算法

KMP主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
关键点就在对于next数组的理解。
next数组可以等同于前缀表,前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。 前缀表主要记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
文章中字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串

1711678824747.png 模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。 1711678882966.png 对于KMP算法的理解,关键在于对next数组的理解,在进行字符串匹配时,对于发生冲突的一位,我们要看模式串不匹配的前一个位置的next数组索引!
通俗来说,next数组位置i的值,就是指的i之前包括i的字符串中,最大相同前后缀的个数,同时也代表着不同的开始索引。
如何通过代码求next数组,主要分为4步:

  1. 初始化:
    定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置。j同时代表当前位置的next值。 next[0]一定是0,因为没有前后缀,j=0,i=1。
  2. 处理前后缀不相同的情况
    因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较。 所以遍历模式串s的循环下标i 要从 1开始,代码如下:
for (int i = 1; i < s.size(); i++) {

如果前后缀不同的话,前缀就要回退,回退到哪里呢,j-1嘛?

  1. 处理前后缀相同的情况

如果 s[i] 与 s[j] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
4. 赋值next数组。

next[i] = j;
class Solution {
    //前缀表(不减一)Java实现
    public int strStr(String haystack, String needle) {
        if (needle.length() == 0) return 0;
        int[] next = new int[needle.length()];
        getNext(next, needle);

        int j = 0;
        for (int i = 0; i < haystack.length(); i++) {
            while (j > 0 && needle.charAt(j) != haystack.charAt(i)) 
                j = next[j - 1];
            if (needle.charAt(j) == haystack.charAt(i)) 
                j++;
            if (j == needle.length()) 
                return i - needle.length() + 1;
        }
        return -1;

    }
    
    private void getNext(int[] next, String s) {
        int j = 0;
        next[0] = 0;
        for (int i = 1; i < s.length(); i++) {
            while (j > 0 && s.charAt(j) != s.charAt(i)) 
                j = next[j - 1];
            if (s.charAt(j) == s.charAt(i)) 
                j++;
            next[i] = j; 
        }
    }
}