【夜深人静算法介绍 | 第一篇】KMP算法

news2024/11/19 18:20:50

目录

 前言:

 KMP算法简介:

引入概念:

前缀后缀

前缀表:

简单例子:

暴力遍历:

KMP算法:​

 KMP算法难点:

总结:


 前言:

本篇我们将详细的从理论层面介绍一下什么是KMP算法,相对应的力扣刷题专栏里也会有相对应的习题,欢迎各位前往阅读。

 KMP算法简介:

         KMP算法是一种字符串匹配算法用于在一个文本串中查找某个子串出现的位置。KMP算法的原理是根据模式串的特点,在匹配过程中避免重复匹配已经匹配过的部分。具体来说,KMP算法维护两个指针:i和j,表示当前匹配位置和模式串匹配的起点。当出现不匹配时,通过已匹配部分构建一个next数组,用以确定模式串下一次匹配起点的位置。

        KMP算法的时间复杂度为O(n+m),其中n为文本串长度,m为模式串长度。KMP算法应用广泛,例如在文件查找、模糊查询等领域都有广泛的应用。

KMP算法的本质就是跳过一部分暴力循环下的无效比较,达到节省时间复杂度的目的

引入概念:

前缀后缀

前缀:字符串中包含首字母但是不包含尾字符的所有子串

后缀:字符串中包含尾字母但是不包含首字符的所有子串

举例:

字符串aabaaf的前缀后缀分别有:

前缀后缀
aa

aa

aa
aabbaa
aabaabaa
aabaaabaaf

前缀表:

一个字符串中每一个子串都有自己的前缀和后缀,也就都有自己的最长相等前后缀长度,这些长度组成的一个数组,我们把它叫做前缀表

举例:
字符串aabaaf的前缀表:

子串前缀后缀最长相等前后缀长度
a0
aaaa1
aaba,aab,ab0
aabaa,aa,aaba,ba,aba1
aabaaa,aa,aab,aabaa,aa,baa,abaa2
aabaafa,aa,aab,aaba,aabaaf,af,aaf,baaf,abaaf0

简单例子:

假设我们要在父串aabaabaaf中寻找子串aabaaf

暴力遍历:

正常情况是我们在父串中逐一遍历,父串与子串挨个匹配,直到找到与子串完全一致的为止:
 

错误之后重新比较:
 

 

 最终:

我们可以发现暴力遍历之所以时间复杂度高,是因为只要出错,父指针与子指针就会不断的回溯。第一次出错后,父类指针回溯到1,子类指针回溯到0,重新开始比较,第二次出错父类指针回溯到2,子类指针回溯到0,重新开始比较。如此类推。大量的回溯带来了高昂的时间成本,我们就在想如何才能够精简回溯,于是经过不懈努力,我们创造出了KPM算法。

   KMP算法:

KMP算法采取了自定义i和j的回溯,通过控制i和j的回溯位置来降低回溯的时间成本。

 此时按照暴力遍历的思路,我们是让i等于1,j等于0重新开始第二轮遍历,但是我们KMP算法给出了新的思路:

我们不对已经比较过的字符串进行二次比较,就节省了回溯成本,既然绿色前缀和红色后缀相等,都是aa,那么下一次我们让子类的绿色前缀对齐父类的红色后缀,这样我们就不用回溯i和j,也可以开启新一轮的字符串对比

 

 而不断的循环这种比较方法,直至找到父类中符合要求的子串,就实现了KMP算法下的字符串匹配。

通过这个我们可以总结出来模板:

        每一次我们都找到不匹配字母前面的字符串(例如我们这里aabaaff不匹配,前置字符串就是aabaa)然后找出他的最长相等前缀后缀长度(找出有无符合上述这种红绿组合的),这里的最长相等前缀后缀是2,最后我们让i不回溯,j回退到最长相等前缀后缀位置开始向后匹配。

    而next数组其实就是我们为了方便使用不匹配字母前置的字符串的最长相等前缀后缀长度,我们自己进行提前算出这个字符串的所有子串的最长相等前缀后缀长度,存储在一个数组里面方便直接使用,我们给这个把这个数组叫做next数组

     需要注意的是next数组的版本多种多样,通常会对里面的数据进行各种处理。不过本质存放的都是前缀表,因此我们其实可以不对next数组做任何处理,就让他存放前缀表,依然可以实现KMP算法。

以下这句话,对于理解为什么使用前缀表可以告诉我们匹配失败之后跳到哪里重新匹配 非常重要!

下标5之前这部分的字符串(也就是字符串aabaa)的最长相等的前缀 和 后缀字符串是 子字符串aa ,因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面从新匹配就可以了。

所以前缀表具有告诉我们当前位置匹配失败,跳到之前已经匹配过的地方的能力。

next表的种类:

我们还是以aabaaf举例:

 KMP算法难点:

整个KMP算法都可以看作两部分

  • 内核:前缀表的求解,建立next数组
  • 外壳:利用前缀表控制子字符串的回溯

