字符串 | 字符串匹配之 KMP 算法以及 C++ 代码实现

news2024/11/19 8:37:02

目录

    • 1 为什么使用 KMP?
    • 2 什么是 next 数组?
      • 2.1 什么是字符串的前后缀?
      • 2.2 如何计算 next 数组?
    • 3 KMP 部分的算法
    • 4 完整代码


😈前言:这篇文章比较长,但我感觉自己是讲明白了的



1 为什么使用 KMP?

答:参与字符串匹配的两个串分别叫 “主串” 和 “模式串”。在朴素字符串匹配中,若主串 s[i] 不等于模式串 p[j],那么需要主串和模式串都回溯到开头,模式串要从主串的下一个字符开始重新匹配。KMP 算法之所以被提出,是因为它不需要主串回溯,且只需要模式串部分回溯,节省了很多不必要也不可能成功的匹配操作。



2 什么是 next 数组?

next 数组是 KMP 算法中的一个辅助工具,我们只需要知道它是什么,以及它是怎么被计算出来的即可。

  • 定义:next 数组的值代表的是字符串的前缀与后缀相同的最大长度。


2.1 什么是字符串的前后缀?

前缀和后缀的定义:

  • 前缀:除最后一个字符以外的,字符串的所有头部子串;
  • 后缀:除第一个字符以外的,字符串的所有尾部子串;

强调:不管是前缀还是后缀,字符都是按从左往右的顺序数的!

假设有如下的字符串:

在这里插入图片描述

  • 设 next[0] = -1;
  • “A” 的前缀和后缀都为空集,共有元素的长度为 0,设 next[1] = 0;
  • “AB” 的前缀为 [A],后缀为 [B],共有元素的长度为 0,设 next[2] = 0;
  • “ABA” 的前缀为 [A, AB],后缀为 [BA, A],共有元素的长度为 1,设 next[3] = 1;
  • “ABAC” 的前缀为 [A, AB, ABA],后缀为 [BAC, AC, C],共有元素的长度为 0,设 next[4] = 1;
  • 以此类推

说明:next[0] = -1 是 KMP 算法的特殊需求,到时候帮助模式串回溯到开头。

上述字符串的 next 数组如下:

在这里插入图片描述

可以看出,字符 “D” 对应的 next 值是 0,即字符串 “ABAC” 的前缀与后缀相同的最大长度。

我主要想强调的是,每个字符对应的 next 值,是该字符之前的字符串的前缀与后缀相同的最大长度,并不包含该字符。



2.2 如何计算 next 数组?

直接上代码:

vector<int> getNextArr(string p) {
  vector<int> next(p.size(), 0);

  int i = -1, j = 0;
  next[0] = -1;

  while (j < p.size() - 1) {
    if (i == -1 || p[i] == p[j]) {
      ++i;
      ++j;
      next[j] = i;
    } else {
      i = next[i];
    }
  }

  return next;
}

我们很自然地想到使用 i 和 j 一前一后两个指针来完成字符串的匹配,因此定义 i = -1 和 j = 0 作为初始值。

① 情况一:当 i = -1 时

说明匹配还没有开始,因为 i 还没有指向任何字符。因此我们让 i 和 j 均右移一格,分别指向两个字符。根据 2.1 节介绍的规则,因为由一个字符构成的字符串是没有前后缀的,所以 next 的值为 0:

next[j] = i;  // j=1, i=0

② 情况二:当 p[i] == p[j] 时

说明 i 和 j 指向的字符相同。因此我们也让 i 和 j 均右移一格,分别指向两个新的字符。同时,使用以下代码计算 next 的值:

next[j] = i;

说明:不管是情况一还是情况二,i 的值其实都代表的是已经匹配了的前后缀长度。因为只有匹配了,i 才会向右移嘛!

③ 情况三:最恶心的 else 情况

说明匹配已经开始了,但 i 和 j 指向的字符不同。比如,下图所代表的时刻:

在这里插入图片描述

其中,绿色部分和黄色部分代表的是 “ABACDABA” 这个字符串中成功匹配的部分。

虽然在这种情况下匹配失败了,但是我们可以考虑比该情况更差的情况。也就是说,字符串 “ABACDABA” 中匹配的长度是 3,但如果我们再纳入 j 指向的字符 “B”,是没有办法再续辉煌的!即没有办法在已匹配部分 “ABA” 的基础上再加 1!

