多图解析KMP算法原理

news2025/1/13 10:33:18

KMP是什么

KMP是一种字符串匹配算法,能够判断字符串s2,是否为字符串s1的子串

例如:s1 = "abd123def",s2 = "123",KMP会返回4,代表s2是s1的子串,第一个匹配的下标为3

假设s1的数据规模为M,s2的数据规模为N

如果用暴力做法,由于对于s1中以每个字符开头,都有可能匹配出s2,因此最坏情况下有M次尝试,每次尝试耗时M,时间复杂度为O(M * N)

而KMP算法能做到查找的时间复杂度为O(M)

前缀和后缀串的最长匹配长度

首先定义一个概念:前缀以后缀串的最长匹配长度

为字符串中某个字符前面,不包括前面的整个字符串,相等的最长前缀和最长后缀的长度

以字符串s = “abcabck"的最后一个字符k来说,最长且相等的前后缀为"abc”,长度为3

可以发现当前后缀为4,5时,前后缀不匹配,因此最长的匹配长度为3

12345
前缀aababcabcaabcab
后缀cbcabccabcbcabc
是否相等

在这里插入图片描述
我们把s2(模式串)的每个字符都计算一次前缀以后缀串的最长匹配长度,得到next数组

对于"abcabck"来说,其next数组为:

在这里插入图片描述

其中第一个和第二个字符串的next值,人为规定为-1,0

为什么需要有这个next数组?因此可以让匹配过程加速

匹配

next数组可以在暴力匹配的过程中进行加速

回到一般的匹配情况,假设从s1的i位置,和s2的0位置开始匹配,直到s1的x位置,和s2的y位置发现不等

也就是说s1[i,x-1]s2[0,y-1]是相等的

在这里插入图片描述

如果按照暴力匹配,需要将i往后移一个位置,从s1[i+1]s2[0]开始匹配

但现在有字符串s2的next数组,可以对这个过程进行加速

当next值大于0

假设字符s2[y]有一个最长的前缀和后缀,分别为p1和p2,根据定义,p1等于p2

s1[i,x-1] == s2[0,y-1],因此s1也有一个p1和p2,两者相等,而这4者是相等的:
在这里插入图片描述

此时不用从s1[i+1]位置开始和s2[0]进行匹配,而是从s1中,p2的第一个位置j开始,和s2的0位置开始匹配:
在这里插入图片描述

但由于s1的p2等于s2的p1,因此这一串不用比对,一定相等直接从s1[x]和s2[z]位置开始匹配

这么做其实隐含了一个假设,s1从i+1位置,到j-1位置,都无法作为开头匹配出s2,所以才跳过这些位置,直接从j位置开始匹配

为什么这么假设成立呢?

我们假设s1能够从i+1位置,到j-1位置,可以作为开头匹配出s2,设该位置为k:
在这里插入图片描述

既然可以匹配出整个s2,那一定也可以匹配出从k到x-1这个长度的前缀:
在这里插入图片描述

即上图中s1的np2等于s2的np1

而根据之前的匹配结果,s1的np2等于s2的np2

推出s2的np1等于s2的np2

这个结论和next数组中的信息矛盾了,

根据next[y]的信息,s2的最长匹配前后缀长度为p1的长度

但现在推出来s2[y]有更长的相等前缀后缀,因为np1比p1长,np2比p2长

因此假设假设s1能够从i+1位置,到j-1位置,可以作为开头匹配出s2不成立

这样就可以放心的放弃s1从i+1位置到j-1位置作为开头进行匹配的可能性

可以发现利用next数组的信息后,有两个加速点:

  1. 放弃s1从i+1位置到j-1位置作为开头进行匹配的可能性
  2. 直接从s1[x]和s2[z]位置开始匹配

以上为next值大于0的情况,我们来看看当next值为0,-1时的做法:

当next值等于0

next值为0,即没有任何相等的前缀和后缀的匹配串,此时该如何进行下一步匹配呢?

此时从s1的x位置开始,和s2的0位置进行匹配

在这里插入图片描述