主要的难点在于:如何求字符串的前缀表 

其实字符串的前缀表数组计算本质上也是在运用KMP算法。

它是把字符串的前缀当作了模式串,把字符的后缀当成了文本串

void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) { // j要保证大于0,因为下面有取j-1作为数组下标的操作
                j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置了
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }

KMP算法完整版:

class Solution {
public:
    void getNext(int* next, const string& s) {
        int j = 0;
        next[0] = 0;
        for(int i = 1; i < s.size(); i++) {
            while (j > 0 && s[i] != s[j]) {
                j = next[j - 1];
            }
            if (s[i] == s[j]) {
                j++;
            }
            next[i] = j;
        }
    }
    int strStr(string haystack, string needle) {
        if (needle.size() == 0) {
            return 0;
        }
        int next[needle.size()];
        getNext(next, needle);
        int j = 0;
        for (int i = 0; i < haystack.size(); i++) {
            while(j > 0 && haystack[i] != needle[j]) {
                j = next[j - 1];
            }
            if (haystack[i] == needle[j]) {
                j++;
            }
            if (j == needle.size() ) {
                return (i - needle.size() + 1);
            }
        }
        return -1;
    }
};

总结:

KMP算法的优点主要包括以下几点:

1. 高效率:KMP算法的时间复杂度为O(n+m),其中n是文本串长度,m是模式串长度,相比暴力匹配算法的时间复杂度O(nm)有很大的提升。

2. 可扩展性:KMP算法不要求文本和模式串的长度一致,因此能够有效地处理不同长度的字符串匹配问题。此外,KMP算法还可以支持多模式匹配,即在一个文本串中查找多个模式串。

3. 避免重复匹配:KMP算法通过计算模式串的next数组来避免重复匹配已经匹配过的部分,从而提高了匹配效率。

4. 空间复杂度低:KMP算法只需要一个长度为m的next数组来存储模式串中最长相同前后缀的长度,空间复杂度相对较低。

5. 实现简单:KMP算法的实现相对简单,易于理解和使用。

总之,KMP算法是一种高效、可扩展、避免重复匹配、空间复杂度低、实现简单的字符串匹配算法,对于字符串匹配问题具有广泛的应用价值。

今天的内容到这里就结束了,感谢大家的阅读。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

 

 

 

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

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

相关文章

Java库Lombok常用注解使用

Lombok已经是很多Java项目最常用的库之一了&#xff0c;我也一直在用&#xff0c;但是仅限于Data、XxxConstructer、Slf4j之类的注解&#xff0c;没有看过其它的注解。 直到前段时间看到别人的代码&#xff0c;使用了一个SneakyThrows注解&#xff0c;搜索了一下&#xff0c;才…

华为OD机试真题 JavaScript 实现【数字涂色】【2022Q4 100分】,附详细解题思路

一、题目描述 疫情过后&#xff0c;希望小学终于又重新开学了&#xff0c;三年二班开学第一天的任务是将后面的黑板报重新制作。 黑板上已经写上了N个正整数&#xff0c;同学们需要给这每个数分别上一种颜色。 为了让黑板报既美观又有学习意义&#xff0c;老师要求同种颜色的…

粘包和半包的解决