但是我们可以考虑字符串 “ABACDABA” 中匹配长度为 2 的部分,看看该部分是否有机会在加上 “B” 后仍然匹配。

我来具体讲一下上述思路,可以参考下图:

在这里插入图片描述

其中 ① 是最理想的情况,即最长匹配部分 “ABA” 分别加上 i 指向的字符和 j 指向的字符后仍然匹配,但显然在上述例子中是不匹配的。那么我们接着考虑匹配长度比 3 小的情况。情况 ② 是根本不会被考虑的,因为前缀 “AB” 和后缀 “BA” 根本不匹配。再来看情况 ③,前缀 “A” 和后缀 “A” 匹配,同时 i 指向的字符和 j 指向的字符相同。因此,我们捡漏到了一种匹配的情况!

刚才我们有提到 “情况 ② 是根本不会被考虑的”,那么 KMP 算法具体是如何规避的呢?核心是如下代码:

i = next[i];

最初,我觉得简直无法理解,但经过上述分析后我恍然大悟!请看下图:

在这里插入图片描述

针对情况 ①,一个重要的信息是绿色部分和黄色部分是完全相同的!我们可以把 “找两个 ‘ABA’ 之间更短的匹配部分” 转换为 “找一个 ‘ABA’ 中的匹配部分”!又由于绿色部分的匹配长度在此前已经被计算出了,等于 next[i] 的值,即 “C” 前面的字符串 “ABA” 中前后缀的最大匹配长度。因此,我们可以直接让 i 指针飞到 next[i] 处,即从情况 ① 飞到情况 ③:

在这里插入图片描述

飞到情况 ③ 后,前缀 “A” 和后缀 “A” 是匹配的,原因我们刚才已经分析过了。接下来就看 “A” 分别加上 i 指向的字符和 j 指向的字符后是否仍然匹配了。

以上就是对计算 next 数组的代码的全部分析!



3 KMP 部分的算法

直接上代码:

int kmp(string s, string p) {
  vector<int> next = getNextArr(p);

  int n = s.size();
  int m = p.size();

  int i = 0, j = 0;
  while (i < n && j < m) {
    if (j == -1 || s[i] == p[j]) {
      ++i;
      ++j;
    } else {
      j = next[j];
    }
  }

  if (j == m) {
    return i - m;
  }

  return -1;
}

其中 i 是主串 s 的指针,j 是模式串 p 的指针。

① 情况一:当 s[i] == p[j] 时

说明 i 和 j 指向的字符相同。因此我们也让 i 和 j 均右移一格,分别指向两个新的字符。

这是最简单易懂的情况。

② 情况二:else 情况

说明 i 和 j 指向的字符不同,但还有拯救的希望。参见下图的例子:

在这里插入图片描述

先看情况 ①,其中的绿色部分和黄色部分(深浅部分都包括)代表主串和模式串匹配的部分。特别地,深色部分代表主串和模式串自己内部的匹配部分。如上图所示,i 和 j 指向的字符不同,如果是朴素字符串匹配算法,那么两个指针都得回到开头以重新进行匹配。而在 KMP 算法中,主串不需要回溯,而模式串也只需要回溯一部分。

与 2.2 节的分析相同,既然我们没有办法让 “CACCA” 分别加上 i 和 j 指向的字符后仍然相同,那么我们可以考虑一个比它短的部分,即绿色部分的一个后缀。当然这个后缀不能乱选,我们还是得有一点依据。这个依据就是:绿色部分的后缀要和黄色部分的前缀一样!否则将无法匹配。

一个重要的信息是绿色部分和黄色部分是完全相同的!我们可以把 “找匹配的绿色部分后缀和和黄色部分前缀” 转换为 “找匹配的黄色部分后缀和和黄色部分前缀”!又由于我们先前为模式串计算了 next 数组,因此这是非常容易得到的!核心代码如下:

j = next[j];

参见情况 ①,next[j] 代表 j 指向的字符 “E” 前面的字符串 “CACCA” 中的前后缀匹配长度,同时代表满足要求的前缀的长度。因此,我们让 j 移动到 next[j],如情况 ② 所示。然后判断 “CA” 分别加上 i 和 j 指向的字符后是否仍然相同。

③ 情况三:当 j == -1 时

说明 i 和 j 指向的字符不同,且没有拯救的希望。具体来说,出现了多次情况二,导致 j 回溯到了 -1 位置。在这种情况下,i 和 j 均需要右移一格,以指向两个新的字符进行比较。

