首页 > 编程语言 >KMP算法

KMP算法

时间:2022-11-12 10:24:10浏览次数:46  
标签:int pattern length next 算法 str KMP

KMP算法是一个字符串匹配算法,或者说是一个子字符串匹配算法

算法在字符串str中搜索子串pattern,如果存在,返回这个子串的起始索引,如果不存在,返回-1

暴力枚举匹配

暴力的字符串匹配算法非常简单,就是枚举所有可能的起始索引 i,对于每一个索引,都通过与pattern逐个字符地进行比较来检查是否匹配,代码如下:

static int matchString(String str, String pattern) {
  for (int i = 0; i <= str.length() - pattern.length(); ++i) {
      for (int j = 0; j < pattern.length(); ++j) {
          if (str.charAt(i + j) != pattern.charAt(j)) {
              break;
          }
          if (j == pattern.length() - 1) {
              return i;
          }
      }
  }
  return -1;
}

显而易见,这种算法在一些比较极端的情况下的运行情况非常不理想

如 str = "aaaabcacbab",pattern = "aaaac" 时

在 i = 0 时,i 和 j 增加到4发现不匹配
        i
a a a a b c b a b
a a a a c
        j

接着 i 回退到 1,又经过了3次比较发现不匹配
        i
a a a a b c b a b
  a a a a c
        j

显然做了很多冗余的比较,而KMP就是一种对于这些极端情况也能够高效处理的算法

什么是next数组

在上面的例子中,如果是人进行模拟,在 i = 0 的4次比较发现不匹配后,显然没有必要将 i 回退到1,j 回退到0再开始比较,而是可以不回退i,直接从 j = 3 开始比较。

这实际上是由于在第一次的比较中,我们得到了 str[0 ~ 3] = pattern[0 ~ 3] 的信息。因此,不需要 i 再回退到1去依次访问和比较 str[1],str[2],str[3],而可以保持 i 不动,通过 pattern 自身的性质让 j 进行跳转,继续进行比较

而对于每一个 j,当出现 str[i] != pattern[j] 时,j 进行跳转后的下一个位置就是 next[j]

如何计算 next 数组

首先根据上面的定义,i 和 j 满足 str[i - j ~ i - 1] = pattern[0 ~ j - 1], 将 next[j] 暂时记为 k,i 和 k 要满足 str[i - k ~ i] = pattern[0 ~ k - 1]

这样一看,j 和 k 岂不是相等的?那是因为还少了几个约束。首先,由于 i 和 j 取旧的值时已经确定不能匹配,因此 k(即新的 j)的值不能和j相等。而如果有 k > j 满足 str[i - k ~ i] = pattern[0 ~ k -1],在之前一定被处理过。因此,k 一定会小于 j。并且,为了不漏过可能的子串,k 应该是满足条件的最大值

那么,根据 str[i - j ~ i - 1] = pattern[0 ~ j - 1],可以得到 str[i - k ~ i - 1] = pattern[j - k ~ j - 1]

再根据 str[i - k ~ i - 1] = pattern[0 ~ k - 1],得到 pattern[0 ~ k - 1] = pattern[j - k ~ j - 1]

也就是说,对于每一个 j,k = next[j] 是满足 pattern[0 ~ k - 1] = pattern[j - k ~ j - 1]的最大的k,直观来描述,就是在j之前的串中,前 k 个位置和后 k 个位置相同,k的最大值

我们把在一个字符串中前 k 个位置和后 k 个位置相同,k的最大值记为字符串的 X 长度,相同的前 k 个位置和后 k 个位置记为 X 前缀和 X 后缀

要求满足条件的 k,就只需要考虑pattern了,首先考虑 k 和 next[j - 1]的关系,t = next[j - 1]是满足 pattern[0 ~ t - 1] = pattern[j - 1 - t ~ j - 2] 的最大值,就是j - 1之前的串的 X 长度,显然,当 pattern[j - 1] = pattern[t] 时,k = t + 1

那么当 pattern[j - 1] != pattern[t]的时候呢,可以通过向前迭代 t 来继续进行比较,令 t = next[t],如果 pattern[j - 1] = pattern[t] 时,k = t + 1,如果不等,则继续迭代,令 t = next[t]

