图解KMP算法
子串的定位操作通常称作串的模式匹配。你可以理解为在一篇英语文章中查找某个单词是否存在,或者说在一个主串中寻找某子串是否存在。
朴素的模式匹配算法
假设我们要从下面的主串S = "goodgoogle" 中,找到T = "google" 这个子串的位置。利用朴素的模式匹配算法我们通常需要下面的步骤。
(1):主串S第一位开始,S与T前三个字母匹配成功,但S第四个字母是d而T的是g。第一位匹配失败。如下图所示:浅绿色方块代表匹配成功,红色方块代表匹配失败。

(2):主串S从第二位开始,主串S首字母是o,要匹配的T的首字母是g。匹配失败:

(3):主串S第三位开始,主串S首字母是o,要匹配的T首字母是g,匹配失败:

(4):主串S第四位开始,主串S首字母是d,要匹配的T首字母是g,匹配失败:

(5):主串S第五位开始,S与T,6个字母全部匹配,匹配成功:

简单来说,朴素的模式匹配算法就是对主串的每一位字符作为子串的开头,与要匹配的子串进行比较,如果匹配不成功,则主串需要回溯到与子串开始匹配的位置的下一个位置。
当子串与主串有较多的相等的字符时,这种算法的效率是极其低下的。例如:

KMP模式匹配算法
如果你忍受不了朴素的模式匹配算法。于是有三位前辈:D.E.Knuth,J.H.Morris和V.R.Pratt发表了一个模式匹配算法,可以大大避免重复遍历的情况,我们把他称之为克努特-莫里斯-普拉特算法,简称KMP算法。
在去理解KMP算法时你需要知道一个字符串的前缀和后缀是什么?
下面以字符串 "google" 为例:
他的前缀集合:{ "g", "go", "goo", "goog", "googl" }。
它的后缀集合:{ "e", "le", "gle", "ogle", "oogle" }。
2.1 KMP算法匹配方式
假设我们要在主串S = "ABBABBABABAAABABAAA" 中查找模式串T = "ABBABAABABAA" 中的位置。
(1):我们发现主串S和子串T的前5个字符均是匹配的,第6个字符是不匹配的。下一步我们肯定不能回溯到主串第二个字符的位置,不然就是在重走朴素的模式算法的道路了。我们观察发现:1:匹配了五个字符中,2:在模式串的左右两端有两个字符子串,他们是完全匹配的。我们称之为最长公共前后缀。

(2):移动模式串,使得原来模式串的前缀到达模式串的后缀的位置:

这样的移动可以使得重新开始匹配的位置之前的字符,均是匹配的,因为公共前后缀AB是匹配的,并且原来的5个字符也是匹配的,所以将模式串的前缀移动到后缀的位置,与主串的后缀是匹配的。这样移动可以省去部分字符的不必要匹配(这样移动的正确性我们稍后探讨)。
(3):我们发现模式串的移动与主串并没有关系,因为只是将模式串的前缀移动到了模式串的后缀的位置,并且已经匹配的字符与主串肯定是一样的撒。
这时我们对一般情况做出分析:

我们现在已经明白了为什么要移动模式串的前缀到后缀的位置,以及这样移动的正确性。
(4):现在我们可以继续匹配一下主串,理解上述的操作:

2.2 next数组的手动推导
上面的图解以及分析仅是一个形象化的过程,我们还得将其转化为计算机能够处理的方式。
emm,我们假设存储字符串是从下标为1的位置开始的呢!
下面以具体的例子:主串S = "ABABABAABABAAABABAA",模式串T = "ABABAAABABAA",来分析如何转化成计算机能够处理的方式。
上面分析过,在模式串的移动过程中,与主串是没有关系的,我们只需要分析模式串即可:
模式串的任何一个位置都可能与主串发生不匹配,我们就是要将不匹配之后模式串由前缀移动到后缀的过程抽象成计算机能处理的形式。


我们发现有这样的规律:next数组对应的值是最长的公共前后缀加1。模式串剩余的字符就不再画图分析了,