在情况二中,i 是不会移动的;在情况三中,由于 i 不移动就不会有匹配的希望,因此 i 也需要移动。



4 完整代码

#include <bits/stdc++.h>

using namespace std;

vector<int> getNextArr(string p) {
  vector<int> next(p.size(), 0);

  int i = -1, j = 0;
  next[0] = -1;

  while (j < p.size() - 1) {
    if (i == -1 || p[i] == p[j]) {
      ++i;
      ++j;
      next[j] = i;
    } else {
      i = next[i];
    }
  }

  return next;
}

int kmp(string s, string p) {
  vector<int> next = getNextArr(p);

  int n = s.size();
  int m = p.size();

  int i = 0, j = 0;
  while (i < n && j < m) {
    if (j == -1 || s[i] == p[j]) {
      ++i;
      ++j;
    } else {
      j = next[j];
    }
  }

  if (j == m) {
    return i - m;
  }

  return -1;
}

int main() {
  string s = "aaaaa";
  string p = "bba";
  vector<int> next = getNextArr(p);
  
  for (auto & n : next) {
    cout << n << ' ';
  }

  cout << kmp(s, p);

  return 0;
}


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1720727.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue之组件基础(插槽)

在HTML中&#xff0c;开发者可以在双标签内添加一些信息。而在Vue中&#xff0c;组件以标签的形式引用&#xff0c;那么如何在组件的标签内添加一些信息并将信息渲染到页面中呢?其实&#xff0c;Vue 提供了插槽&#xff0c;专门用来实现这样的效果。 一.什么是插槽 Vue为组件…

怎么使用Python代码在图片里面加文字

在Python中&#xff0c;给图片添加文字可以使用Pillow库&#xff08;PIL的一个分支&#xff09;&#xff0c;它是一个强大的图像处理库。如果你还没有安装Pillow&#xff0c;可以通过pip安装&#xff1a; pip install Pillow下面使用一个简单的示例&#xff0c;演示如何使用Pi…

系统架构设计师【第9章】: 软件可靠性基础知识 (核心总结)

文章目录 9.1 软件可靠性基本概念9.1.1 软件可靠性定义9.1.2 软件可靠性的定量描述9.1.3 可靠性目标9.1.4 可靠性测试的意义9.1.5 广义的可靠性测试与狭义的可靠性测试 9.2 软件可靠性建模9.2.1 影响软件可靠性的因素9.2.2 软件可靠性的建模方法9.2.3 软件的可靠性模…

CISCN 2023 初赛 被加密的生产流量

题目附件给了 modbus.pcap 存在多个协议 但是这道题多半是 考 modbus 会发现 每次的 Query 末尾的两个字符 存在规律 猜测是base家族 可以尝试提取流量中的数据 其中Word Count字段中的22871 是10进制转16进制在转ascii字符串 先提取 过滤器判断字段 tshark -r modbus.pcap …

java线程状态介绍

1.新建&#xff08;New&#xff09;: 线程对象已创建&#xff0c;但还没有调用 start() 方法。 2.可运行&#xff08;Runnable&#xff09;: 线程已启动&#xff0c;处于就绪状态&#xff0c;等待 JVM 的线程调度器分配CPU时间。 3.阻塞&#xff08;Blocked&#xff09;: 线程…

自动化桌面整理新时代:Llama 3驱动的智能文件管理系统

在信息爆炸的时代,个人和企业用户的电脑桌面常常被海量文件占据,导致查找特定文件如同大海捞针。为了解决这一痛点,Llama 3应运而生——一个集成了先进多模态AI技术的智能文件管家,旨在将杂乱无章的文件世界变得井然有序。本文将深入探讨Llama 3如何利用其创新功能,不仅自…

上传RKP 证书签名请求息上传到 Google 的后端服务器

上传证书签名请求 1.准备环境&#xff1a;OK pip3 install google-auth2.13.0 requests2.28下载 device_info_uploader.py 。 没找到先跳过 选项 1&#xff1a;通过 GCP 帐户使用 device_info_uploader.py 运行脚本。 ./device_info_uploader.py --credentials /secure/s…

简要分析学习spring内存马,劫持马

简要分析学习spring内存马&#xff0c;劫持马 本文主要是通过SpringMemShell这个工程&#xff0c;来对spring内存马进行演示&#xff0c;利用。 写在前面&#xff1a; 参考的是大佬给的流程以及思路,其中的解释与分析非常详细 ----->>大佬的链接 这里的内存马文件取自gi…

python采集汽车价格数据

