直观清晰的带你了解KMP算法(超详细)

news2024/9/25 21:28:54

KMP算法用来找某个字符串是否存在某个连续的真子串的

下面举一个例子让抽象的KMP算法更加直观,有助于理解

首先我们要了解KMP算法首先要找到一个next数组来表示主串中每一个字符的回退的下标(这个下标是对于真子串而言的,主串不需要回退)(这可能看的很懵逼,但是不要紧,接着往下看你肯定明白了)

例:寻找字符串”abcaefabcdabsd"的子串abcd

寻找字符串”a   b  c    a e  f  a  b  c d a  b  s  d"的子串abcd(这里我把题目字符串拉的很开便于大家理解)
     主串p[i]   a     b   c   a    e   f   a    b    c     d     a     b    s    d
                    
主串不回退,子串回退
                            0     1   2   3    4   5  6    7    8    9     10   11  12  13
                            a     b   c   a    e   f   a    b    c     d     a     b    s    d
(所有从字符串必须从a开始p[i-1]字符结束的相同字符串,而且两个子字符串必须相同。
我们记a的回退下标为-1,那么从字符b开始,准备开始寻找从a字符开始,从p[i]字符前一个字符结束的相同的字符串是否有两个,单一的字符也算
我们可以看到从b字符下标应该是0,因为从a字符开始,从a字符结束的字符串只有1个,那就是a字符,所以b下标是0
同理我们看第三个字符c就是从a字符开始,b字符结束的字符串是否存在两个,很显然是没有的,所以c的下标是0
同理第四个字符也是0
从第五个字符就不一样了,从a开始从e结束相同的字符串没有
我们看第八个字符,前面一个字符以a开始b结束的相同字符串是否有两个,我们可以找到两个(我做红色标记的),(必须a为首,b为尾巴,找到两个相同的字符串),而且这两个相同的字符串的长度为2所以我们把字符c的下标记为2,后面就同理了
 0     1   2   3    4   5  6    7    8    9     10   11  12  13
 a     b   c   a    e   f   a    b    c     d     a     b    s    d


  主串        p[i]      0     1   2   3    4   5  6    7    8    9    10  11  12  13
                            a     b   c   a    e   f   a    b    c    d     a     b    s    d

 回退数组next    -1    0   0   0    0   0   0    1    2    3     0     1    0    0

             子串s[j]   a    b   c   d
 这个回退是针对子串而言的,我们定义i为主串下标,j为子串下标

 i从0开始遍历,j也从0开始,第一个都是a,都往后走i++,j++,然后到第4个字符发现不一样,那么怎么办呢?这个时候就开始用到了next数组了,next[j]赋给j,也就是回退到子串的最开始的字符s[0],因为主串中的第四个字符a对于的next数组下标为0,然后此时的j=-1,怎么办数组越界了,这个时候我们需要把-1回正为0,又可以从子串的第一个字符开始遍历了,直到主串中第7个字符,对于的next数组下标为1 ,我们对于的子串需要回退到下标为1的字符,也就是s[1],然后继续进行到主串第9个字符对于的next数组下标为3,这时把子串回退到s[3],也就是字符d,又开始遍历到第10个字符a,发现前面一个字符9正好就是对应子串最后一个字符,所以我们要返回找到的子串在主串的下标位置,也就是i-j,为什么呢?你看这时的i是10,j是4,对应的返回值是6不就是主串中第6个字符a开始,到第9个字符d就是子串吗?

上面字面意思理解了的话我们开始设计程序了

问题1:如何设计next数组

问题2:子串的字符怎么设计程序回退呢?

下面解决问题1:如何设计next数组

首先如果我们已经直到next[0]=-1,next[1]=0,怎么推next[2]呢?

解析如下:

  主串        p[i]      0     1   2   3    4   5  6    7    8    9    10  11  12  13
                            a     b   c   a    e   f   a    b    c    d     a     b    s    d

 回退数组next    -1     0   0   0    0   0   0    1    2    3     0     1    0    0

 代表回退数组的对应的数字

 我们先假设主串遍历到了第九个字符,但是我不知道第9个字符对应的next数组的数字,我   们的第8个字符的上标为8,然后对应的k是2,这里的p[2]是c,说明p[2]和p[8]相等啊,那么

p[9]的下标怎么求呢?next[9]=2+1,你们看对不对

然后再举个例子:

如果我们要找p[10]的下标呢?同上,先看p[9]的下标为3,对吧,我们看p[9]和p[3],发现根本不相等,怎么办呢?那么我们就从p[3]对应的next值入手,p[3]对应的next值为0,我们再回退到0,发现第p[0]与p[10]相等,这里p[0]的next的值就是对应的p[10]next值+1啦!

下面总结:

我们能不能发现一个规律,如果          p[i-1]==p[k]  那么就有next[i]=k+1

如果不相等,就是不断回退,如果回退到第一个字符还是不相等那么此时对应的下标就是0

问题2:子串的字符怎么设计程序回退呢?

代码如下:k=next,就是字符不相等的时候回退上一个对应next值的主串上标的字符。

//str代表主串
//sub代表子串
//pos代表主串开始移动的位置
//我们定义主串第一个字符下标为-1,第二个就为0
//len是主串的长度
void Getnext(char* str, int* next, int len)
{
	next[0] = -1;;//我们定义主串第一个字符对应的next数组第一个元素为-1
	next[1] = 0;//第二个就为0
	//从第三个开始找next数组的规律
	int i = 2;//第三个元素的标号
	int k = 0;
	while (i < len)
	{
		if (k==-1||str[i - 1] == str[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}

核心知识点就上面这些了,如果看明白的话,大家先根据自己的理解设计一下,下面就是源码了,有注释

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
//str代表主串
//sub代表子串
//pos代表主串开始移动的位置
//我们定义主串第一个字符下标为-1,第二个就为0
void Getnext(char* str, int* next, int len)
{
	next[0] = -1;;//我们定义主串第一个字符对应的next数组第一个元素为-1
	next[1] = 0;//第二个就为0
	//从第三个开始找next数组的规律
	int i = 2;//第三个元素的标号
	int k = 0;
	while (i < len)
	{
		if (k==-1||str[i - 1] == str[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}
int KMP(char* str,char* sub,int pos)
{
	assert(str&&sub);
	int i = pos;//遍历主串
	int j = 0;//遍历子串
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	//此时都空的字符串
	if (lenstr == 0 || lenstr == 0) return -1;
	//此时如果遍历主串的pos<0或者大于等于主串长度,也是返回-1
	if (pos < 0 || pos >= lenstr) return -1;
	int* next = (int*)malloc(lenstr * sizeof(int));//主串的元素个数和next数组相同
	assert(next);
	Getnext(str,next,lenstr);
	while(i<lenstr&&j<lenstr)
	{
		if (j==-1||str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	//遍历完之后
	if (j >= lensub)
		return i - j;//返回主串找到子串第一个字符的下标
	//如果没有找到返回0
	return 0;
}
int main()
{
	printf("%d", KMP("abdaabc","abc",0));
	return 0;
}

上面代码的next数组还有优化空间:

不断回退的次数,我们怎么改成一次性回退呢?

下面我们来设计nextval数组

void Getnext(char* str, int* next, int* nextval,int len)
{
	next[0] = -1;;//我们定义主串第一个字符对应的next数组第一个元素为-1
	next[1] = 0;//第二个就为0
	//从第三个开始找next数组的规律
	int i = 2;//第三个元素的标号
	int k = 0;
	while (i < len)
	{
		if (k==-1||str[i - 1] == str[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
	//开始设计nextval数组
	i = 2;
	nextval[0] = -1;
	nextval[1] = 0;
	while (i < len)
	{
		//相同的话就回退到那一个nextval的值
		if (str[i] == str[next[i]])
		{
			nextval[i] = next[next[i]]; 
			i++;
		}
		//不同的话,就等于next的值
		else
		{
			nextval[i] = next[i];
			i++;
		}
	}
}

最后补充一些KMP算法的时间复杂度O(M+N)

M是主串的长度,N是子串的长度

BF算法的时间复杂度是O(M*N)

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

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

相关文章

ubuntu安装tomcat并配置前端项目

1.1查找 # 先更新 sudo apt update # 查找 apt search jdk1.2安装 sudo apt install openjdk-8-jdk1.3验证 java -version 2.安装tomcat 下载链接&#xff1a;Apache Tomcat - Apache Tomcat 8 Software Downloadshttps://tomcat.apache.org/download-80.cgi下载这个&…

layui+ssm实现数据批量删除

layuissm实现数据的批量删除 //数据表格table.render({id: adminList,elem: #adminList,url: ctx "/admin/getAdminList", //数据接口cellMinWidth: 80,even: true,toolbar: #toolbarDemo,//头部工具栏limit: 10,//每页条数limits: [10, 20, 30, 40],defaultToolba…

JS生成登录验证码

采用js生成登录的验证码 采用的技术点有html&#xff0c;css&#xff0c;JS&#xff0c;jQuery HTML&#xff1a; <div class"box_b"><img src"./img/0775639c-c82c-4a29-937f-d2a3bae5151a.png" alt""><div class"regist…

4G基站BBU、RRU、核心网设备

目录 前言 基站 核心网 信号传输 前言 移动运营商在建设4G基站的时候&#xff0c;除了建设一座铁塔之外&#xff0c;更重要的是建设搭载铁塔之上的移动通信设备&#xff0c;这篇博客主要介绍BBU&#xff0c;RRU以及机房的核心网等设备。 基站 一个基站有BBU&#xff0c;…

Leetcode—1038.从二叉搜索树到更大和树【中等】

2023每日刷题&#xff08;四十九&#xff09; Leetcode—1038.从二叉搜索树到更大和树 算法思想 二叉搜索树的中序遍历&#xff08;左根右&#xff09;结果是一个单调递增的有序序列&#xff0c;我们反序进行中序遍历&#xff08;右根左&#xff09;&#xff0c;即可以得到一…

Redis系列之incr和decr命令是线程安全的?

Redis是一个单线程的服务&#xff0c;所以正常来说redis的命令是会排队执行的。incr/decr命令是redis提供的可以实现递增递减的命令&#xff0c;所以这两个命令也是具有原子性的&#xff1f;是线程安全的&#xff1f;这个也是互联网公司面试的常见题&#xff0c;话不多说&#…

linux 命令 tmux 用法详解

一、tmux 解决的痛点&#xff08;screen命令一样可以解决&#xff0c;但是tmux功能更强大&#xff09; 痛点一&#xff1a;大数据传输的漫长一夜 相信做过 Linux 服务运维的同学&#xff0c;都用 scp 进行过服务器间的大文件网络传输。一般这需要很长的时间&#xff0c;这期间…

react结合vant的Dialog实现签到弹框操作

1.需求 有时候在开发的时候&#xff0c;需要实现一个签到获取积分的功能&#xff0c;使用react怎么实现呢&#xff1f; 需求如下&#xff1a; 1.当点击“签到”按钮时&#xff0c;弹出签到框 2.展示签到信息&#xff1a; 签到天数&#xff0c; 对应天数签到能够获取的积分&…

封装时间轴组件 timeline

要求时间轴的点展示进度百分比&#xff0c;线也根据进度不同展示不同长度的颜色 实现效果&#xff1a; 使用的组件库是vant的circle 子组件&#xff1a; <template><div class"m-timeline-area" :style"width: ${width}px"><div class&qu…

XXL-Job详解(五):动态添加、启动任务

目录 前言XXL-Job API接口添加任务API动态添加任务动态启动任务 前言 看该文章之前&#xff0c;最好看一下之前的文章&#xff0c;比较方便我们理解 XXL-Job详解&#xff08;一&#xff09;&#xff1a;组件架构 XXL-Job详解&#xff08;二&#xff09;&#xff1a;安装部署 X…

SQLserver通过字符串中间截取然后分组

当我们存的数据是json的时候可以全部取出在模糊查询但是有多个重复数据的时候就没办法准确的模糊出来这个时候我们就需要用的字符串截取 --创建函数create FUNCTION [dbo].[Fmax] (str varchar(50),start VARCHAR(50),length VARCHAR(50)) RETURNS varchar(max) AS BEGINDEC…

如何使用Cloudreve搭建本地云盘系统并实现随时远程访问

文章目录 1、前言2、本地网站搭建2.1 环境使用2.2 支持组件选择2.3 网页安装2.4 测试和使用2.5 问题解决 3、本地网页发布3.1 cpolar云端设置3.2 cpolar本地设置 4、公网访问测试5、结语 1、前言 自云存储概念兴起已经有段时间了&#xff0c;各互联网大厂也纷纷加入战局&#…

nginx部署和安装-后端程序多端口访问-后端代理设置

部分补充 查看nginx是否安装http_ssl_module模块 ./nginx -V 看到有 configure arguments: --with-http_ssl_module, 则已安装。 如果没有安装&#xff1a;参考文档 nginx官网地址&#xff1a;nginx: download 这里下载nginx-1.18.0稳定版tar.gz 下载后&#xff0c;利用…

失落的艺术:无着色器3D渲染

假设你想创建一个甜蜜的弹跳立方体&#xff0c;如下所示&#xff1a; 一个弹跳的立方体 你可以使用 3D 框架&#xff0c;例如 OpenGL 或 Metal。 这涉及编写一个或多个顶点着色器来变换 3D 对象&#xff0c;以及编写一个或多个片段着色器来在屏幕上绘制这些变换后的对象。 然…

Docker Image(镜像)——5

目录&#xff1a; Docker 镜像是什么镜像生活案例镜像分层生活案例为什么需要镜像镜像命令详解 镜像命令清单docker imagesdocker tagdocker pulldocker pushdocker rmidocker savedocker loaddocker historydocker importdocker image prunedocker build镜像操作案例 查找镜像…

为什么Nginx被称为反向代理

下图显示了 &#x1d41f;&#x1d428;&#x1d42b;&#x1d430;&#x1d41a;&#x1d42b;&#x1d41d; &#x1d429;&#x1d42b;&#x1d428;&#x1d431;&#x1d432; 和 &#x1d42b;&#x1d41e;&#x1d42f;&#x1d41e;&#x1d42b;&#x1d42c;&#…

如何制定公司网络安全战略

网络安全可以保护公司的重要信息免受恶意软件和数据泄露等威胁。网络安全策略列出了您公司的 IT 系统当前面临的风险、您计划如何预防这些风险&#xff0c;以及如果发生这些风险该怎么办。 让本文成为您制定有效网络安全策略的一站式指南。我们将讨论网络安全风险评估以及策略…

LeetCode 1212 查询球队积分(PostgreSQL)

数据准备 Create table If Not Exists Teams (team_id int, team_name varchar(30)) Create table If Not Exists Matches (match_id int, host_team int, guest_team int, host_goals int, guest_goals int) Truncate table Teams insert into Teams (team_id, team_name) va…

创新领航 | 竹云参编《基层智治系统安全接入规范》团体标准正式发布!

近日&#xff0c;由杭州市委办公厅&#xff08;市密码管理局&#xff09;、杭州市基层治理综合指挥保障中心、杭州市拱墅区社会治理中心、杭州市拱墅区数据资源管理局、杭州竹云数字智能科技有限公司、杭州智诚质量标准技术评定中心共同参与编写的《基层智治系统安全接入规范》…

01、pytest:帮助你编写更好的程序

简介 ​pytest框架可以很容易地编写小型、可读的测试&#xff0c;并且可以扩展以支持应用程序和库的复杂功能测试。使用pytest至少需要安装Python3.7或PyPy3。PyPI包名称为pytest 一个快速的例子 content of test_sample.py def inc(x):return x1def test_ansewer():assert i…