这里隐含了一个前提,即从s1的i位置开始,到x-1值,都无法匹配出完整的s2

还是用反证法证明,这个前提是成立的

假设不成立,即可以匹配出,假设从s1的k位置开始匹配,那一定有s1.p1等于s2.p1,而之前s1和s2是匹配到x,y才不相等的,因此s1.p2 等于s2.p2,推出s2.p1 等于 s2.p2

和前提s2[y]的next值为0相矛盾了,因此假设不成立,前提成立,即从s1的i位置开始,到x-1值,都无法匹配出完整的s2

那只好从s1[x]开始匹配s2的[0]

在这里插入图片描述

还有一种next值为0的情况,即人为规定第2个字符的next值为0

当s1[x]和s2[1]不等时,此时是从s1[x-1]和s2[0]开始,无法匹配出整个字符串

那么接下来就从s1[x]开始和s2[0]进行匹配就好了,这里和暴力解法一样,不跳过任何字符

当next值等于-1

next值为-1只有一种情况,就是认为规定的s2[0] = -1,当s1[x]和s2[0]不等时,从s1[x+1]开始和s2[0]进行匹配就好了

匹配代码

综合以上三种情况,可以写出如下的匹配代码:

public  int indexOf(char[] s1, char[] s2) {
    int x = 0;
    int y = 0;
    // 计算next数组,下文讲解
    int[] next = getNext(s2);
    while (x < s1.length && y < s2.length) {
        // 匹配
       if (s1[x] == s2[y]) {
            x++;
            y++;
            continue;
        }
        // y == 0
       if (next[y] == -1) {
            x++;
            continue;
        }
        // y跳到最长前后缀的下一个位置,即z开始和x进行比较
        // 综合了next大于0和next等于0
        y = next[y];
    }
     
    // 匹配成功
    if (y == s2.length) {
        return x - y;
    }
    // 匹配失败
    return -1;
}

时间复杂度

要估计时间复杂度,需要估计while循环中的3个分支,这3个分支在每次while中只会中一个

定义两个量x,x-y,依次观察这3分支中,这两个量的变化情况

在这里插入图片描述

可以发现这两个量要么都推高,要么只推高一个,而这两个量上限都是O(M),因此时间复杂度为O(M)

计算next数组

我们从头到尾计算s2的next数组,假设当前计算到第i个位置

假设第i-1个位置的next值为a,对于s2[i-1]来说,有相等的前缀p1和p2

我们比较s2[i-1]和s2[a]是否相等,如果相等,则next[i]为a+1

在这里插入图片描述

证明如下:

首先因为np1 = p1 + a,np2 = p2 + s[i-1],p1 == p2,s[a] == s[i-1],因此np1 == np2

此时对于s[i]来说,至少有长度为a+1的前缀和后缀相等

那有没有可能next[i]大于a+1呢?

假设有这个可能,设这两段多余的部分分别为n1,n2,如下图所示:

在这里插入图片描述

现在np1 = p1 + s2[a] + n1

np2 = n2 + p2 + s[i-1]

np1 == np2,既然这两个大串都相等了,那对于s[i-1]来说,这个大串的一部分n2 + p2 一定和一个等量的前缀相等,而这个前缀的长度超过了 next[i-1],和前提不符

因此next[i]不可能大于a+1,即s[i]就等于a+1

再来看当s2[i-1]和s2[a]不等的情况:

在这里插入图片描述

继续看s2[a]的最长前缀pp1下一个字符,和s2[i-1]是否相等,如果相等,next[i] = next[a] + 1

关于为什么排除掉next[a]+1,到a这个区间的可能性,同样可以用反证法证明

如果还不等,就继续往前看

代码如下:

private  int[] getNext(char[] s2) {
    if (s2.length == 1) {
        return  new  int[]{-1};
    }

    int[] next = new  int[s2.length];
    next[0] = -1;
    next[1] = 0;
    // 目前在哪个位置求next
    int i = 2;
    // 需要和s2[i-1]比较的字符下标
    int c = 0;
    while (i < next.length) {
        if (s2[i-1] == s2[c]) {
            next[i] = c + 1;
            i++;
            c++;
        // 如果s[i-1]和第一个字符不等,next[i] = 0
        } else  if (c == 0){
            next[i] = 0;
            i++;
        } else {
            c = next[c];
        }
    }

    return next;
}

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

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