python采集汽车价格数据 一、项目简介二、完整代码一、项目简介 本次数据采集的目标是车主之家汽车价格数据,采集的流程包括寻找数据接口、发送请求获取响应、解析数据和持久化存储,先来看一下数据情况,完整代码附后: 二、完整代码 #输入请求页面url #返回html文档 imp…

Scikit-Learn 基础教程

目录 &#x1f40b;Scikit-Learn 基础教程 &#x1f40b;Scikit-Learn 简介 &#x1f40b; 数据预处理 &#x1f988;数据集导入 &#x1f988;数据清洗 &#x1f988;特征选择 &#x1f988;特征标准化 &#x1f40b; 模型选择 &#x1f988;分类模型 &#x1f988;回…

Resilience4j结合微服务出现的异常

Resilience4j结合微服务出现的异常 1、retry未生效 由于支持aop&#xff0c;所以要引入aop的依赖。 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>2、circ…

解决:【无法安装“vue.volar“扩展,因为它与当前 VIsual Studio Code 版本不兼容(版本 1.80.0)】

目录 问题复现问题分析解决步骤1、进入VSCode插件市场&#xff0c;搜索Vue.volar2、点击搜索结果&#xff0c;进入详情页面3、下载.vsix文件完成后&#xff0c;用解压软件打开 4、复制package.json文件&#xff0c;修改vscode版本5、保存package.json文件&#xff0c;并更新.v…

2024 HN CTF WebMisc 部分 wp

Web ez_tp 判断是thinkphp 3.2 参考官方手册:https://www.kancloud.cn/manual/thinkphp/1697 判断路由模式 URL_CASE_INSENSITIVE > true, // 默认false 表示URL区分大小写 true则表示不区分大小写URL_MODEL > 1, // URL访问模式,可选参数0、1、…

cURL error 60: SSL certificate problem: unable to get local issuer certifica

本地小程序把接口换到本地的服务器接口&#xff0c;然后就报错了&#xff1a; cURL error 60: SSL certificate problem: unable to get local issuer certificate (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) 经查询查到&#xff1a;此问题的出现是由于没有配…

第八十九周周报

学习目标&#xff1a; 论文 学习时间&#xff1a; 2024.05.25-2024.05.31 学习产出&#xff1a; 一、论文 SAN: INDUCING METRIZABILITY OF GAN WITH DISCRIMINATIVE NORMALIZED LINEAR LAYER 将GAN与切片最优输运联系起来&#xff0c;提出满足方向最优性、可分离性和单射…

数据资产价值如何评估?一文详解如何构建数据资产评估的综合框架

数据资产&#xff0c;如同其构成的数据本身&#xff0c;具备物理、存在和信息三重属性。数据资产的物理属性体现在其对存储空间的占用&#xff1b;其存在属性则体现在数据的可读取性上&#xff0c;若数据无法读取&#xff0c;则其作为资产的价值便无从体现。这两个属性共同构成…

LeetCode 算法: 字母异位词分组c++

原题链接&#x1f517;&#xff1a;字母异位词分组 难度&#xff1a;中等⭐️⭐️ 题目 给你一个字符串数组&#xff0c;请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。 字母异位词 是由重新排列源单词的所有字母得到的一个新单词。 示例 1: 输入: strs [“e…

centos7.9离线安装mysql5.7

centos7.9离线安装mysql5.7 查询mysql查询组查询用户不存在创建即可&#xff0c;创建mysql用户组上传下载的安装包创建my.cnf文件修改配置文件修改mysql登陆密码 centos7.9 mysql5.7 查询mysql rpm -qa | grep mysql我这里查询是不存在&#xff0c;如果你的存在可以用rm -rf […

云原生架构相关技术_4.服务网格

1.技术特点 服务网格&#xff08;ServiceMesh&#xff09;是分布式应用在微服务软件架构之上发展起来的新技术&#xff0c;旨在将那些微服务间的连接、安全、流量控制和可观测等通用功能下沉为平台基础设施&#xff0c;实现应用与平台基础设施的解耦。这个解耦意味着开发者无需…

ROS2在RVIZ2中加载机器人urdf模型

参考ROS2-rviz2显示模型 我这边用的solid works生成的urdf以及meshes&#xff0c;比参考的方法多了meshes 问题一&#xff1a;Error retrieving file [package://rm_dcr_description/meshes/leftarm_link7.STL]: Package [rm_dcr_description] does not exist 这个是urdf模型中…