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

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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

编写并调试运行一个简单的 Java 应用程序,显示自己的学号、姓名、兴趣爱好等。

源代码&#xff1a; public class Main { public static void main(String[] args) { System.out.println("学号是:""0233217821"); System.out.println("姓名是:""赵港"); System.out.println("兴趣爱好是:""运动&qu…

【若依框架实现上传文件组件】

若依框架中只有个人中心有上传图片组件&#xff0c;但是这个组件不适用于el-dialog中的el-form表单页面 于是通过elementui重新写了一个上传组件&#xff0c;如图是实现效果 vue代码 <el-dialog :title"title" v-model"find" width"600px"…

基于Eclipse+Mysql+Tomcat开发的 教学评价管理系统

基于EclipseMysqlTomcat开发的 教学评价管理系统 项目介绍&#x1f481;&#x1f3fb; 随着教育信息化的发展&#xff0c;教学评价管理系统已经成为了学校、教育机构等场所必不可少的一部分。本项目是基于EclipseMysqlTomcat开发的一套教学评价管理系统&#xff0c;旨在帮助教育…

Linux系统上RabbitMQ安装教程

一、安装前环境准备 Linux&#xff1a;CentOS 7.9 RabbitMQ Erlang 1、系统内须有C等基本工具 yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel tk tc xz socat2、下载安装包 1&#xff09;首先&a…

C++12.4

沙发床的多继承 多继承代码实现沙发床沙发床继承于沙发和床 代码&#xff1a; #include <iostream>using namespace std;//封装 沙发 类 class Sofa { private:string sitting;double *size; public://无参构造函数Sofa() {cout << "Sofa::无参构造函数&quo…

JAVAEE初阶相关内容第十六弹--网络原理之TCP_IP

目录 1. TCP-IP五层模型 2. UDP协议 2.1 特点 2.2 UDP协议端格式 2.3 校验和 3. TCP协议 3.1 特点 3.2 TCP协议段格式 3.2.1 首部长度 3.2.2 选项 3.2.3 保留6位 3.3 TCP内部的工作机制 3.3.1 确认应答 &#xff08;1&#xff09;应答报文ack &#xff08;2&…

【Filament】Filament环境搭建

1 前言 Filament 是一个实时物理渲染引擎&#xff0c;用于 Android、iOS、Linux、macOS、Windows 和 WebGL 平台。该引擎旨在提供高效、实时的图形渲染&#xff0c;并被设计为在 Android 平台上尽可能小而尽可能高效。Filament 支持基于物理的渲染&#xff08;PBR&#xff09;&…

【Vue】使用 Vue CLI 脚手架创建 Vue 项目(使用GUI创建)

前言 在开始使用Vue进行开发之前&#xff0c;我们需要先创建一个Vue项目。Vue CLI&#xff08;Command Line Interface&#xff09;是一个官方提供的脚手架工具&#xff0c;可以帮助我们快速创建Vue项目。Vue CLI也提供了一个可视化的GUI界面来创建和管理Vue项目。 步骤 打开终…

接口获取数据控制台打印有值但是展开又没有了

谷歌浏览器只会展现响应式数据最后的结果&#xff0c;证明原来接口是有值的&#xff0c;后面对这个数据进行操作后&#xff0c;最终没有值了。所以对数据进行操作时最好对数据进行一次深拷贝 JSON.parse(JSON.stringify(data))

任意文件上传漏洞实战和防范

文件上传漏洞广泛存在于Web1.0时代&#xff0c;恶意攻击者的主要攻击手法是将可执行脚本&#xff08;WebShell&#xff09;上传至目标服务器&#xff0c;以达到控制目标服务器的目的。 此漏洞成立的前提条件至少有下面两个&#xff1a; 1.可以上传对应的脚本文件&#xff0c;…

图解系列--HTTPS,认证

