字符串匹配算法—KMP算法,对KMP算法重点的理解,KMP中的next数组该如何求解。Java代码实现KMP算法

KMP算法是什么,我就不在此赘述了。如下的文章主要记录了我在学习KMP算法中的一些重点内容,包括next数组该怎么求解,该如何用代码来实现求解next算法的这一过程。对于一些不好理解的地方,我找了一些图片,自己也绘制了相应的图示便于理解。


如果完全不了解KMP算法,可以先参见:

本文参考:如何借助BM算法轻松理解KMP算法?

KMP算法初探

当模式串与主串发生不匹配的时候,我们试着一次性多滑动几位。而不是又从头开始比较。比如下面的e、c发生不匹配了。

在这里插入图片描述
凭借肉眼,我们决定应该这么移动,然后主串和模式串继续开始比较。
在这里插入图片描述
对应到算法中,我们无法在程序中也这样滑动模式串去进行比较。而是用两个指针去进行比较。
比如这样:
在这里插入图片描述
或者是这样:
在这里插入图片描述

然后,在继续下面的内容之前,需要先了解一下前缀子串和后缀子串
在这里插入图片描述

求解next数组

用代码实现KMP算法的时候,我们不可能像凭借肉眼的观察那样,得知到发生失配时,应该从头开始比较,还是从某个位置继续开始比较。

因此我们需要借助一个next数组,来告诉程序当发生失配时,应该怎么做。

那next[i]究竟表示什么呢?

当在模式串的index = i的位置与主串发生了失配时,就可以跳转到next[index - 1] + 1的位置开始继续比较,而不是从头开始。

求next数组。计算模式串每个前缀的最长可匹配前缀。思路:列举出该子串的所有前缀子串和所有后缀子串,从中找出最长的公共子串。比如下图所示,找到了ababa这个前缀子串的最长可匹配前缀为aba
举例:
在这里插入图片描述
如果在这之后发生了失配,就可以将模式串一次性移动5 - length(aba) = 2格,再与主串进行比较。
在这里插入图片描述
对应到程序代码里,我们应该这样进行移动,将j指针进行回溯,而不是滑动模式串。
在这里插入图片描述

当出现失配时,比如上图,我们已经成功匹配了ababac但是在模式串的c的这个位置,却匹配失败了。我们已经计算出了ababa的最长可以匹配前缀长度为3,也就是aba这个字符串,然后指针j回溯5 - 3 = 2个单位。

然后干脆一点,我们用一个next[]数组,直接指示当在index = i这个位置发生失配时,我们查阅next[index - 1]的值来计算应该将j指针回溯到什么位置。比如这里,当index = 5发生失配时,查阅next[ 5 -1] = next[4],next[4]应该等于3。但是一般以-1作为开始,故这个地方习惯写成2。所以next[4] = 2。当在index = 5这个位置发生失配时,查阅next[index - 1]的值将其加一,得到的就是j应该回溯到的位置。

如下是手工计算next的一个过程:

计算ababacd的next数组

说明:

对于a这个前缀子串而言:其没有前缀,也无后缀,所以最长可匹配前缀也就无从谈起。若在第一个字符这里发生了失配。只能从头开始比较。

对于ab这个前缀子串而言:其前缀子串为a,后缀子串为b,无公共元素。若在第二个字符这里发生了失配。也只能从头开始比较。

aba的公共子串为a,发生失配时,从a的一个元素b继续开始比较
在这里插入图片描述

计算出来了next[]数组,又该怎么使用呢?

如果模式串在index = j的位置发生了失配,则查看next[j - 1]的值,通过这个值可以来判断是否需要重新开始比较。

在这里插入图片描述
继续看:
在这里插入图片描述

程序如何求得next数组?

有了next数组以后,当发生失配时,我们知道该怎么做。可见:关键在于next[]数组的计算,我们在程序中如果也需要去一个个的列举每个子串的前缀字串和后缀子串,然后通过比较找出其中最长的子串,如果是这样,时间复杂度就会非常高。

注意:如下所说的子串b[]也就是指模式串。

这段话比较难以理解,的好好读读。

  • 如果 next[i-1]=k-1,也就是说,子串 b[0, k-1]是 b[0, i-1]的最长可匹配前缀子串。如果子串 b[0, k-1]的下一个字符 b[k],与 b[0, i-1]的下一个字符 b[i]匹配,那子串 b[0, k]就是 b[0, i]的最长可匹配前缀子串。所以,next[i]等于 k。

  • 如果 b[0, k-1]的下一字符 b[k]跟 b[0, i-1]的下一个字符 b[i]不相等。

    我们假设 b[0, i]的最长可匹配后缀子串是 b[r, i]。如果我们把最后一个字符去掉,那 b[r, i-1]肯定是 b[0, i-1]的可匹配后缀子串,但不一定是最长可匹配后缀子串。所以,既然 b[0, i-1]最长可匹配后缀子串对应的模式串的前缀子串的下一个字符并不等于 b[i],那么我们就可以考察 b[0, i-1]的次长可匹配后缀子串 b[x, i-1]对应的可匹配前缀子串 b[0, i-1-x]的下一个字符 b[i-x]是否等于 b[i]。如果等于,那 b[x, i]就是 b[0, i]的最长可匹配后缀子串。如何求得 b[0, i-1]的次长可匹配后缀子串呢?次长可匹配后缀子串肯定被包含在最长可匹配后缀子串中,而最长可匹配后缀子串又对应最长可匹配前缀子串 b[0, y]。于是,查找 b[0, i-1]的次长可匹配后缀子串,这个问题就变成,查找 b[0, y]的最长匹配后缀子串的问题了。

在这里插入图片描述
在这里插入图片描述

Java代码实现KMP算法

public class KMP {
  public static int kmp(String mainStr, String subString) {
    if (mainStr == null || subString == null || mainStr.length() == 0 || subString.length() == 0) {
      return -1;
    }
    char[] main = mainStr.toCharArray();
    char[] sub = subString.toCharArray();
    int[] next = getNext(sub);
    int j = 0;
    for (int i = 0; i < mainStr.length(); ++i) {
      while (j > 0 && main[i] != sub[j]) {
        j = next[j] + 1;
      }
      if (main[i] == sub[j]) {
        ++j;
      }
      //如果j==sub.length,说明模式串已经完全匹配上了,返回下标
      if (j == sub.length) {
        return i - j + 1;
      }
    }
    return -1;
  }

  private static int[] getNext(char[] sub) {
    int[] next = new int[sub.length];
    next[0] = -1;
    int k = -1;
    for (int i = 1; i < sub.length; ++i) {
      while (k != -1 && sub[k + 1] != sub[i]) {
        k = next[k];
      }
      if (sub[k + 1] == sub[i]) {
        ++k;
      }
      next[i] = k;
    }
    return next;
  }
}

模式串与主串匹配成功以后,返回的是在主串中第一次匹配成功时的起始下标。
如下所示:
在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值