我理解的KMP算法next数组推导详解

目录

一、引入

二、问题解决

推导思路

重点

注意点和一些小结论

完整代码


一、引入

KMP算法作为经典的字符串匹配算法,确实很难理解,我尽量解释清楚我的理解。

因为该算法大体上可分为两块(kmp的原理,next数组的推导),该博客将详细分析next的数组的推导。(ps:大约有两种next数组的写法,我使用的是next[x]数组的值是包括x的最大前后缀

例:模式串 abcab next数组:00012)

二、问题解决

相信来看的兄弟都是对kmp的原理有理解的,我就先直接放代码,再根据代码讲解:

//needlee是模式串,next是我们在该函数内要完成的next数组
getNext(string needle, vector<int> &next) {
  	int n = needle.size();

    next[0] = 0;//不必要,为了清晰写出
    for(int j = 0, i = 1;i < n;i++) {
        while(j != 0 && needle[j] != needle[i]) {
            j = next[j - 1];//难点
        }
        if(needle[j] == needle[i]) {
            j++;
        }
        next[i] = j;
    }
}

推导思路

对于next数组的推导,我将其分为几个步骤:

1. j 初始化为0,i 初始化为 1

2. 若needle[i] == needle[j],则此时的next数组值为 j + 1,然后 i j 一起向后移动一格

3. 若needle[i] != needle[j],则 j 变为上一个元素对应next数组中的值除非 j == 0(该过程是循环的)

重点

1. j 的逻辑含义: 在 j 对应的字符前已经有 j 个元素 与 i 对应的字符的前 j 个元素相等

例:对于模式串:aabaabaaa的next数组推导过程中,中有一个 i j 状态如下图所示:

此时,j == 5, 即在 j 对应的 b 之前已经有 aabaa 和 i 对应的 a 之前的 aabaa 相同了

j 有一种储存器的感觉,如果读者有心自己推一下,会发现 j 在该字符串中是1 2 3 4 5递增的

即:a == a => aa == aa => aab == aab => aaba == aaba => aabaa == aabaa

2. 为什么needle[i] != needle[j]时就要 j 变为上一个元素对应next数组中的值?

注意 j 的逻辑意义,此时的不相等说明了 j "储存"的 5 个元素数量太多,不能满足条件,所以需要进行缩减,那么缩减到哪里呢? 自然需要相等的尽可能长的前缀与尽可能长的后缀。

那么问题就转化为了 "尽可能长的相等前后缀在哪里?" 以下是我的理解以及证明

还看上方这个例子,在该情况下 i 与 j 对应的值不等,根据上方规则 j 要移动到前面的 a 对应的next 值即 数组下标为 2 位置,因为此时 j == 5 即在 j 对应的字符前已经有 5 个元素与 i 对应的元素的前 5 个元素相等,也即 aabaa① == aabaa②

注意此时的 对应next为 2 的 a 它的意义是 对于 aabaa①  有最长公共前后缀是 aa 和 aa 

所以后缀的 aa 可以对应到 ② 的后缀 aa 所以此时就有最长的 ① 的前缀aa 与 ② 的后缀 aa 相等。

注意点和一些小结论

1. 不要把next数组的值和模式串的下标值弄混

2. j 一般而言不靠近 i 且在模式串的前侧徘徊(这可以帮你理解 j 的逻辑意义)

3. j 的值就是next数组的下标值。

4. 在 j == 0 时 若 j i 对应的值不匹配 那么结果是 i 向后一格。

5. 自己多推,多debug 总能理解的。

完整代码

class Solution {
public:
    void getNext(string needle, vector<int> &next) {
        int n = needle.size();

        next[0] = 0;
        
        for(int j = 0, i = 1;i < n;i++) {
            while(j != 0 && needle[j] != needle[i]) {
                j = next[j - 1];
            }
            if(needle[j] == needle[i]) {
                j++;
            }
            next[i] = j;
        }
    }

    int strStr(string haystack, string needle) {
        int m = haystack.size(), n = needle.size();//m为主串长度 n为模式串长度

        if(n == 0) return 0;

        vector<int> next(n);

        getNext(needle, next);//初始化next数组
    
        for(int i = 0, j = 0;i < m;i++) {
            while (j != 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if(j == n) {//结束条件
                return i - n + 1; 
            }
        }
        return -1;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值