KMP算法理解

KMP主要应用在字符串匹配上。
其他废话不多说,本文主要在他人对KMP算法讲解的基础上加入我自己的想法,便于我自己理解,同时也希望能帮助大家更好理解KMP算法。如果哪里有问题,还望评论指正。
示例:
文本串:aabaabaafa
模式串:aabaaf
暴力法:
在这里插入图片描述
而KMP算法通过前缀表可以做到这样:
在这里插入图片描述
比暴力法快很多
那么前缀表怎么构建呢

前缀表

一.前缀和后缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
例如:aabaaf
在这里插入图片描述
这里的最长相同前后缀长度即可构成前缀表,而next数组也可以就是前缀表
在这里插入图片描述

但这里是我们手动计算的,如何用代码来实现呢?

二.next数组——实现思路

具体可以分为以下几步:
1.初始化
2.前后缀相同的情况
3.前后缀不相同的情况

  • 先来解决第一步:初始化
    定义两个指针 i 和 j,j指向前缀末尾位置,i指向后缀末尾位置。然后还要对next数组进行初始化赋值,如下:
int j = 0;
next[0] = 0;

j 从位置0开始,i 从位置1开始,并需要一直走到模式串结束,不断比较 s[i] 与 s[j] ,因此外循环为:

for(int i=1;i<s.length();i++){
}
  • 接下来解决第二步:前后缀相同的情况
    如果 s[ i ] 与 s[ j + 1] 相同,说明找到了最长相同前后缀,因此需要同时向后移动 i 和 j ,i 在下一次循环中会+1,而 j 在此处就++的原因是,因为next 数组中记录的是相同前后缀的长度(一定要记住),而 j 本身表示的是前缀结尾的下标,因此长度是 j+1,即 next[ i ] = j+1;而先 j++的话,next[ i ] = j 就可以了。
if (s[i] == s[j]) { // 找到相同的前后缀
    j++;
}
next[i] = j;
  • 最后解决第三步:前后缀不相同的情况
    若是暴力法,s[i] != s[j] 不相同,j 直接退为0了,但KMP算法需要利用前缀表不断尝试,尽量退到还有前缀可用的位置,万不得已才退到0。
while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
    j = next[j-1]; // 向前回退
}

这里面的 j = next[ j-1] 我用了很长时间理解
假设这样一种情况:
在这里插入图片描述

当 j 走到 c,i 走到 b 的时候,出现了不相等的情况,这时红圈中的两坨内容是相同的,我们需要在这串前缀中找到尽可能长的子前缀能复用,因此 j 从前缀的尾部的next[]开始判断(即 next[ j-1])。
当找到 s[i] == s[j] 时,可以保证前缀从0到 j-1和后缀从 i 往前数 j 个(不包括 i)一定是相同的,并且可以直接复用第二步的过程。而 s[i] != s[j] 时,还可以继续往前跳,不管怎么跳,从0到 j-1 的前缀内容都等于从i往前数j个的后缀内容。

因为:
笼统来看,下图黑色这两坨内容是相同的,假设第一跳后 s[i] 仍不等于 s[j],j 跳到了 e 的位置,这时说明从0到 j-1(即0到f)与从 c 向前数 j 个(不包括 c)是相同的,因此,图中红色四坨是相同的;再跳一次,这回 j 跳到了 b ,此时 s[ i ] 等于 s[ j ] 了,根据前面的逻辑,图中的八坨黑色圈是相同的。因此next[ i ]= j+1,同样可以复用相同前后缀的 if 代码,先让 j++,再赋值。
在这里插入图片描述

三.构造next数组完整代码

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

完整题解

文本串与模式串比较也同样,如果相同,则都++,不同就需要向前找 j 的位置。

class Solution {
    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(haystack.charAt(i) == needle.charAt(j)){
                j++;
            }
            if(j==needle.length()){
                return i-j+1;
            }
        }
        return -1;
    }
    public void getNext(int[] next,String needle){
        int j = 0;
        next[0] = 0;
        for(int i=1;i<needle.length();i++){
            while(j>0 && needle.charAt(j) != needle.charAt(i)){
                j = next[j-1];
            }
            if(needle.charAt(i)==needle.charAt(j)){
                j++;
            }
            next[i] = j;
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值