全网最容易理解的KMP算法讲解

引言

其实网上有很多讲解KMP算法的文章,详略不一,我认为有两点没有解释清楚:

第一点:匹配失败以后,模式串的位移

第二点:next数组的生成算法

希望本篇文章能将KMP算法清晰易懂的拆解开来。

暴力匹配

假如你是一名生物学家,现在,你的面前有两段 DNA 序列 S 和 T,你需要判断 T 是否可以匹配成为 S 的子串:

我们很容易想到的一个方法就是对每个字符进行逐一比较。

首先:我们从左边第一个位置开始进行逐一比较:

这样,当匹配到 T 的最后一个字符时,发现不匹配,于是从 S 的第二个字符开始重新进行比较:

仍然不匹配,再次将 T 与 S 的第三个字符开始匹配......不断重复以上步骤,直到从 S 的第四个字符开始时,最终得出结论:S 与 T 是匹配的。

大家发现这个方法的弊端了吗?我们在进行每一轮匹配时,总是会重复对 A 进行比较。也就是说,对于 S 中的每个字符,我们都需要从 T 第一个位置重新开始比较,并且 S 前面的 A 越多,浪费的时间也就越多。假设 S 的长度为 m,T 的长度为 n,理论上讲,最坏情况下迭代 m−n+1 轮,每轮最多进行 n 次比对,一共比较了 (m−n+1)×n 次,当 m>>n 时,渐进时间复杂度为 O(mn)。

改进方案

我们再来看一个例子,现在有如下字符串 S 和 P,判断 P 是否为 S 的子串:

我们仍然按照原来的方式进行比较,比较到 P 的末尾时,我们发现了不匹配的字符。

注意,按照原来的思路,我们下一步应将字符串 P 的开头,与字符串 S 的第二位 C 重新进行比较。那能不能改进一下呢?当 T 和 Y 不匹配时,我们仔细观察发现在字符 Y的前面有一个子串:“ACTGPAC”,该子串有相同的部分AC。当比较到S的T位置时,其实我们已经知道S 中的蓝色 AC 与P中右侧的 AC是匹配的,又因为P中左侧也有AC,如果我们将字符串 P 需要比较的位置重置到图中 j 的位置,S 保持 i 的位置不变,接下来即可从 i,j 位置继续进行比较,这样不就能大大简化匹配过程了吗?这就是 KMP 的核心思想。

备注:【上述部分内容来源:力扣(LeetCode)】

KMP算法

简介:

 Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。

它是一种改进的字符串匹配算法,它的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。它的时间复杂度是 O(m+n)。

算法流程:

  • 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
  • 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
  • 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
  • next 数组存储的值的含义:代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] 等于 k,代表 j 之前的字符串中有最大长度为k 的相同前缀后缀。

最长前缀后缀:

前缀:

除去字符本身,它前面的字符能组成的所有组合,这里注意一下,必须以第一个字符开头,比如字符串abc,它的前缀有a,ab

后缀:

除去字符本身,它后面的字符能组成的所有组合,,这里注意一下,必须以最后一个字符开结尾,比如字符串abc,它的后缀有c,bc

前缀后缀的公共元素:这个就非常简单了,不再解释。

最大长度表【最大公共元素长度】:

 如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:

取上表的第一列和最后一列,然后进行行列转换一下得到如下的《最大长度表》

基于最长前缀后缀的匹配:

因为模式串中首尾可能会有重复的字符,故可得出下述结论:不匹配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应字符串的最长前缀后缀

如果给定文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,如下图所示:

 

1. 因为模式串中的字符A跟文本串中的字符B、B、C、空格一开始就不匹配,所以不必考虑结论,直接将模式串不断的右移一位即可,直到模式串中的字符A跟文本串的第5个字符A匹配成功:


2. 继续往后匹配,当模式串最后一个字符D跟文本串匹配时失配,显而易见,模式串需要向右移动。但向右移动多少位呢?因为此时已经匹配的字符数为6个(ABCDAB),然后根据《最大长度表》可得失配字符D的上一位字符B对应的长度值为2,所以根据之前的结论,可知需要向右移动6 - 2 = 4 位。


3. 模式串向右移动4位后,发现C处再度失配,因为此时已经匹配了2个字符(AB),且上一位字符B对应的最大长度值为0,所以向右移动:2 - 0 =2 位。


4. A与空格失配,向右移动1 位。


5. 继续比较,发现D与C 失配,故向右移动的位数为:已匹配的字符数6减去上一位字符B对应的最大长度2,即向右移动6 - 2 = 4 位。


6. 经历第5步后,发现匹配成功,过程结束。

备注:【上述原文链接:https://blog.csdn.net/v_july_v/article/details/7041827】

next数组:

由来:

通过上一节,我们可以看到,当我们要匹配失败的时候,需要去找匹配失败的字符的上一位字符对应的最长前缀后缀,那么如果我们将这个最大长度表的值整体右移一位,不就更简单了吗,这就是我们的next数组。next 数组就是求得了最大长度表,然后整体右移一位,对next[0]赋值为-1,也即:

算法实现:

前面我们通过一步一步的推导,求得了最大长度表,然后又将其右移一位得到next数组,那么我们如何通过代码来求得next数组呢?
对于P的前j+1个序列字符:
若p[k] == p[j],则next[j + 1 ] = next [j] + 1 = k + 1; -- 这个大家理解起来都没有问题
若p[k ] ≠ p[j],如果此时p[ next[k] ] == p[j ],则next[ j + 1 ] =  next[k] + 1,否则继续递归前缀索引k = next[k],而后重复此过程。--这个理解起来就非常困难了,我们看下面的图:

1、p[k] != p[j]

2、p[0]到p[k-1] 等于 p[j-k]到p[j-1]的,也就是图中最上面K个元素

3、我们现在要找到新的p[0]到p[k’-1] 等于 p[j-k’]到p[j-1],那么这个新的k怎么求呢?

4、因为p[0]到p[k-1] 等于 p[j-k]到p[j-1],所以上图中蓝2色块与蓝4色块相等的,现在又要找蓝1色块+黄1色块 等于蓝4色块+黄2色块,所以蓝1色块等于蓝2色块,也意味着我们在找p[k]左边的字符串的最长前缀后缀,而这正是next[k],是不是无巧不成书。

算法代码:

next数组代码:

int[] getNext(String p) {int l = p.length();int[] next = new int[l];int j = 0;int k = -1;next[0] = -1;while (j < l -1) {if (k == -1 || p.charAt(j) == p.charAt(k)) {k++;j++;next[j] = k;} else {k = next[k];}}return next;}

KMP算法

int KmpSearch(char* s, char* p)
{int i = 0;int j = 0;int sLen = strlen(s);int pLen = strlen(p);while (i < sLen && j < pLen){   if (j == -1 || s[i] == p[j]){i++;j++;}else{j = next[j];}}if (j == pLen)return i - j;elsereturn -1;
}

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

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

相关文章

网络同步—帧同步和状态同步解析

概述 同步就是要多个客户端表现效果是一致的&#xff0c;而且对于大多数的游戏&#xff0c;不仅仅要表现一致&#xff0c;还要客户端和服务器的数据也是一致的。所以同步是个网络游戏概念&#xff0c;只有网络游戏才需要同步&#xff0c;而单机游戏是不需要同步的。 帧同步和…

算法-3-基本的数据结构

