【夜深人静学数据结构与算法 | 第一篇】KMP算法

news2024/11/17 3:23:54

目录

 前言:

 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/634586.html

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

相关文章

理解和创建Windows和Linux下的动态和静态库区别

一、引言 在计算机编程的世界中&#xff0c;库是一个非常重要的改变。它的出现提供了一种共享和重用代码的可能性&#xff0c;复杂的程序因为动态库的出现而变得简洁和方便。然而&#xff0c;库并不是单一的&#xff1a;它们可以是动态的&#xff0c;也可以是静态的&#xff0…

达梦数据库的下载与安装(Linux)

一、创建用户组 1、创建一个用户组和用户 添加分组 groupadd dinstall添加用户 useradd -g dinstall dmdba设置用户名和密码 echo "dameng123" | passwd --stdin dmdba查看操作系统中id为 dmdba 的用户的用户ID&#xff08;uid&#xff09;、组ID&#xff08;gi…

web漏洞-逻辑越权之水平垂直越权全解(33)

他是业务逻辑层面&#xff0c;和一些业务方便应用的安全问题&#xff0c;这个是因为代码层面没用考虑到的逻辑关系所造成的安全问题&#xff0c;越权是其中一个比较关键的问题。登录是指在登录这里出现了安全问题&#xff0c;业务等等今天只说越权。 越权漏洞 分为水平和垂直…

容器镜像按层分析工具dive

概述 dive是一个容器镜像分析工具&#xff0c;可以直观的看到容器每一层变动了哪些文件&#xff0c;每一层占用的磁盘空间&#xff0c;这样也就可以看到镜像的历史构建过程&#xff1b;同时也可以看到镜像的磁盘空间使用率&#xff0c;面对特别大的镜像文件是&#xff0c;可以…

【论文随笔】Rewrite-Based Decomposition of Signal Temporal Logic Specifications

文章目录 Overview1 IntroLTL任务分解STL任务分解本文工作 Background and Problem DefinitionSTLAgent假设与问题方法 An STL Rewriting SystemRewriting SystemFormula Rewrite DAG Decomposing STL智能体编队任务分解最优分解 Exploring the Formula Rewrite DAG心得体会 多…

如何创建可引导的 macOS 安装介质

如何创建可引导的 macOS 安装介质 如何创建可引导的 macOS 安装器 | 如何制作 macOS USB 启动盘 请访问原文链接&#xff1a;https://sysin.org/blog/macos-createinstallmedia/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xff1a;sysin.or…

asp.net卷烟物价管理系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio

一、源码特点 asp.net卷烟物价管理系统 是一套完善的web设计管理系统&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为vs2010&#xff0c;数据库为sqlserver2008&#xff0c;使用c#语言开发 asp.net卷烟物价管理系统VS开发sq…

清华青年AI自强作业hw3_2:前向传播和反向传播实战

清华青年AI自强作业hw3_2&#xff1a;前向传播和反向传播实战 实现过程各层参数维度分析拟合结果相关链接 一起学AI系列博客&#xff1a;目录索引 前向传播和反向传播的公式理解化用于作业hw3_2中&#xff1a;&#xff1a;用NN网络拟合小姐姐喜好分类 完成前向传播、反向传播算…

【JavaEE进阶之Spring】一分钟让你学会什么是Spring以及如何使用创建Spring

前言&#xff1a; &#x1f49e;&#x1f49e;今天我们正式进入到JavaEE进阶的学习中了&#xff0c;在JavaEE进阶的学习中&#xff0c;我们最主要的就是学习Spring框架。 &#x1f49f;&#x1f49f;那我们从今天就要逐渐从最基础的Spring开始&#xff0c;教会大家什么是Spring…

54、基于51单片机饮水机温度水位控制无线蓝牙APP控制报警系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+开题报告+元器件清单等)

方案的选择 方案一&#xff1a;采用51单片机作为控制核心&#xff0c;配合无线蓝牙模块、水温加热模块继电器开关、基于Dallas单线数字式的DS18B20温度传感器模块、蜂鸣器报警模块、按键模块、LCD1602液晶显示器模块、晶振电路模块、复位电路模块以及电源模块为一体构成无线水…