上面的迭代为什么是正确的呢,因为当 pattern[j - 1] != pattern[t] 时,我们考虑 j - 1 之前的串的 X 后缀的 X 前缀和 X 后缀,j - 1之前的串的 X 前缀的 X 前缀和 j - 1 之前的串的 X 后缀的X 后缀是相等的,又因为 X 的最长特性,显然是下一个检查的对象,也就是令 t = next[t] 进行迭代,这样的迭代可以通过递归来考虑,终止条件就是当 t 迭代到 0 的时候

X 前缀
--------- t           j - 1
XXX***XXX   XXX***XXX
            ---------
            X 后缀
//X 前缀中***部分第一个位置即为 next[t]

结合上面的算法,考虑一些边界情况,最终写出代码如下:

static int matchString(String str, String pattern) {
  if (str.length() < pattern.length() || str.isEmpty()) {
      return -1;
  }
  int[] next = new int[pattern.length()];
  //计算next数组的值
  next[0] = -1;
  for (int i = 1; i < next.length; ++i) {
      int k = i - 1;
      while (k > 0 && pattern.charAt(i - 1) != pattern.charAt(next[k])) {
          k = next[k];
      }
      next[i] = next[k] + 1;
  }
  //进行匹配
  int i = 0, j = 0;
  while (i < str.length()) {
      while (i < str.length() && str.charAt(i) == pattern.charAt(j)) {
          i++;
          j++;
          if (j == pattern.length()) {
              return i - j;
          }
      }
      j = next[j];
      //终止条件,j 需要移动到 pattern 的第一个位置,那么 i 就需要向后移动一位
      if (j < 0) {
          j = 0;
          i++;
      }
  }
  return -1;
}

标签:int,pattern,length,next,算法,str,KMP
From: https://www.cnblogs.com/tea-in-the-snow/p/16882798.html

相关文章

  • 基于prim算法的网络最小生成树生成得到路径规划
    目录​​一、理论基础​​​​二、核心程序​​​​三、测试结果​​一、理论基础    普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由......
  • 每日算法题之扑克牌顺子
    JZ61扑克牌顺子描述现在有2副扑克牌,从扑克牌中随机五张扑克牌,我们需要来判断一下是不是顺子。有如下规则:1.A为1,J为11,Q为12,K为13,A不能视为142.大、小王为0,0可以看......
  • 回溯算法解数独问题
    好久没写算法了,浅解个数独本篇代码以伪代码为主,主要讲解解题思路规则介绍:首先数独的游戏规则,每个九宫格每一行每一列每个数字只能出现一次(1-9)开局时会生成一些不......
  • 基于prim算法的网络最小生成树生成得到路径规划
    目录一、理论基础二、核心程序三、测试结果一、理论基础普里姆算法(Prim算法),图论中的一种算法,可在加权连通图里搜索最小生成树。意即由此算法搜索到的边子集所构成的......
  • 道长的算法笔记:二分图匹配
    二分图的概念二分图又称作二部图,是图论中的一种特殊模型。假设\(G=(V,E)\)是一个无向图,如果顶点\(V\)能够分割为两个互不相交的子集\((S,T)\),并且图中的每条边\((......
  • 献芹奏曝-Python面试题-算法-DFS&BFS
    上一篇:献芹奏曝-Python面试题    开篇的话:本文目的是收集和归纳力扣上的算法题,希望用python语言,竭我所能做到思路最清奇、代码最简洁、方法最广泛、性能最高效,了解......
  • 算法笔记(三):数学问题
    最大公约数辗转相除法intgcd(inta,intb){ if(b==0)returna; returngcd(b,a%b);}最小公倍数根据最大公约数得出最小公倍数步骤:先求得a与b的最大公......
  • 算法笔记(二):知识点补充
    万能头文件#include<bits/stdc++.h>数组最大范围int型一维数组:小于4e8,即4亿int型二维数组:小于2e4,即2万数据类型范围int和long都是用32位来存储最大值和最小值分......
  • Floyd算法的关键点
    Floyd算法的代码很简单,就是三个for这个算法最重要的地方就是中转点的枚举for(intk=1;k<=n;k++)for(inti=1;i<=n;i++)for(intj=1;j<=n;j++)g[i][j]=max(g[i][j],g[i......
  • 旋转门数据压缩算法在PostgreSQL中的实现 - 流式压缩在物联网、监控、传感器等场景的
      背景在物联网、监控、传感器、金融等应用领域,数据在时间维度上流式的产生,而且数据量非常庞大。例如我们经常看到的性能监控视图,就是很多点在时间维度上描绘的曲线......