相关文章

线程池及源码分析

目录 1 java构建线程的方式 2 线程池的7个参数 3 线程池属性标识&线程池的状态 3.1 核心属性 3.2 线程池的状态 4 线程池的执行流程 5 添加工作线程的流程 6 Worker的封装&后续任务的处理 1 java构建线程的方式 一般就3~4种&#xff1a; 继承Thread&#xff…

迎接新年,暂且用Python绘制几个中国结吧

前言 今天就来分享几个用python绘制的图案吧 马上就要迎来新年了 就绘制了几个中国结&#xff0c;嘿嘿 话不多说&#xff0c;直接展示一下代码和效果图吧 更多学习资料与源码点击文章末尾名片领取 1. 效果图&#xff1a; 代码展示 import turtle turtle.screensize(600,…

GPDB插件安装工具之gppkg

gppkg命令gppkg是一个python3编写的打包脚本&#xff0c;在整个集群中安装.gppkg格式的Greenplum数据库扩展&#xff08;例如PL/Java、PL/R和MADlib&#xff09;及其依赖项&#xff0c;位于/usr/local/cloudberry-db/bin/gppkg(自己安装的gpdb目录)&#xff0c;安装到$GPHOME里…

1个寒假能学多少网络安全知识?

现在可以看到很多标题都声称三个月内就可以转行网络安全领域&#xff0c;并且成为月入15K的网络工程师。那么&#xff0c;这个寒假的时间能学多少网络安全知识&#xff1f;是否能入门网络安全工程师呢&#xff1f; 答案是肯定的。 虽然网络完全知识是一门广泛的学科&#xff…

ccc-sklearn-13-朴素贝叶斯(1)

朴素贝叶斯 一种直接衡量标签和特征之间概率关系的有监督学习算法&#xff0c;专注分类的算法&#xff0c;基于概率论和数理统计的贝叶斯理论。在计算的过程中&#xff0c;假设特征之间条件独立&#xff0c;不进行建模&#xff0c;采用后验估计。 sklearn中的朴素贝叶斯 类含…

1-选择题练手

1.采用递归方式对顺序表进行快速排序&#xff0c;下列关于递归次数的叙述中&#xff0c;正确的是 A.每次划分后&#xff0c;先处理较长的分区可以减少递归次数 B.递归次数与初始数据的排列次序无关 C.每次划分后&#xff0c;先处理较短的分区可以减少递归次数 D.递归次数与…

DaVinci:键 - 外部蒙版

调色页面&#xff1a;键Color&#xff1a;Key在调色页面&#xff0c;可以轻松地从媒体池将某个片段拖至节点面板中&#xff0c;以作为外部蒙版。或者&#xff0c;在节点上右击选择“添加蒙版” Add Matte。若无附加&#xff0c;则可以选择本节点片段的明度信息作为外部蒙版。当…

hbase2.x orphan regions on filesystem(region丢失)问题修复

问题描述&#xff1a;orphan regions on filesystem 可以通过主master web页面的HBCK Report查看 也可以通过hbck2工具查看 # 查看指定表 hbase hbck -j $HBASE_HOME/lib/hbase-hbck2-1.3.0-SNAPSHOT.jar addFsRegionsMissingInMeta default:tableName # 查看命名空间下所有…

Yolov5+TensorRT-生成dll-python/c++调用dll

YOlov5-6.0TensorRTdllpython/c调用简介1.项目环境2.TensorRT验证1.在tensorrtx-yolov5-v6.0\yolov5目录下新建build目录2.编写CMake.txt,根据自己目录更改2&#xff08;OpenCV_DIR&#xff09;、3&#xff08;TRT_DIR&#xff09;、10&#xff08;Dirent_INCLUDE_DIRS&#xf…

LabVIEW网络服务器何使用,有哪些不同