winsw使用——将Nginx和Jar包注册到WIN服务

文章目录 1.winsw介绍2.注册Nginx到win服务2.1 首先将下载的winsw下并改名2.2 nginx-service.exe.config配置2.3 nginx-service.xml配置2.4 nginx-service安装到服务 3.注册Jar包到win服务3.1 复制winsw文件并改名3.2 创建xml配置文件3.3 执行安装命令 1.winsw介绍 Windows Se…

ChatGPT Prompt Engineering for Developers from DeepLearning.AI

链接&#xff1a;https://learn.deeplearning.ai/chatgpt-prompt-eng/lesson/1/introduction In this course, there are some example codes that you can already run in Jupyter Notebook. Below, I will write down the core knowledge points (how to build a prompt and…

CSS基础学习--4 创建式样

一、插入样式表的几种方法&#xff1f; 外部样式表内部样式表内联样式 二、外部样式表 使用前提&#xff1a;当样式需要应用于很多页面时&#xff0c;外部样式表将是理想的选择。 在使用外部样式表的情况下&#xff0c;你可以通过改变一个文件来改变整个站点的外观。每个页…

Ognl使用总结

目录 一、简介二、快速入门三、详细使用3.0 Ognl操作3.1 基本数据类型3.2 对象类型3.3 List集合3.4 Set集合3.5 Map集合3.6 数组3.7 静态调用3.8 算术运算3.9 逻辑运算3.10 同时执行多个表达式3.11 位运算 一、简介 OGNL&#xff08;Object-Graph Navigation Language的简称&a…

开始使用chat-gpt4

目录 一、说明 二、安装步骤 三、测试效果咋样 &#xff08;1&#xff09;写代码能力 &#xff08;2&#xff09;回答问题能力 &#xff08;3&#xff09;写作能力 一、说明 参考&#xff08;非常感谢这位博主的分享&#xff09;&#xff1a;http://t.csdn.cn/qypw9 注意&…

FTP服务器项目

文章目录 1. 项目简介2. FTP协议和用到指令说明2.1 FTP协议详解2.2 指令说明 3. FTP项目的类图分析3.1 UML3.2 工厂类XFtpFactoryXFtpFactory.hXFtpFactory.cpp 2.2 XFtpTaskXFtpTask.hXFtpTask.cpp 2.3 XFtpServerCMDXFtpServerCMD.hXFtpServerCMD.cpp 4. 运行演示FileZilla的…

数字逻辑期末必刷卷(基础卷)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 &#x1f4a1;一、填空题&#xff08;每空1分&#xff0c;共20分&#xff09;&#x1f4a1;二、单项选择题&#xff08;每小题2分&#xff0c;共20分&#xff09;&a…

第七章 Linux实际操作——组管理和权限管理

第七章 Linux实际操作——组管理和权限管理 7.1 Linux组基本介绍7.2 文件、目录 所有者7.2.1 查看文件的所有者7.2.2 修改文件所有者 7.3 组的创建7.3.1 基本指令7.3.2 应用实例 7.4 文件、目录所在组7.4.1 查看文件、目录所在组7.4.2 修改文件、目录所在组 7.5 其他组7.6 权限…

观澜南林輋旧改回迁房--周边巨量旧改,未来区政府核心商圈。

项目亮点 观澜福城街道办旧改最集中的区域&#xff0c;且地铁4号的茜坑站就在门口&#xff01;未来一区域成为龙华区政府的中心地段。本项目拆迁约10万&#xff0c;主打高端商业综合体&#xff0c;项目规划27班九年一贯性学校&#xff0c;约4万多平用于建设公共设施、绿地。 …

ROS学习——通信机制(服务通信)

2.2.3 服务通信自定义srv调用A(C) Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 068服务通信(C)3_客户端优化_Chapter2-ROS通信机制_哔哩哔哩_bilibili 一、理论模型 服务通信也是ROS中一种极其常用的通信模式&#xff0c;服务通信是基于请求响应模式的&#xf…