C++刷题 -- KMP算法

C++刷题 – KMP算法

文章目录

  • C++刷题 -- KMP算法
    • 1.算法讲解
    • 2.算法实现


https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/

1.算法讲解

KMP算法是一种字符串匹配算法,当出现字符串不匹配时,可以记录一部分之前已经匹配的文本内容,利用这些信息避免从头再去做匹配;

前缀表(prefix table):
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。

  • 举一个例子:
    要在文本串:aabaabaafa 中查找是否出现过一个模式串:aabaaf。请添加图片描述
    可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,发现不匹配,此时就要从头匹配了。
    但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配,找到了模式串中第三个字符b继续开始匹配。

  • 前缀表是如何记录的:
    首先要知道前缀表的任务是当前位置匹配失败,找到之前已经匹配上的位置,再重新匹配,此也意味着在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置
    前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀

  • 最长公共前后缀:
    字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串
    后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
    前缀表要求的就是相同前后缀的长度;
    字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2。 等等…

  • 为什么一定要用前缀表
    刚刚匹配的过程在下标5的地方遇到不匹配,模式串是指向f,如图:请添加图片描述
    然后就找到了下标2,指向b,继续匹配:如图
    请添加图片描述
    下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面重新匹配就可以了。
    所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。

  • 如何计算前缀表
    如图:
    请添加图片描述
    长度为前1个字符的子串a,最长相同前后缀的长度为0。(注意字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。)
    请添加图片描述
    长度为前2个字符的子串aa,最长相同前后缀的长度为1;
    请添加图片描述
    长度为前3个字符的子串aab,最长相同前后缀的长度为0
    以此类推: 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。
    那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
    请添加图片描述
    可以看出模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀
    再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:
    请添加图片描述
    找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少,因为要找前面字符串的最长相同的前缀和后缀
    前一个字符的前缀表的数值是2, 所以把下标移动到下标2的位置继续比配。
    最后就在文本串中找到了和模式串匹配的子串了。

  • 前缀表与next数组
    很多KMP算法的实现都是使用next数组来做回退操作,那么next数组与前缀表有什么关系呢?
    next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。
    其实这并不涉及到KMP的原理,而是具体实现,next数组既可以就是前缀表,也可以是前缀表统一减一(右移一位,初始位置为-1)

2.算法实现

  • 使用next数组来匹配
    以下我们以前缀表统一减一之后的next数组来做演示。
    有了next数组,就可以根据next数组来 匹配文本串s,和模式串t了。
    注意next数组是新前缀表(旧前缀表统一减一了)。
    匹配过程动画如下请添加图片描述

  • 时间复杂度分析
    其中n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m)。所以整个KMP算法的时间复杂度是O(n+m)的。
    暴力的解法显而易见是O(n × m),所以KMP在字符串匹配中极大地提高了搜索的效率。
    为了和力扣题目28.实现strStr保持一致,方便大家理解,以下文章统称haystack为文本串, needle为模式串

  • 构造next数组
    定义一个函数getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串。
    在这里插入图片描述
    构造next数组其实就是计算模式串s,前缀表的过程。 主要有如下三步:

    1. 初始化:
      定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置
      然后还要对next数组进行初始化赋值,如下:
      在这里插入图片描述
      j 为什么要初始化为 -1呢,因为之前说过 前缀表要统一减一的操作仅仅是其中的一种实现,我们这里选择j初始化为-1,下文我还会给出j不初始化为-1的实现代码。
      next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)
      所以初始化next[0] = j 。

    2. 处理前后缀不相同的情况:
      因为j初始化为-1,那么i就从1开始,进行s[i] 与 s[j+1]的比较
      所以遍历模式串s的循环下标i 要从 1开始,代码如下:
      在这里插入图片描述
      如果 s[i] 与 s[j+1]不相同,也就是遇到 前后缀末尾不相同的情况,就要向前回退。
      怎么回退呢?
      next[j]就是记录着j(包括j)之前的子串的相同前后缀的长度。
      那么 s[i] 与 s[j+1] 不相同,就要找 j+1前一个元素在next数组里的值(就是next[j])。
      在这里插入图片描述
      每次求的都是当前字符串开头到i之间的子串的最大公共前后缀
      因此j指向子串的一个前缀末尾位置,i指向子串的一个后缀末尾位置
      例如:
      在这里插入图片描述
      上图中判断的是aab这个子串的前后缀,显然没有相同的前后缀,因此next[i]为0;
      在这里插入图片描述
      上图判断的是aabaa这个子串的前后缀,j+1指向的是当前的前缀aa的末尾,i指向的是当前的后缀aa的末尾,显然前后缀末尾是一致的,因此next[i] = j = 1;

    3. 处理前后缀相同的情况:
      如果 s[i] 与 s[j + 1] 相同,那么就同时向后移动i 和j 说明找到了相同的前后缀,同时还要将j(前缀的长度)赋给next[i], 因为next[i]要记录相同前后缀的长度。
      在这里插入图片描述

