KMP算法

本文深入讲解KMP算法原理,包括覆盖函数的概念、计算方法及如何应用于字符串匹配问题,通过实例帮助理解并提供LeetCode题解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考博客:
1. 经典算法研究系列:六、教你初步了解KMP算法、updated
2. KMP算法的前缀next数组最通俗的解释,如果看不懂我也没辙了

字符串的匹配问题

假设文本是一个长度为n的数组T[1…n],模式是一个长度为m<=n的数组P[1….m]。
这里写图片描述

找出所有在文本T=“abcabaabcaabac”中的模式P=“abaa”所有出现。
该模式仅在文本中出现了一次,在位移s=3处。

简单的字符串匹配算法

循环, 对n-m+1个可能的每一个s值检查条件P[1….m]=T[s+1….s+m]。
运行时间为O((n-m+1) * m)

例: 目的字串target是banananobano,要匹配的字串pattern是nano

先将target和pattern串的第一个字符比较,如果相同就重复比较下一位,如果发现不同就将pattern整体右移一位,重新比较
这里写图片描述

时间浪费: 已经比较过的位置要重比一遍。如index =2,3,4,其中index=3的尝试是不必要的。

如果我们事先知道pattern本身的某些性质,可以不用每次匹配失败后都把index回退回去(pattern右移一位),在失配时直接把pattern移动到下一个可能的位置,就可以把根本不可能匹配的过程省略掉,kmp算法就是从此出发。

KMP算法

覆盖函数(overlay_function)

覆盖的定义
对序列a0a1…aj,找最大的k,使它满足
a0a1…ak=aj-kaj-k+1…aj,
即找到尽可能大的k,使pattern 前k字符与后k字符相匹配

覆盖函数含义
覆盖函数所表征的是pattern本身的性质,即pattern从左开始的所有连续子串的自我覆盖程度
比如如下的字串,abaabcaba
这里写图片描述
这里计数是从0开始的(也可以从1开始计数),其中-1表示没有覆盖

注意事项
如果选择较小的满足条件的k,那么当失配时,我们就会使pattern向右移动的位置变大,而较少的移动位置是存在匹配的,就会把可能匹配的结果丢失。
比如下面的序列,
这里写图片描述
在红色部分失配,正确的结果是k=1的情况,把pattern右移4位,如果选择k=0,右移5位则丢失了k=1的可能匹配结果。

计算
如果前一个字符i-1的next值是j,那么我们就把当前字符i与字串第j+1个字符进行比较

  1. 相等 当前字符i的next值为j+1, 即next(i) = j + 1 = next(i-1) + 1
  2. 不相等

    1. 前一个字符i-1的next值是0, 则当前字符的next值也为0
    2. 前一个字符i-1的next值不为0,则在前j个字符中找到h=next[j],如果pattern(h+1)==pattern(i),则next(i)=h,否则重复该过程

      如(a g c t a g c )( a g c t a g c) t, 到最后一个c时next值为7,到t时不匹配需要重新求next值

      这里写图片描述

      t如果存在对称性,那么对称程度肯定比前面的c的对称程度(next值)小,要找更小的对称,必然在对称内部还存在子对称,而且这个t必须紧接着在子对称之后。如图所示
      这里写图片描述

      子对称存在的证明: 将两个agctagc分别设为T1,T2,有T1 = T2,假设存在更小的对称,T11 = T22,则T1中存在T12 = T22, T2中存在T21 = T11,则T11 = T12, T21 = T22, 说明必然有子对称。

      故这里定位到T1末尾的c(index为j = 7),得知其next值为h = 3,且pattern(h+1) == pattern(i) == ‘t’,故当前字符t的next值为4。

示例代码如下,这里为从1开始计数

private int[] makeNext(String arr){
        int len = arr.length();
        int[] next = new int[len];
        for(int i = 1, j = 0; i < len;){
            if(arr.charAt(i) == arr.charAt(j))  next[i++] = ++j;
            else if(j != 0)   j = next[j-1];
            else next[i++] = 0;
        }
        return next;
    }

如”abaabcaba”的返回结果是0,0,1,1,2,0,1,2,3

KMP

当失配发生在j时,不用把index向回移动,index前面已经匹配过的部分在pattern自身就能体现出来,只要把pattern向右移动j-overlay(j)长度( 相当于将pattern_index改为overlay(j) )就可以了

如果失配时pattern_index==0,相当于pattern第一个字符就不匹配,这时应该把target_index加1,pattern向右移动1位。

这里写图片描述
老图了,有画错的地方,index=8和index=11处不应跳过,敬请见谅。

实战

Leetcode 28. Implement strStr()

Description
Implement strStr().
Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
(找出needle在haystack中最早出现的index,没有返回-1)

Solution

public class Solution {
    public int strStr(String haystack, String needle) {
        int m = haystack.length(), n = needle.length();
        if(n == 0)    return 0;
        int[] next = makeNext(needle);
        for (int i = 0, j = 0; i < m; ) {
            if (haystack.charAt(i) == needle.charAt(j)) { 
                i++;
                j++;
                //字串匹配成功
                if (j == n) return i - j;
            }
            else{
                //pattern向右移动
                if(j > 0)   j = next[j-1];
                //第一个字符就不匹配
                else    i++;
            }
        }
        return -1;
    }
    //计算覆盖函数
    private int[] makeNext(String arr){
        int len = arr.length();
        int[] next = new int[len];
        for(int i = 1, j = 0; i < len;){
            if(arr.charAt(i) == arr.charAt(j))  next[i++] = ++j;
            else if(j != 0)   j = next[j-1];
            else next[i++] = 0;
        }
        return next;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值