单双链表 1.单链表双链表如何反转 import java.util.ArrayList; import java.util.List;public class Code01_ReverseList {public static class Node {public int value;public Node next;public Node(int data) {value data;}}public static class DoubleNode {public int…

掘根宝典之C++深复制与浅复制(复制构造函数,默认复制构造函数)

到目前为止我们已经学了构造函数&#xff0c;默认构造函数&#xff0c;析构函数&#xff1a;http://t.csdnimg.cn/EOQxx 转换函数&#xff0c;转换构造函数&#xff1a;http://t.csdnimg.cn/kiHo6 友元函数&#xff1a;http://t.csdnimg.cn/To8Tj 接下来我们来学习一个新函数…

python毕设选题 - 大数据全国疫情数据分析与3D可视化 - python 大数据

文章目录 0 前言1 课题背景2 实现效果3 设计原理4 部分代码5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业答辩的要求和难度不断提升&#xff0c;传统的毕设题目缺少创新和亮点&#xff0c;往往达不到毕业答辩的要求&#xff0c;这两年不断有学弟学妹告诉学长自己做的…

2024阿里云云服务器ECS价格表出炉

2024年最新阿里云服务器租用费用优惠价格表&#xff0c;轻量2核2G3M带宽轻量服务器一年61元&#xff0c;折合5元1个月&#xff0c;新老用户同享99元一年服务器&#xff0c;2核4G5M服务器ECS优惠价199元一年&#xff0c;2核4G4M轻量服务器165元一年&#xff0c;2核4G服务器30元3…

【Funny Game】 人生重开模拟器

目录 【Funny Game】 人生重开模拟器&#xff01; 人生重开模拟器&#xff01; 文章所属专区 Funny Game 人生重开模拟器&#xff01; 人生重开模拟器&#xff0c;让你体验从零开始的奇妙人生。在这个充满惊喜和挑战的游戏中&#xff0c;你可以自由选择性别、出生地、家庭背景…

String.format()详细用法

String 类有一个强大的字符串格式化方法 format()。下面是常用的方法总结。 一、占位符类型 String formatted String.format("%s今年%d岁。", "小李", 25); // "小李今年25岁。" 二、字符串和整数格式化 // 将第二个入参拼接到模板中,入参长…

职业性格在求职应聘和跳槽中的作用

性格测试对跳槽者的影响大不大&#xff1f;首先我们要弄清楚两个问题&#xff0c;性格对我们的职业生涯又没有影响&#xff0c;性格测试是什么&#xff0c;职场中有哪些应用&#xff1f;性格可以说从生下来就有了&#xff0c;随着我们的成长&#xff0c;我们的性格也越来越根深…

大模型训练流程(一)预训练

预训练GPU内存分析&#xff1a; GPU占用内存 模型权重 梯度 优化器内存&#xff08;动量估计和梯度方差&#xff09; 中间激活值*batchsize GPU初始化内存 训练流程 &#xff08;选基座 —> 扩词表 —> 采样&切分数据 —> 设置学习参数 —> 训练 —>…

什么是美颜SDK?美颜SDK在短视频平台中的作用探究

在这个以视频为主导的平台上&#xff0c;美颜技术在其中扮演了不可或缺的角色。本文将探讨美颜SDK的本质&#xff0c;以及它在短视频平台中所发挥的作用。 一、什么是美颜SDK&#xff1f; 美颜SDK是一种软件开发工具包&#xff0c;其主要功能是通过算法对图像进行美化处理。…

【教3妹学编程-算法题】人员站位的方案数 II

2哥 : 3妹&#xff0c;今天第一天上班啊&#xff0c;开工大吉~ 3妹&#xff1a;2哥&#xff0c;开工大吉鸭&#xff0c;有没有开工红包&#xff1f; 2哥 : 我们公司比较扣&#xff0c;估计不会发的。 3妹&#xff1a;我们公司估计也一样&#xff0c;不过依然挡不住我打工人的热…

HarmonyOS router页面跳转

默认启动页面index.ets import router from ohos.router import {BusinessError} from ohos.baseEntry Component struct Index {State message: string Hello World;build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold)//添加按钮&am…

【 JS 进阶 】原型对象、面向对象

目标 了解构造函数原型对象的语法特征&#xff0c;掌握 JavaScript 中面向对象编程的实现方式&#xff0c;基于面向对象编程思想实现 DOM 操作的封装。 了解面向对象编程的一般特征掌握基于构造函数原型对象的逻辑封装掌握基于原型对象实现的继承理解何为原型链及其作用能够处理…

C++Qt:noteBookPro_01

一、创建项目 选择Qt Widgets 常用的是QWidgets和MainWindow。两者的区别&#xff1a; QWidgets用于简单的窗口&#xff0c;没有内置的菜单栏、工具栏和状态栏。适用于简单专用的应用程序&#xff0c;不需要复杂的界面组件。 MainWindow是包含完整的菜单栏、工具栏和状态栏的主…

Linux 主机数据拷贝与 Linux 服务器之间拷贝文件的方法

Linux 主机数据拷贝与 Linux 服务器之间拷贝文件的方法 1. 使用 scp 命令2. 使用 rsync 命令3. 使用 scp 和 rsync 的图形界面工具4. 使用 FTP/SFTP 协议总结与比较 在 Linux 系统中&#xff0c;数据拷贝是日常操作中的常见需求&#xff0c;尤其是在不同主机或服务器之间进行文…

近场2D beamforming Heatmap图

文章目录 想法代码目前啥样想法 参考论文Beam Focusing for Near-Field Multiuser MIMO Communications,可视化beam focusing效应 代码 clc; clear;% 网格范围 D = 1; % 整个均匀平面阵列的孔径 lambda = 1e-2; % 波长0.01m,单位:米 30GhzN_d = floor(2 * D / lambda); %…

PS自由变换的小技巧--墙面广告牌

墙面广告牌&#xff0c;如何用PS做出看上去特别真实的一个效果 1.首先&#xff0c;我们会有墙面跟广告栏2个图层 2.然后将广告牌复制一层 3.接着用钢笔工具画出墙面的透视&#xff0c;也就是两条线&#xff0c;这两条线的交叉点就是墙面的透视点 4.接着选中广告牌复制图层&…

杨氏矩阵和杨辉三角

杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N)&#xff0c;就不能每一行一个个…

TCP流量控制+拥塞控制

流量控制&#xff1a; 目标&#xff1a;流量控制主要解决的是发送方和接收方之间处理能力的不匹配问题。它的目的是确保发送方不会发送数据过快&#xff0c;以至于接收方无法及时接收并处理这些数据&#xff0c;从而避免数据包在网络中堆积和丢失。实现方式&#xff1a;在TCP协…

【.NET Core】深入理解async 和 await 理解

【.NET Core】深入理解async 和 await 理解 文章目录 【.NET Core】深入理解async 和 await 理解一、概述二、async异步执行机制理解三、async与await应用3.1 async与await简单应用3.2 带有返回值async与await应用 四、async和await中常见问题总结4.1 当方法用async标识时&…