直观清晰的带你了解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数组第一个元素为-1next[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数组第一个元素为-1next[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或者大于等于主串长度,也是返回-1if (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;//返回主串找到子串第一个字符的下标//如果没有找到返回0return 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数组第一个元素为-1next[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.hqwc.cn/news/247390.html

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

相关文章

Apache solr XXE 漏洞(CVE-2017-12629)

任务一&#xff1a; 复现环境中的漏洞 任务二&#xff1a; 利用XXE漏洞发送HTTP请求&#xff0c;在VPS服务器端接受请求&#xff0c;或收到DNS记录 任务三&#xff1a; 利用XXE漏洞读取本地的/etc/passwd文件 1.搭建环境 2.开始看wp的时候没有看懂为什么是core&#xff0c;然…

Linux中的网络配置

本章主要介绍网络配置的方法 网络基础知识查看网络信息图形化界面修改通过配置文件修改 1.1 网络基础知识 一台主机需要配置必要的网络信息&#xff0c;才可以连接到互联网。需要的配置网络信息包括IP、 子网掩码、网关和 DNS 1.1.1 IP地址 在计算机中对IP的标记使用的是3…

哈希与哈希表

哈希表的概念 哈希表又名散列表&#xff0c;官话一点讲就是&#xff1a; 散列表&#xff08;Hash table&#xff0c;也叫哈希表&#xff09;&#xff0c;是根据关键码值(Key value)而直接进行访问的数据结构。也就是说&#xff0c;它通过把关键码值映射到表中一个位置来访问记…

中缀表达式转后缀表达式(详解)

**中缀表达式转后缀表达式的一般步骤如下&#xff1a; 1&#xff1a;创建一个空的栈和一个空的输出列表。 2&#xff1a;从左到右扫描中缀表达式的每个字符。 3&#xff1a;如果当前字符是操作数&#xff0c;则直接将其加入到输出列表中。 4&#xff1a;如果当前字符是运算符&a…

dll动态链接库【C#】

1说明: 在C#中,dll是添加 【类库】生成的。 2添加C#的dll: (1)在VS中新建一个Windows应用程序项目,并命名为TransferDll。 (2)打开Windows窗体设计器,从工具箱中为窗体添加相应的控件。 (3)在该应用程序的“解决方案资源管理”中的“引用”文件上单击鼠标右键, 在…

10、外观模式(Facade Pattern,不常用)

外观模式&#xff08;Facade Pattern&#xff09;也叫作门面模式&#xff0c;通过一个门面&#xff08;Facade&#xff09;向客户端提供一个访问系统的统一接口&#xff0c;客户端无须关心和知晓系统内部各子模块&#xff08;系统&#xff09;之间的复杂关系&#xff0c;其主要…

STM32通用定时器

本文实践&#xff1a;实现通过TIM14_CH1输出PWM&#xff0c;外部显示为呼吸灯。 通用定时器简介 拥有TIM2~TIM5、TIM9~TIM14 一共10个定时器&#xff0c;具有4路独立通道&#xff0c;可用于输入捕获、输出比 较&#xff0c;同时包含了基本定时去的所有功能。 通用定时器的结…

【Linux | 编程实践】防火墙 (网络无法访问)解决方案 Vim常用快捷键命令

&#x1f935;‍♂️ 个人主页: AI_magician &#x1f4e1;主页地址&#xff1a; 作者简介&#xff1a;CSDN内容合伙人&#xff0c;全栈领域优质创作者。 &#x1f468;‍&#x1f4bb;景愿&#xff1a;旨在于能和更多的热爱计算机的伙伴一起成长&#xff01;&#xff01;&…

字符串函数strlen的用法详解及其相关题目

strlne函数的使用 一.strlen函数的声明二.strlen函数的头文件三.相关题目代码1代码2题目1题目2题目3题目4题目5题目6 一.strlen函数的声明 size_t strlen ( const char * str );二.strlen函数的头文件 使用strlen函数我们需要使用以下头文件 #include <string.h>三.相…

景联文科技解读《2023人工智能基础数据服务产业发展白皮书》,助力解决数据标注挑战

前段时间&#xff0c;国家工业信息安全发展研究中心发布《2023人工智能基础数据服务产业发展白皮书》&#xff08;以下简称“白皮书”&#xff09;。 《白皮书》指出&#xff0c;2022年&#xff0c;中国人工智能基础数据服务产业的市场规模为45亿元&#xff0c;预计今年将达到5…

一篇解析context_switch进程切换(针对ARM体系架构)

一. 概述 在最近初学ebpf时&#xff0c;使用到了挂载点finish_task_switch统计内核线程的运行时间&#xff0c;遂进入内核源码对其进行学习分析。 finish_task_switch在context_switch被调用&#xff0c;其功能是完成进程切换的收尾工作&#xff0c;比如地址空间的清理。而co…

pycharm打断点调试

在PyCharm中使用断点调试可以帮助逐行执行代码并查看变量的值&#xff0c;以便更好地理解程序的执行过程。以下是在PyCharm中设置断点和使用调试功能的步骤和注意事项&#xff1a; 步骤&#xff1a; 打开PyCharm并打开要调试的项目。找到要设置断点的代码行。您可以在行号区…