【数据结构】KMP算法细节详解
创始人
2025-05-30 03:58:12

KMP算法细节详解

    • 前言
    • 一、字符串匹配问题
      • 1.BF算法
      • 2.KMP算法
    • 二、next数组
    • 三、手写nex思想
    • 四、机算next思想
    • 五、next数组细节理解
    • 六、nextVal数组
    • 七、KMP算法代码实现
    • 八、nextVal数组代码实现
    • 完结

前言

KMP算法是为了字符串匹配问题而被研究出来的,字符串匹配问题就是查看一个字符串A是否是字符串B的子串,如果是字串的话,在B的哪个位置?此算法代码简练,但理解起来非常困难,建议挑出一整块时间来专门学习,本文作者写的非常用心,还不了解KMP的小伙伴一定要静下心来慢慢细品,你一定会有所收获🍊

一、字符串匹配问题

如果遇到这种在一个字符串中寻找另一个字符串的子串这种问题,大多数人第一时间想到的肯定是通过暴力匹配算法来完成,也就是Brute-Force算法简称BF算法,时间复杂度为O(m*n),如果有上千行上万文本呢?,时间成本一定会很高,所以D.E.Knuth,J.H.Morris和V.R.Pratt三位大神提出了KMP算法,KMP(Knuth-Morris-Pratt )算法也由他们三个的名字命名。

1.BF算法

在这里插入图片描述

2.KMP算法

肉眼可见的差距

在这里插入图片描述

二、next数组

next数组是KMP算法的精髓

KMP之所以会这样高效的查找字符串全都是next数组的功劳,也就是动图中字符串下面的数字,KMP算法会根据生成的next数组来移动,如果比对错误,将子串的首位直接移动到主串对比错误的位置,随后根据next数组提供的下标向左移动x格,图中下标为-1,所以向左移动-1格,就是向右移动一格

在这里插入图片描述

三、手写nex思想

注:上面图片的下标是按照nextVal生成的,next数组生成请看下面,nextVal是next的改进版,后面会讲到,我们先理解next数组生成原理

next数组的值计算的是从第n位开始前面字符串的最大公共前后缀的长度,不包括整个字符串本身,比如有这样一个字符串

在这里插入图片描述

从第一位向前看,没有字符,没法求,所有人为规定next[0]固定为-1(这里也有部分老师讲解第一位是0,其实思想都一样只不过是下标不一样而已,我们学的是思想不用在意这些),从第二位b向前看,也没有公共前后缀,next[2]为0,从第三位c向前看,也没有,next[3]为0,第四位a向前看,也没有next[4]为0,第五位b向前看,此时,最大公共前后缀为a,长度就是1,next[5]为1,从第六位向前看,最大公共前后缀为ab,长度为2,所以next[6]为2

在这里插入图片描述

四、机算next思想

比如一个字符串abcda,此时next数组的最大公共前后缀是1,那如果加个b呢,abcdab,此时最大公共前后缀是2,加个c呢,最大公共前后缀就变成3,经过大量数据观察,发现每次最大公共前后缀最多+1。

如果一个字符串abcdeffabcde,此时最大公共前后缀是3,如果在后面加一个g,这个字符串变成abcdffabcdg,最大公共前后缀直接就变成了0,也就是说每次最大公共前后缀可能直接减少到0。

如果增加一个字符的时候,发现新增加的字符与前缀后面的字母不相同了,就会进行减少,如下图所示,一次次进行查找,当前新的最大相同前后缀是什么,直到查找到位置。

在这里插入图片描述

这种方法未免有些麻烦,我们可以利用已有的条件进行优化,如果没有找到,直接找到其对应的next数组的值的字符串的下标,进行比对,如果成功,将找打他的next数组的值进行+1,就是next数组最新一位的值,如果找到最后,会找到字符串下标为0的位置,0位置的next数组值是-1,拿到-1的话数组会报错,这里写代码的时候不要忘记处理

在这里插入图片描述
为什么可以这么做呢?因为前面红框的前缀,等于后面红框的后缀,我们在此基础上向后进行对比一位即可判断出next数组最新一位的值。
在这里插入图片描述

五、next数组细节理解

为什么按照next数组移动就可以保证不跳过匹配成功的字符串呢?