我们将上面的数组取名为next数组,例如:我们发现模式串在与主串比较时,下标为5的位置发生了不匹配,我们只需要查找next[5] 的值,值为:3,就知道下一步是将模式串的3号位与主串的当前位置进行比较。
2.3 next数组的代码分析
/// <summary>
/// 求解next数组
/// </summary>
/// <param name="s"> 模式串的首字符地址 </param>
/// <param name="n"> 模式串的字符个数 </param>
/// <param name="next"> next数组的首元素地址 </param>
void getNext(char* s, int n, int* next)
{//遍历模式串int i = 1;//为next数组赋值int j = 0;//第一个字符不匹配将编号赋值为0,代表从主串的下一个位置开始比较next[1] = 0;while (i < n){if (j == 0 || s[i] == s[j])next[++i] = ++j;elsej = next[j];}
}
现在我们先以一个具体的例子来看代码是否正确哈,假设模式串为:"abab":

next[1] 需要一开始就初始化的哈。用i遍历模式串时,如果j指向的字符与i指向的字符相等,进入if分支,说明最长公共前后缀需要增加1,为什么是增加1呢?因为i一次只能遍历一个字符嘛!如果i指向的字符与j指向的字符不相等的话,进入else分支,令j=next[j],即更新j的位置,将新的j指向的字符与i指向的字符进行比较,看是否相等,不相等继续令j=next[j],直到j指向的字符与i指向的字符相等或者说j为0。为什么j的更新是j=next[j]? 我们下面就来分析原因!


这下你就能理解求next数组的全部代码啦。
2.4 查找子串的完整代码
下面是字符串从下标为1开始存储时的查找子串的代码。只是以一个具体的例子实践了一下,你只要理解了思路就随便写代码的。这里的代码仅供参考,具体代码还需结合题目具体分析!
/// <summary>
/// 求解next数组
/// </summary>
/// <param name="s"> 模式串的首字符地址 </param>
/// <param name="n"> 模式串的字符个数 </param>
/// <param name="next"> next数组的首元素地址 </param>
void getNext(char* s, int n, int* next)
{//i用来遍历模式串,j用来为next数组赋值,并且查找最长公共前后缀int i = 1, j = 0;//next[1]需要直接赋值next[1] = 0;while (i < n){if (j == 0 || s[i] == s[j])next[++i] = ++j;elsej = next[j];}
}
int main()
{char a[10] = " abababde";char s[5] = " aba";int next[4];getNext(s, 3, next);//8是主串的长度哈for (int i = 1, j = 0; i <= 8; i++){while (j && a[i] != s[j+1])j = next[j];if (a[i] == s[j+1])j++;//3是模式串的字符个数,相等就代表找到了if (j == 3){//输出开始匹配的位置printf("%d ", i - j + 1);//更新j尝试找到更多解j = next[j];}}return 0;
}

下面就是字符串从0开始存储的代码,同样的哈,代码版本有很多,重要的是理解思路!
void getNext(char* s, int n, int* next)
{int i = 0, j = -1;next[0] = -1;while (i < n){if (j == -1 || s[i] == s[j])next[++i] = ++j;elsej = next[j];}
}int main()
{char a[9] = "abababde";char b[5] = "abab";int next[5];getNext(b, 4, next);for (int i = 0, j = 0; i < 8; i++){while (j && a[i] != b[j])j = next[j];if (a[i] == b[j])j++;if (j == 4){printf("%d ", i - j + 1);j = next[j];}}return 0;
}

小试牛刀
下面的题解法肯定不止一种,kmp算法也不一定是最简单的,但是阔以用来练习kmp算法的!
3.1 旋转字符串
796. 旋转字符串 - 力扣(LeetCode)
题目描述:给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true。
s 的 旋转操作 就是将 s 最左边的字符移动到最右边。
例如, 若 s = 'abcde',在旋转一次之后结果就是'bcdea' 。
用kmp解题的大致思路就是拼接一个字符串到s后面,将goal当成模式串,在拼接后的字符串中查找goal就行。
void getNext(char* neddle, int n,int*next)
{int i =0;int j =-1;next[0] = -1;while(i<n){if(j==-1||neddle[i]==neddle[j])next[++i] = ++j;elsej = next[j];}
}
bool rotateString(char * s, char * goal){int lens = strlen(s);int leng = strlen(goal);if(lens!=leng)return false;char*str = (char*)calloc(lens*2+1, sizeof(char));memmove(str,s,lens);memmove(str+lens,s,lens);int * next = (int*)malloc(sizeof(int)*(leng+1));getNext(goal, leng,next);for(int i = 0,j=0;i<lens*2;i++){while(j&&str[i]!=goal[j])j = next[j];if(str[i]==goal[j])j++;if(j==leng){return true;}}return false;
}