粘包产生 public class HelloWordServer {static final Logger log LoggerFactory.getLogger(HelloWordServer.class);public static void main(String[] args) {NioEventLoopGroup boss new NioEventLoopGroup(1);NioEventLoopGroup worker new NioEventLoopGroup();try {…

Java实现微信公众号直接发送参数二维码给用户

文章目录 前言一、参数二维码的作用二、功能实现1. 生成带参数二维码2. 上传二维码图片3. 发送带参数二维码给用户 总结 前言 公众号开发近些年是一个比较热门的方向&#xff0c;今天为大家讲解的是用Java如何实现自动生成二维码图片&#xff0c;有如何把这个和用户信息单独绑…

Python3数据分析与挖掘建模(12)复合分析-相关分析与实现示例

1. 相关分析 1.1 概述 相关分析是一种统计分析方法&#xff0c;用于研究两个或多个变量之间的关系和相互影响程度。它帮助我们了解变量之间的线性关系、趋势和相关程度。 在相关分析中&#xff0c;常用的指标是相关系数&#xff0c;用于衡量两个变量之间的相关程度。最常见的…

linux 定时任务

可以用非root用户创建定时任务 Linux crontab 是用来定期执行程序的命令。 当安装完成操作系统之后&#xff0c;默认便会启动此任务调度命令。 crond 命令每分钟会定期检查是否有要执行的工作&#xff0c;如果有要执行的工作便会自动执行该工作。 注意&#xff1a;新创建的 cro…

怎么查询电脑的登录记录及密码更改情况?

源头是办公室公用的电脑莫名其妙打不开了&#xff0c;问别人也都不知道密码是多少 因为本来就没设密码啊&#xff01;&#xff08;躺倒&#xff09; 甚至已经想好了如果是50万想攻破电脑&#xff0c;被po抓住要怎么花这笔钱了 是我想太多 当然最后也没解决&#xff0c;莫名…

27 getcwd 的调试

前言 同样是一个 很常用的 glibc 库函数 不管是 用户业务代码 还是 很多类库的代码, 基本上都会用到 获取当前路径 不过 我们这里是从 具体的实现 来看一下 测试用例 就是简单的使用了一下 getcwd rootubuntu:~/Desktop/linux/HelloWorld# cat Test04Getcwd.c #inc…

11.DIY可视化-拖拽设计1天搞定主流小程序-小程序首页公告详情页面

小程序首页公告详情页面 本教程均在第一节中项目启动下操作 小程序首页公告详情页面前言一、添加界面,布局1.设定组件样式:数据绑定 二. 新增接口三:绑定公告四.查看效果五.动态参数设置 :之前是指定了公告单条数据2.优化还在那时详情页<p>标签:借助工具查看,清空绑定修改…

【树莓派】树莓派4B镜像安装(使用Raspberry Pi image)

本文主要记录下如何使用Raspberry Pi image 软件进行树莓派镜像进行安装。 官网&#xff1a;Raspberry Pi OS – Raspberry Pi 百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1G7z1Fdvk5Chmhj894WPU3A 提取码&#xff1a;xnzw 一、格式化SD卡 若SD卡存在…

【Linux】按键驱动程序

【Linux】按键驱动程序 前言&#xff1a; 一、按键驱动程序的背景知识 1.1 查询方式 1.2 休眠-唤醒方式 1.3 poll方式 1.4 异步通知 1.5 总结 二、按键驱动程序的框架 三、按键驱动程序实战 3.1 头文件&#xff08;button_drv.h&#xff09; 3.2 驱动程序&#xf…

我的开源实践之路!这一路我遇到的困难和收获总结

Datawhale干货 作者&#xff1a;诸葛子房&#xff0c;Datawhale成员 从参与Apache开源项目&#xff0c;到凭借业务需求独自开发个人开源项目&#xff1b;从项目开源出来无人问津到至今500star&#xff0c;多个企业级用户&#xff0c;在开源过程中&#xff0c;我也从走过低谷&a…

Matplotlib的一些总结

plt.figure(numNone, figsizeNone, dpiNone, facecolorNone, edgecolorNone, frameonTrue) 参数说明&#xff1a; 1.num&#xff1a;图像编码或者名称&#xff0c;数字是编码&#xff0c;字符串是名称 2.figsize&#xff1a;宽和高&#xff0c;单位是英尺 3.dpi&#xff1a;指…

chatgpt赋能python:Python怎么取二进制低三位?

Python怎么取二进制低三位&#xff1f; 在Python编程中&#xff0c;处理位运算是一个非常常见的任务。其中&#xff0c;取二进制低三位也是其中的一项操作。那么&#xff0c;如何实现这个操作呢&#xff1f;本篇文章将为大家介绍Python如何取二进制低三位的方法。 什么是二进…

苹果Vision Pro:虚拟现实走进个人计算机未来

一段时间以来&#xff0c;虚拟现实&#xff08;VR&#xff09;这个概念以其无限的潜力吸引了全世界&#xff0c;用户可以进入身临其境的计算机生成的环境中&#xff0c;这些环境通常模糊了数字和物理世界之间的界线。多年来&#xff0c;VR 技术持续以惊人的速度发展&#xff0c…

软考A计划-系统架构师-学习笔记-第三弹

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

yolov4——你总能在这找到你想要的答案

目录 一&#xff1a;前言 二&#xff1a;一些数据增强的方法 三&#xff1a;自提议 四&#xff1a;dropout 普通的dropout yolov4的dropblock 五&#xff1a;Label smothing 标签平滑 六&#xff1a; GIOU&#xff0c;DIOU&#xff0c;CIOU 七&#xff1a; 对网络结构的…

代码随想录算法训练营第五十五天 | 力扣 392.判断子序列, 115.不同的子序列

392.判断子序列 题目 392. 判断子序列 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace&quo…

Mysql 经典面试题总结

&#x1f353; 简介&#xff1a;java系列技术分享(&#x1f449;持续更新中…&#x1f525;) &#x1f353; 初衷:一起学习、一起进步、坚持不懈 &#x1f353; 如果文章内容有误与您的想法不一致,欢迎大家在评论区指正&#x1f64f; &#x1f353; 希望这篇文章对你有所帮助,欢…

Flask+pyecharts实现电影数据分析可视化

之前有写过pyecharts实现电影数据分析可视化和Djangopyecharts实现电影数据分析可视化&#xff0c;但是综合起来感觉还是有缺陷&#xff0c;所以我使用Flaskpyecharts重新整合一下电影数据可视化。 下面是完成后的截图 这应该就算是可视化大屏了吧 文章目录 代码结构index.cs…