前面说过,如果匹配失败子串的首字符的位置会移动到匹配失败的位置,再向左移动next数组[i]格,匹配失败的那一位的next数组记录着前面字符的最大公共前后缀长度,由于前面的前缀都已经于主串匹配过了,只不过后缀后面的位置对不上,那么我们直接将后缀的起始位置对准匹配失败的位置,也就是向左移动next[i]个长度(也就是向左移动最大公共前后缀的长度),就能保证跳跃过程中没有错过任何可以匹配成功的串。还可以使主串中已经成功配对的后缀于子串的前缀相匹配,再向后匹配。

在这里插入图片描述

下图中,①=②=③=④,所以移动回去时保证前面的后缀会与匹配失误的地方对齐

在这里插入图片描述

上面展示的是有公共前后缀的情况,那么如果整个字符串都没有公共前后缀会怎么样?

  • 将发挥KMP算法的最大作用!

我们只需大胆向后跳,不用再往前跳了

此动画后半部省略掉了,找到最后没有发现相同串

在这里插入图片描述

六、nextVal数组

讲完了next数组,接下来讲解nextval数组,nextVal数组是next数组的改进版,因为next数组还有改进的空间,那就是在生成数组的时候对在next数组的基础上,对失配字符的next数组下标进行读取,读取数组下标到字符串的下标位置,然后进行比较,如果不相等,那么不对其进行优化,如果读取失配字符的next数组下标到字符串的下标位置相等,那么将读取失配字符的next数组下标到字符串下标,然后取出这个位置的next数组的值到失配位置的next数组。(这里难理解,多看几次)

在这里插入图片描述

这样做的原理是什么呢?

在这里插入图片描述

如果此时a在与上面的字符串进行配对,那么a此时与b失配,说明对比的数是非a,按照next数组原理我们将下标为0的位置移动到上面b的位置,可是a已经和b对比过了,将下标为0的a移动过去必然也会失配,所有nextVal要先对失配的位置与next数组的值对应的下标的字符进行比较,相等就会读取其对应位置的next下标到失配位置的next下标。

七、KMP算法代码实现

讲完了原理,这里是代码实现。

  • 可以将里面的next替换为nextVal,两者都为独立的数组生成函数。
#define _CRT_SECURE_NO_WARNINGS 1
#include 
#include 
#include void getNext(int * next,const char* s,int len)
{assert(next && s);				//判断两个指针是不是空指针int i = 0, cur = -1;			//i是当前位置、cur是当前最大相同前后缀的长度next[0] = cur;while (i < len){if (cur==-1||s[i]==s[cur]) 	//cur为-1的时候也要恢复成0同时i++{next[i + 1] = cur+1;cur++;					//cur恢复成0i++;				}else{cur = next[cur];		//如果失配,直接找到这个位置的next数组的值给到cur}}
}
int kmp(const char* p, const char* s)
{int len = strlen(s);int* next = (int*)malloc(sizeof(len) * len);getNext(next, s, len - 1);assert(next);int i = 0, j = 0;			//定义i为字符串下标,j为next数组下标while (p[i])				{if (j==-1||p[i] == s[j])	//如果next数组下标为-1,next数组下标恢复为0,字符串下标+1	{i++;				j++;		if (s[j] == '\0')		//如果字符串下标为‘\0’,查找成功返回i-j也就是字符串匹配成功位置的收个字符的下标{free(next);next = NULL;return i - j;}}else{j = next[j];			//将此位置的next数组下标对准字符串对应位置得下标}}free(next);next = NULL;return -1;					//如果p[字符串下标]=='\0',跳出循环,证明查找失败,返回-1
}
int main()
{char p[] = "abcdabecabcfdsafdasdasfdasfasdfasfdasafdsbc";char s[] = "abecab";printf("%d", kmp(p, s));
}

八、nextVal数组代码实现