构建next数组的逻辑:
请添加图片描述

最后整体构建next数组的函数代码如下:

void getNext(int* next, const string& s){int j = -1;next[0] = j;for(int i = 1; i < s.size(); i++) { // 注意i从1开始while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了j = next[j]; // 向前回退}if (s[i] == s[j + 1]) { // 找到相同的前后缀j++;}next[i] = j; // 将j(前缀的长度)赋给next[i]}
}
  • 使用next数组来做匹配
    在文本串s里 找是否出现过模式串t。
    定义两个下标j 指向模式串起始位置i指向文本串起始位置
    那么j初始值依然为-1,为什么呢? 依然因为next数组里记录的起始位置为-1。
    i就从0开始,遍历文本串,代码如下:
    在这里插入图片描述
    接下来就是 s[i] 与 t[j + 1] (因为j从-1开始的) 进行比较。
    如果 s[i] 与 t[j + 1] 不相同,j就要从next数组里寻找下一个匹配的位置。
    代码如下:
    在这里插入图片描述
    如果 s[i] 与 t[j + 1] 相同,那么i 和 j 同时向后移动, 代码如下:
    在这里插入图片描述
    如何判断在文本串s里出现了模式串t呢,如果j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了。
    本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
    那么使用next数组,用模式串匹配文本串的整体代码如下:
int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始while(j >= 0 && s[i] != t[j + 1]) { // 不匹配j = next[j]; // j 寻找之前匹配的位置}if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动j++; // i的增加在for循环里}if (j == (t.size() - 1) ) { // 文本串s里出现了模式串treturn (i - t.size() + 1);}
}

KMP算法的整体代码:

class Solution {
public:void getNext(int* next, const string& s) {int j = -1;next[0] = j;for(int i = 1; i < s.size(); i++) { // 注意i从1开始while (j >= 0 && s[i] != s[j + 1]) { // 前后缀不相同了j = next[j]; // 向前回退}if (s[i] == s[j + 1]) { // 找到相同的前后缀j++;}next[i] = j; // 将j(前缀的长度)赋给next[i]}}int strStr(string haystack, string needle) {if (needle.size() == 0) {return 0;}int next[needle.size()];getNext(next, needle);int j = -1; // // 因为next数组里记录的起始位置为-1for (int i = 0; i < haystack.size(); i++) { // 注意i就从0开始while(j >= 0 && haystack[i] != needle[j + 1]) { // 不匹配j = next[j]; // j 寻找之前匹配的位置}if (haystack[i] == needle[j + 1]) { // 匹配,j和i同时向后移动j++; // i的增加在for循环里}if (j == (needle.size() - 1) ) { // 文本串s里出现了模式串treturn (i - needle.size() + 1);}}return -1;}
};
  • 时间复杂度: O(n + m)
  • 空间复杂度: O(m), 只需要保存字符串needle的前缀表

前缀表不减1

class Solution {
public:void getNext(int* next, const string& s){int j = 0;//字符串当前检查的前缀的末尾next[0] = j;//初始化for(int i = 1; i < s.size(); i++){//前后缀两个子串都是从大小为1的子串开始检查的//如果前缀后缀末尾不一致,就对j进行回退,看回退后能否匹配while(j > 0 && s[i] != s[j]){j = next[j - 1];//next中记录着已经检查到的相同前后缀的长度}//如果前后缀末尾一致,就对j++if(s[i] == s[j]){j++;}//当出现一直的前后缀末尾,就需要在next表中记录当前的相同前后缀长度next[i] = j;}}int strStr(string haystack, string needle) {if(needle.size() == 0){return -1;}int next[needle.size()];getNext(next, needle);int j = 0; // 指向needle的指针for(int i = 0; i < haystack.size(); i++){//判断当前两指针所指字符是否相等,不相等就回退jwhile(j > 0 && haystack[i] != needle[j]){j = next[j - 1];}//如果一致,就对j++if(haystack[i] == needle[j]){j++;}//如果j遍历到了needle的末尾,就说明存在子串if(j == needle.size()){return i - needle.size() + 1;}}return -1;}
};

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

求奇数的和 C语言xdoj147

题目描述&#xff1a;计算给定一组整数中奇数的和&#xff0c;直到遇到0时结束。 输入格式&#xff1a;共一行&#xff0c;输入一组整数&#xff0c;以空格分隔 输出格式&#xff1a;输出一个整数 示例&#xff1a; 输入&#xff1a;1 2 3 4 5 0 6 7 输出&#xff1a;9 #inclu…

线上环境如何正确配置 Django 的 DEBUG?

Author&#xff1a;rab Django Version&#xff1a;3.2 Python Version&#xff1a;3.9 目录 前言一、DEBUG True二、DEBUG False三、页面异常解决总结 前言 由于最近在学习 Django 的知识&#xff0c;于是尝试开发了一套 Blog 系统&#xff0c;在本地测试时是页面显示没问题…

Ubuntu安装ARM交叉编译器

Ubuntu安装交叉编译器 更新apt # 更新apt sudo apt update安装gcc sudo apt install build-essential查看gcc版本 gcc -v下载交叉编译工具 复制到用户目录 解压 tar -xvf gcc-linaro-5.5.0-2017.10-x86_64_arm-linux-gnueabihf.tar.xz移动到/opt/下 sudo ./gcc-linaro-5.…

Java项目-瑞吉外卖项目优化Day1

创建新仓库 push项目 新建分支v1.0做优化 导入Redis相关配置 导入坐标。 实现配置类&#xff0c;重写序列化器&#xff0c;也可以直接用StringRedisTemplate。 application.xml配置&#xff1a; 实现缓存短信验证码 将手机号与验证码存进redis。 从redis中获取验证码&…

JMESPath语言

JMESPath&#xff08;JSON Matching Expression Path&#xff09; 一种查询语言。 主要用于从JSON文档中检索和过滤数据。 通过写表达式提取和处理JSON数据&#xff0c;而无需编写复杂的代码。 功能&#xff1a;数据提取、过滤、转换、排序。 场景&#xff1a;处理API响应…

手撕分布式缓存---HTTP Client搭建

经过上个章节的学习&#xff0c;我们已经实现了一致性哈希算法&#xff0c;这个算法保证我们可以在节点发生变动时&#xff0c;最少的key请求受到影响&#xff0c;并返回这个节点的名称&#xff1b;这很大程度上避免了哈希雪崩和哈希穿透的问题。这个章节我们要基于此实现完整的…

docker小白第六天

docker小白第六天 容器数据卷是什么 首先&#xff0c;容器卷有个坑&#xff1a;容器卷需要加入privilegedtrue&#xff0c;如下图所示&#xff0c;是为了解决permission denied的问题。其中“挂载”的意思是相当于一个硬盘插到主机上。使用该命令。是扩大容器的权限解决挂载目…

PyQt6 QSpacerItem弹簧控件

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计46条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

二维码搭建意见建议反馈留言系统

传统纸质投诉建议需要用笔进行填写,反馈意见费时费力,相关单位收到留言反馈意见后也无法进行直接进行回复。 将投诉建议的记录单制作成二维码,放在公告栏等位置,用户可以扫描二维码随时随地进行反馈,常见的有评价表、投诉建议表、反馈意见表、留言表等,也就是通过扫码实现对用…

2023 英特尔On技术创新大会直播 |让更多人了解AI魅力

2023 英特尔On技术创新大会直播 |让更多人了解AI魅力 前言&#xff1a;主要领域:人工智能&#xff1a;使用 OpenVINO™ 落地边缘端生成式 AIOpenVINO™学习总结&#xff1a; 新一代 AI PC计算平台&#xff1a;新一代至强平台&#xff1a;边云协同&#xff1a;先进技术&#xff…

一款电压检测LVD

一、基本概述 The TX61C series devices are a set of three terminal low power voltage detectors implemented in CMOS technology. Each voltage detector in the series detects a particular fixed voltage ranging from 0.9V to 5.0V. The voltage detectors consist…

HTML_有哪些字体样式及使用

文章目录 &#x1f431;‍&#x1f409;一、字体样式的基本概念&#xff1a;&#x1f431;‍&#x1f409;二、css字体样式属性有&#xff1a;&#x1f923;1、设置字体类型&#xff08;font-family&#xff09;&#x1f923;2、设置字体大小&#xff08;font-size&#xff09;…