3.2 最长快乐前缀
1392. 最长快乐前缀 - 力扣(LeetCode)
题目描述:
「快乐前缀」 是在原字符串中既是 非空 前缀也是后缀(不包括原字符串自身)的字符串。
给你一个字符串 s,请你返回它的 最长快乐前缀。如果不存在满足题意的前缀,则返回一个空字符串 "" 。
void getNext(char*s,int n,int* next)
{int i = 0;int j = -1;next[0] = -1;while(i<n){if(j==-1||s[i]==s[j])next[++i] = ++j;elsej = next[j];}
}char * longestPrefix(char * s){int len = strlen(s);int next[len+1];getNext(s,len,next);int max = 0;if(next[len]>max)max = next[len];if(max==0)return "";else{char* ret = (char*)calloc(max+1,sizeof(char));for(int i = 0;i<max;i++){ret[i] = s[i];}return ret;}
}

相关文章:

图解KMP算法
子串的定位操作通常称作串的模式匹配。你可以理解为在一篇英语文章中查找某个单词是否存在,或者说在一个主串中寻找某子串是否存在。朴素的模式匹配算法假设我们要从下面的主串S "goodgoogle" 中,找到T "google" 这个子串的位置。…...

Java Map和Set
目录1. 二叉排序树(二叉搜索树)1.1 二叉搜索树的查找1.2 二叉搜索树的插入1.3 二叉搜索树的删除(7种情况)1.4 二叉搜索树和TreeMap、TreeSet的关系2. Map和Set的区别与联系2.1 从接口框架的角度分析2.2 从存储的模型角度分析【2种模型】3. 关于Map3.1 Ma…...

【C/C++ 数据结构】-八大排序之 冒泡排序快速排序
作者:学Java的冬瓜 博客主页:☀冬瓜的主页🌙 专栏:【C/C数据结构与算法】 分享:那我便像你一样,永远躲在水面之下,面具之后! ——《画江湖之不良人》 主要内容:八大排序选…...

苹果ipa软件下载网站和软件的汇总
随着时间的流逝,做苹果版软件安装包下载网站和软件的渐渐多了起来。 当然,已经关站、停运、下架、倒闭的苹果软件下载网站和软件我就不说了,也不必多说那些关站停运下架倒闭的网站和软件了。 下面我统计介绍的就是苹果软件安装包下载网站和软…...

深度学习-【语义分割】学习笔记4 膨胀卷积(Dilated convolution)
文章目录膨胀卷积为什么需要膨胀卷积gridding effect连续使用三次膨胀卷积——1连续使用三次膨胀卷积——2连续使用三次膨胀卷积——3Understanding Convolution for Semantic Segmentation膨胀卷积 膨胀卷积,又叫空洞卷积。 左边是普通卷积,右边是膨胀…...

【10】SCI易中期刊推荐——工程技术-计算机:人工智能(中科院2区)
🚀🚀🚀NEW!!!SCI易中期刊推荐栏目来啦 ~ 📚🍀 SCI即《科学引文索引》(Science Citation Index, SCI),是1961年由美国科学信息研究所(Institute for Scientific Information, ISI)创办的文献检索工具,创始人是美国著名情报专家尤金加菲尔德(Eugene Garfield…...

模电计算反馈系数,有时候转化为计算电阻分压的问题
模电计算反馈系数,有时候转化为计算电阻分压的问题 如果是电压反馈,F的除数是Uo 如果是电流反馈,F的除数是Io 串联反馈,F的分子是Uf 并联反馈,F的分子是If 点个赞呗,大家一起加油学习!...

专治Java底子差,不要再认为泛型就是一对尖括号了
文章目录一、泛型1.1 泛型概述1.2 集合泛型的使用1.2.1 未使用泛型1.2.2 使用泛型1.3 泛型类1.3.1 泛型类的使用1.2.2 泛型类的继承1.4 泛型方法1.5 泛型通配符1.5.1 通配符的使用1)参数列表带有泛型2)泛型通配符1.5.2 泛型上下边界1.6 泛型的擦除1.6.1 …...

PayPal轮询收款的那些事儿
想必做跨境电商独立站的小伙伴,对于PayPal是再熟悉不过了,PayPal是一个跨国际贸易的支付平台,对于做独立站的朋友来说跨境收款绝大部分都是依赖PayPal以及Stripe条纹了。简单来说PayPal跟国内的支付宝有点类似,但是PayPal它是跨国…...