void getNextVal(int *next,const char* s,int len)
{assert(next);int i = 0, cur = -1;next[0] = cur;while(i < len){if (cur == -1 || s[i] == s[cur])  //为-1注意处理{i++;cur++;if (s[i] != s[cur])			//比next数组增加一个判断{next[i] = cur;}else{next[i] = next[cur];		//如果相等,把next[cur]的值取过来}}else{cur = next[cur];}}
}

完结

作者的话
KMP是一块硬骨头,建议各位能挑出一段长时间专门攻克它,不要今天看一点,明天再看一点,这样不会有收获,写代码不代表就已经真正理解了KMP,你最好要能熟练的讲给其他小伙伴KMP到底一种什么样的算法,记录一份详细的笔记,忘记的时候拿出来反复重温,最后如果觉得哪里没有理解,可以评论区留言或者私信作者,我会耐心地给大家来答疑解惑。当然如果有不足的地方还请各位帮忙补充。

相关内容

热门资讯

Mysql常用数据类型总结 整形 枚举类型ENUE整形       TINYINT,SMALLINT,MEDIUMINT,IN...
【flink sql】创建表 flink sql创建表语法 CREATE TABLE [IF NOT EXISTS] [catal...
python opencv 保... 👨‍💻个人简介: 深度学习图像领域工作者 dz...
Pytorch深度学习实战3-... 目录1 数据集Dataset2 数据加载DataLoader3 常用预处理方法4 模型处理5 实例&...
自定义类型的超详细讲解ᵎᵎ了解...   目录 1.结构体的声明 1.1基础知识 1.2结构体的声明 1.3结构体的特殊声明  1.4结构...
Docker等容器技术如何与移... 移动应用程序的开发面临着很多挑战,包括开发环境的设置、测试的困难、部署的复杂性等。由于...
【微服务】—— Nacos安装... 文章目录1. Windows安装1.1 下载安装包1.2 解压1.3 端口配置1.4 启动1.5 访...
【OpenGL】 为了理解这个函数我们需要先学习一些OpenGL的内容 OpenGL可视化 https://g...
hjr-详细说一下Redis集... Redis作用 缓存 一般我们用Redis做缓存,热点数据 击穿:访问到...
【蓝桥杯】 C++ 数字三角形... 文章目录题目描述输入描述输出描述实现代码解题思路注意点知识点 题目描述 上图给出了一个数字三角形。从...
VR全景展会丨探索未来,重塑现... 随着科技的不断发展,虚拟现实(VR)技术逐渐成为一个重要的...
C++数据类型 目录 C++基础数据类型 指针 指针类型 指针赋值 引用 参考:《深...
超实用!!! 三分钟将你的项目... 文章目录前言一、在项目中新增配置二、配置github page setting?三、如...
数据结构---队列 专栏:数据结构 个人主页:HaiFan. 专栏简介:这里是...
数字操作方法 系列文章目录 前端系列文章——传送门 JavaScript系列文章——传送门 文章目录系列文章目录...
Cartesi 2023 年 ... 查看 Cartesi Machine、Cartesi Rollups 和 Noether 的更新正在...
JavaWeb——jsp概述入... JSP定义:  在如下一个jsp文件里面有如下的代码  <%@ page content...
一切喜怒哀乐都来自于你的认知 01 有个学子,准备出国,父母请来清华的教授宁向东。请问教授࿱...
JAVA并发编程——synch... 引言         Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,...
git学习----3.21 未... 文章目录前言Git :一个分布式版本控制工具目标一、概述1.1 开发中的实际场景1.2...
Qt优秀开源项目之十七:QtP... QtPromise是Promises/A+规范的Qt/C++实现。该规范的译...
【前端八股文】JavaScri... 文章目录Set概念与arr的比较属性和方法并集、交集、差集Map概念属性和方法String用索引值和...
海康硬盘录像机接入RTSP/o... EasyNVR安防视频云服务平台可支持设备通过RTSP/Onvif协议接入平台,能提供...
在混合劳动力时代如何避免网络安... 在混合劳动力时代如何避免安全网络风险 三年多来,混合工作一直是工作生活中不可或缺的一...
2023还不懂Jmeter接口... 这里介绍的Jmeter接口测试的的实战,如果文章内容没遇看懂的话,我这边...
基于4G/5G弱网聚合的多链路... 基于4G/5G多卡聚合(弱网聚合)的智能融合通信设备技术亮点 增强带宽提供可靠连接 通过将多个有线和...
如何使用Synplify综合v... 文章目录使用Synplify综合的好处synplify的教程方法1(无效)...
2023年全国最新高校辅导员精... 百分百题库提供高校辅导员考试试题、辅导员考试预测题、高校辅导员考试真题、辅导员证考试题库等ÿ...
2022年18个值得期待的Le... 有数百个独特的LearnDash附加组件,您可能很难选择您的LearnDash LMS...
【java基础】Stream流... 文章目录基本介绍流的创建流的各种常见操作forEach方法filter方法map方法peek方法fl...