确保 Web 安全的HTTPS 1.HTTP 的缺点 1.1.通信使用明文可能会被窃听 加密处理防止被窃听 加密的对象可以有这么几个。 (1).通信的加密 HTTP 协议中没有加密机制&#xff0c;但可以通过和 SSL&#xff08;Secure Socket Layer&#xff0c;安全套接层&#xff09;或TLS&#xff…

Redis持久化及常见问题解决

持久化缓存雪崩缓存穿透缓存击穿缓存预热 持久化 Redis的储存形式&#xff1a;一份在内存、一份在磁盘。内存的是最新的&#xff1b;磁盘里的会隔一段时间更新。 Redis持久化方式&#xff1a; RDB:快照方式&#xff1b;将某⼀个时刻的内存数据&#xff0c;以⼆进制的⽅式写⼊…

Vue框架学习笔记——列表渲染:v-for

文章目录 前文提要代码正文 前文提要 本人仅做个人学习记录&#xff0c;如有错误&#xff0c;请多包涵 主要学习链接&#xff1a;尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 代码正文 <body><div id"box"><ul><li v-for"(p,index)…

基于Python自动化测试框架之接口测试

前段时间由于公司测试方向的转型&#xff0c;由原来的web页面功能测试转变成接口测试&#xff0c;之前大多都是手工进行&#xff0c;利用postman和jmeter进行的接口测试&#xff0c;后来&#xff0c;组内有人讲原先web自动化的测试框架移驾成接口的自动化框架&#xff0c;使用的…

leetcode算法之栈

目录 1.删除字符串中的所有相邻重复项2.比较含退格的字符串3.基本计算器II4.字符串解码5.验证栈序列 1.删除字符串中的所有相邻重复项 删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {string ret;//使用数组模拟栈操作for(auto …

龙迅#LT6911GX是一款高性能HDMI2.1至MIPI或LVDS芯片,支持图像处理,DSC压缩和嵌入式LPDDR4 旋转功能!

1.描述 应用功能&#xff1a;LT6711GX适用于HDMI2.1转MIPICSI/DSI&#xff1b;HDMI2.1转LVDS&#xff0c;支持高刷模式&#xff0c;带HDCP 方案&#xff01; 分辨率&#xff1a;最高支持8K30HZ 工作温度范围&#xff1a;−40C to 85C 产品封装&#xff1a;BGA169&#xff08;9*…

clip-path,css裁剪函数

https://www.cnblogs.com/dzyany/p/13985939.html clip-path - CSS&#xff1a;层叠样式表 | MDN 我们看下这个例子 polygon里有四个值分别代表这四个点相对于原图左上方的偏移量。 裁剪个五角星

【跨境营商】创新科技助力数码转型 增强大湾区企业核心竞争力

粤港澳大湾区作为国家的重点发展区域&#xff0c;坐拥丰富的资源及商机&#xff0c;企业积极推行数码化&#xff0c;务求在大湾区抢占先机。香港电讯商业客户业务董事总经理吴家隆表示&#xff0c;近年企业锐意加快数码化步伐&#xff0c;香港电讯以创新科技融入的数码方案&…

解密Prompt系列20. LLM Agent之再谈RAG的召回多样性优化

几个月前我们就聊过RAG的经典方案解密Prompt系列14. LLM Agent之搜索应用设计。前几天刚看完openAI在DevDay闭门会议上介绍的RAG相关的经验&#xff0c;有些新的感悟&#xff0c;借此机会再梳理下RAG相关的优化方案。推荐直接看原视频&#xff08;外网&#xff09;A Survey of …

Fiddler的配置、原理和使用

一、Fiddler的工作原理 本地应用与服务器之间所有的请求&#xff08;request&#xff09;和响应&#xff08;response&#xff09;&#xff0c;由fiddler进行转发&#xff0c;此时fiddler以代理服务器的方式存在。 由于所有的网络数据都要经过fiddler&#xff0c;因此&#xf…