【Linux】项目自动化构建工具——make/Makefile
目录 1.make与Makefile的关系 Makefile make 项目清理 clean .PHONY 当我们编写一个较大的软件项目时,通常需要将多个源文件编译成可执行程序或库文件。为了简化这个过程,我们可以使用 make 工具和 Makefile 文件。Makefile 文件可以帮助我们自动…...

成本降低90%,OpenAI正式开放ChαtGΡΤ
今天凌晨,OpenAI官方发布ChαtGΡΤ和Whisper的接囗,开发人员现在可以通过API使用最新的文本生成和语音转文本功能。OpenAI称:通过一系列系统级优化,自去年12月以来,ChαtGΡΤ的成本降低了90%;现在OpenAI用…...

hls.js如何播放m3u8文件(实例)?
HLS(HTTP Live Streaming)是一种视频流传输协议,是苹果推出的适用于iOS与macOS平台的流媒体传输协议。它将视频分割成若干个小段,每个小段大小一般为2~10秒不等,并通过HTTP协议进行传输。通过在每个小段之间插入若干秒…...
大数据平台建设方法论集合
文章目录从0到1建设大数据解决方案大数据集群的方法论数据集成方法论机器学习算法平台方法论BI建设的方法论云原生大数据的方法论低代码数据中台的方法论大数据SRE运维方法论批流一体化建设的方法论数据治理的方法论湖仓一体化建设的方法论数据分析挖掘方法论数字化转型方法论数…...

25- 卷积神经网络(CNN)原理 (TensorFlow系列) (深度学习)
知识要点 卷积神经网络的几个主要结构: 卷积层(Convolutions): Valid :不填充,也就是最终大小为卷积后的大小. Same:输出大小与原图大小一致,那么N 变成了N2P. padding-零填充. 池化层(Subsampli…...
把数组里面数值排成最小的数
问题描述:输入一个正整数数组,将它们连接起来排成一个数,输出能排出的所有数字中最小的一个。例如输入数组{12, 567},则输出这两个能排成的最小数字12567。请给出解决问题的算法,并证明该算法。 思路:先将…...

云his系统源码 SaaS应用 基于Angular+Nginx+Java+Spring开发
云his系统源码 SaaS应用 功能易扩 统一对外接口管理 一、系统概述: 本套云HIS系统采用主流成熟技术开发,软件结构简洁、代码规范易阅读,SaaS应用,全浏览器访问前后端分离,多服务协同,服务可拆分ÿ…...
小红书场景营销怎么做?场景营销主要模式有哪些
小红书作为新兴媒体领域的佼佼者,凭借着生动,直观,代入感等元素的分享推荐收揽了巨额的流量。但是,随着时代的脚步逐渐加快,发展和变革随之涌来,传统的营销已经无法满足。所以场景营销就出现了。今天就来和…...
c++基础——数组
数组数组是存放相同类型对象的容器,数组中存放的对象没有名字,而是要通过其所在的位置访问。数组的大小是固定的,不能随意改变数组的长度。定义数组数组的声明形如 a[b],其中,a 是数组的名字,b 是数组中元素…...
odoo15 登录界面的标题自定义
odoo15 登录界面的标题自定义 原代码中查询:<title>Odoo<title> <html> <head><meta http-equiv="content-type" content="text/html; charset=utf-8" /><title>Odoo</title><link rel="shortcut icon…...

【内网服务通过跳板机和公网通信】花生壳内网穿透+Nginx内网转发+mqtt服务搭建
问题:服务不能暴露公网 客户的主机不能连外网,服务MQTT服务部署在内网。记做:p1 (computer 1)堡垒机(跳板机)可以连外网,内网IP 和 MQTT服务在同一个网段。记做:p2 (computer 2)对他人而言&…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...

(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版分享
平时用 iPhone 的时候,难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵,或者买了二手 iPhone 却被原来的 iCloud 账号锁住,这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...

视频字幕质量评估的大规模细粒度基准
大家读完觉得有帮助记得关注和点赞!!! 摘要 视频字幕在文本到视频生成任务中起着至关重要的作用,因为它们的质量直接影响所生成视频的语义连贯性和视觉保真度。尽管大型视觉-语言模型(VLMs)在字幕生成方面…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...