LabVIEW网络服务器何使用&#xff0c;有哪些不同NI有几款不同的Web服务器&#xff0c;可使用不同的产品并覆盖不同的用例。它们具有非常相似的名称&#xff0c;可以互换使用&#xff0c;但每个都提供不同的功能。应用程序Web服务器描述&#xff1a;NI应用Web服务器加载使用LabV…

企业微信商户号是什么?如何开通?

企业微信作为一款优秀的移动办公工具&#xff0c;与微信全方位打通&#xff0c;既可以与客户沟通交流&#xff0c;也可以在达成交易后直接进行对公收款&#xff0c;但是前提是要开通企业微信商户号。前言企业微信和微信都出自腾讯&#xff0c;而且企业微信全方位连接微信&#…

C#,图像二值化(16)——全局阈值的力矩保持算法(Moment-proserving Thresholding)及其源代码

1、力矩保持法 提出了一种基于矩保持原理的自动阈值选择方法。以这样的方式确定地计算阈值&#xff0c;即在输出画面中保留输入画面的时刻。实验结果表明&#xff0c;该方法可以将给定的图像阈值化为有意义的灰度级。该方法描述了全局阈值&#xff0c;但也适用于局部阈值。 A…

企业微信开发——企业内部自建应用开发(第二篇)---JS_SDK配置

企业微信如果想要使用企业微信的JS_SDK来实现拍照、定位等等功能&#xff0c;就需要预先在使用到的页面进行配置&#xff0c;当然你可以做全局配置。对于JS_SDK的配置设计前端和后端的统一配置。下面我来说明下具体的步骤。特别说明&#xff1a;1、企业微信有的接口需要配置wx.…

shader基础入门(1)

本文基于unity免费公开课“Hi Shader以及网络公开资料等书写”遵循开源协议。 MeshFilter网格过滤器 从海量资源中挑选适合的Mesh将他交给MeshRender MeshRenderer 网格渲染器 负责把MeshFilter丢过来的Mesh&#xff0c;绘制显示到我们的场景中 Material 材质球 Material…

多线程之死锁

目录&#xff1a; 1.什么是死锁&#xff1f; 2.可重入与不可重入 3.发生死锁的三个典型情况 4.发生死锁的四个必要条件 5.如何破除死锁&#xff1f; 1.什么是死锁&#xff1f; 谈到死锁&#xff0c;程序猿们都心存忌惮&#xff0c;因为程序一旦出现死锁&#xff0c;就会导…

深度学习训练营之鸟类识别

深度学习训练营之鸟类识别原文链接环境介绍前置工作设置GPU导入数据并进行查找数据处理可视化数据配置数据集残差网络的介绍构建残差网络模型训练开始编译结果可视化训练样本和测试样本预测原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&am…

机器学习:如何解决类别不平衡问题

类别不平衡是一个常见问题&#xff0c;其中数据集中示例的分布是倾斜的或有偏差的。 1. 简介 类别不平衡是机器学习中的一个常见问题&#xff0c;尤其是在二元分类领域。当训练数据集的类分布不均时会发生这种情况&#xff0c;从而导致训练模型存在潜在偏差。不平衡分类问题的示…

【Unity云消散】理论基础:实现SDF的8SSEDT算法

距离元旦假期已经过去5天了&#xff08;从31号算起&#xff01;&#xff09;&#xff0c;接着开始学习&#xff01; 游戏中的很多渲染效果都离不开SDF&#xff0c;那么SDF究竟是什么呢&#xff1f;到底是个怎么样的技术&#xff1f;为什么能解决那么多问题&#xff1f; 1 SD…

git介绍及环境搭建

git介绍及环境搭建Git介绍Git安装流程配置用户信息git工作流程与常用命令问题点总结主要工作流程git工作流程与原理总结Git介绍 1.Git是什么&#xff1f; Git版本控制系统是一个分布式的系统,是用来保存工程源代码历史状态(游戏存档)的命令行工具 GIT是一个命令行工具,用于版…

基于Java+Spring+vue+element社区疫情服务平台设计和实现

基于JavaSpringvueelement社区疫情服务平台设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源…