kmp算法超详细

在计算机科学中,字符串匹配是一个常见的问题。给定一个文本串和一个模式串,我们需要在文本串中找到所有与模式串匹配的位置。传统的字符串匹配算法如暴力匹配(Brute Force)方法在最坏情况下的时间复杂度为O(m*n),其中m和n分别是文本串(长的字符串)和模式串(短的字符串)的长度,kmp算法是一种高效的字符串匹配算法。

废话不多说我们直接介绍重点,带你理解kmp算法

1. kmp 算法原理 

为什么暴力匹配这么慢?

我们发现当每次匹配失败后,bf算法都会让 文本串(长的字符串)后退到匹配的第一个字符的下一个字符,让模式串(短的字符串)后退到第一个字符,重新开始匹配,例如:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
a b c a b d
0 1 2 3 4 5
当我们使用bf算法时,在 5 下标位置匹配失败时,会让 文本串 后退到 1 下标 ,让模式串后退到 0 下标,重新开始匹配,但其实我们发现:文本串的 1 下标和 模式串的 0 下标其实并不匹配,其实大可跳过,文本串的 1 下标,如果重新匹配,我们发现,只有从文本串的 3 位置开始匹配才可能成功,kmp算法对于bf算法的优化就是在于,跳过了那些一定匹配不上的位置。

 kmp的算法核心在于,让文本串不后退:

如上述例子,我们在5位置匹配失败了,此处不让文本串后退,只让 模式串 后退,我们发现,文本串,的 3 4 下标是和模式串的 0 1 下标匹配的,所以我们可以让,让模式串后退到 2 下标位置,与 文本串 5 下标位置 进行比较:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d 
         a b c a b d
         0 1 2 3 4 5

即相当于,我们跳过了用 模式串 与 1 位置, 和 2 位置 的比较,因为这两个位置匹配一定是失败的,也跳过了 ,模式串 的 0 1 下标和,文本串的 3 4 下标的比较,因为我们知道一定是成功的,所以直接从模式串的 3 位置与文本串的 5 位置开始匹配

我们要如何知道 模式串 回退的位置?靠眼睛看肯定是不行的

如果上面的内容没有看懂,没关系,请重点理解下面的内容:

2. 最长前缀后缀

在模式串中,如果一个子串的前缀和后缀相同,则称该子串为前缀后缀。例如,模式串"a b c a b "的前缀有"a"、"ab"、"abc"、"abca"、"abca",后缀有"b"、"ab"、"cab"、"bcab"、"abca"。 
那么"abcab"的最长前缀后缀不就是 "ab"吗。

注意:最长前缀后缀不能是这个字串本身

练习一下:
"abcdbcabcd"的最长前后缀是?
没错,是 "abcd"。现在你已经会求最长前后缀了,现在我们可以解决,模式串回退的位置的问题了,这是最关键的一步。
以刚才的例子来说:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
a b c a b d
0 1 2 3 4 5
现在我们发现了不匹配的地方,根据kmp算法,我们只回退模式串,要知道回退的位置,我们刚才的最长前缀后缀就有用处了。我们发现:前面绿色的代码匹配成功的字串。
我们现在把这两个字串的最长前缀后缀标出:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
a b c a b d
0 1 2 3 4 5

现在你发现了什么。
没错,模式串中匹配成功的字串的 0 1 下标 和 3 4 下标互为最长前缀后缀,即 0 1 下标 的字符与 3 4 下标的字符相等,即模式串的的0 1 下标与 文本串中的 3 4 下标 相等,所以我们移动模式串,让模式串已经匹配成功的字串的 前缀 与 文本串中的后缀对应:

0 1 2 3 4 5 6 7 8 9 10
a b c a b a b c a b d
         a b c a b d
         0 1 2 3 4 5
于是我们得出,模式串匹配失败后的回退的位置 为 最长前后缀的 前缀 的后一个位置,也就是前缀/后缀的长度当前例子为 2 

如果理解了这一步,kmp 的关键你就已经掌握了。
接下来就是一个重复的过程了,模式串中每一个字符的前面的字串都有最长前缀后缀,而且最长相等前后缀的长度是我们移位的关键,所以我们用一个next数组记录下每个字符前面的字串的最长前缀后缀的长度,即在该字符匹配失败后模式串回退的位置。
例如:a b c a b d 的next数组为:
下标:          0 1 2 3 4 5 
模式串:       a b c a b d
next数组:   -1 0 0 0 1 2
注意:在第一个位置 为 -1 做特殊处理,下面会详细讲解。最长前后缀为空字符串即长度为0。

下面做一个练习:
abcababcabca的next数组为?

 

答案:

-1 0 0 0 1 2 1 2 3 4 5 3

3. 计算next 数组

 将next数组用代码计算出来:

我们发现,next 的值一定是序数递增的,不会由 0 直接到2,只能先到1 再到2。

举个例子:
 0 1 2 3 4 5
 a b c a b d
-1 0 0 0 1 2

5 位置前的字串 最长前缀后缀为 a b 长度为2,那 4 前面的字串的最长前缀后缀一定为 a 
如果4 前面 的最长前缀后缀为 0 即 0 位置的字符,与 3 位置的字符没有匹配上,  5 前面的字串的 最长前缀后缀 长度要为2 的话只能是 0 1小标和3 4 下标,所以0 3下标必须是匹配的 长度才可能为2。

那么,我们求 next数组,是不是就可以只判断当前的前一个字符是不是和他的最长前缀后缀的长度的对应位置的字符是否相等,如果相等,则当前的next值即为前一个值+1.

举个例子:

 0  1  2  3  4  5  6  7  8  9 10 11
 a  b  c  a  b  a  b  c  a  b   c   a
-1  0  0  0

现在要求4下标的next值,我们判断 4 下标的前一个字符 即 3下标的 a 是否和 它的 最长前缀后缀的长度 即对应的next值 即 0 位置的元素 即 a 是否相等 ,显然 a 与 a 相等,所以 4 下标的值应为 
0 + 1 = 1
 0  1  2  3  4  5  6  7  8  9 10 11
 a  b  c  a  b  a  b  c  a  b   c   a
-1  0  0  0  1    

同理,5下标 只需判断 4 下标 下的字符 是否等于 1 下标的字符,显然相等,所以 5 下标下的值应该为 1 + 1 = 2。

 0  1  2  3  4  5  6  7  8  9 10 11
 a  b  c  a  b  a  b  c  a  b   c   a
-1  0  0  0  1  2  

那如果比较的字符不相等呢?

 0  1  2  3  4  5  6  7  8  9 10 11
 a  b  c  a  b  a  b  c  a  b   c   a
-1  0  0  0  1  2  
1  2  3  4   5    
如现在求11下标的next值,我们比较 10 下标的 字符和 5 下标的字符 发现并不相等。
注意!!此时我们继续 判断 5 下标下的 next 的值 即 2 对应位置的字符是否和 10 下标下的字符相等,显然 c == c ,所以 10 下标下的 next 值即为 2 + 1 = 3.

以下为证明过程,理解不了直接记下结论即可

解释:因为我们尝试在 11 的前面找比 5 更长的最长前后缀,显然 匹配失败了,代表找不到,所以我们,继续向前,找“最长前后缀的前后缀的长度”,即在 abcab中找最长前缀后缀,即 5 下标下的next 值 2,由于 10 下标前的 最长前缀后缀的长度是知道的 ,即 5 ,说明 0 到 4 下标与 5 到 9 下标是匹配的,先找到 0 到 4 下标字串的最长前缀后缀 的长度为 ,2 那说明 0 1 下标 与 2 4 下标匹配,又因为 0 4 下标与 5 9 下标匹配 ,所以 ,0 1 下标 与 8 9 下标匹配,所以我们直接判断,2 下标与10 下标下的字符是否相等,相等则该下标对应的next 值应为 2 + 1 = 3. 如果不相等 则 继续 用 2 对应的 next 值往下找直到 为 -1 时做特殊处理

代码实现:

c语言版:

int* getNext(char* s, int len)
{//申请一块内存用于返回next数组int* next = (int*)malloc(4 * len);next[0] = -1; //处理特殊值,以便代码实现next[1] = 0; //第二个字符匹配失败一定是回到 0 位置开始比较int k = 0; // 代表前一个下标的 next 值,我们从2开始,所以k初始为next[1] = 0int i = 2;while (i < len){//判断当前位置前一个字符是否等于 k 位置的 字符if (s[i - 1] == s[k]){//相等则当前位置的next值为 k + 1next[i] = k + 1;//让 i++i++;//让k的值更新k++;}else{//匹配失败,让当前字符与,next[k] 下标下的字符进行比较//我们这里直接更新k的值,在下一次循环比较,注意不需要 i++k = next[k];}}return next;
}

注意如果一直匹配失败会导致 k 的值 为 -1 导致 上面 数组越界
所以我们在上方的if中做特殊处理

int* getNext(char* s, int len)
{int* next = (int*)malloc(4 * len);next[0] = -1; //处理特殊值,以便代码实现next[1] = 0; //第二个字符匹配失败一定是回到 0 位置开始比较int k = 0; // 代表前一个下标的 next 值,我们从2开始,所以k初始为next[1] = 0int i = 2;while (i < len){//判断当前位置前一个字符是否等于 k 位置的 字符,注意处理 k == -1if (k == -1 || s[i - 1] == s[k]){//相等则当前位置的next值为 k + 1next[i] = k + 1;//让 i++ 进入下次循环继续获取next数组i++;//让k的值更新k++;}else{//匹配失败,让当前字符与,next[k] 下标下的字符进行比较//我们这里直接更新k的值,在下一次循环比较,注意不需要 i++k = next[k];}}return next;
}

我们让k等于-1的时候也进入if内部,当k等于-1说明,当前i位置的字符前面最长前缀后缀的长度为0,此时我们给 next[i] 赋的值为 -1 + 1 = 0,所以这就是我们为什么要把next[0] 设置为 -1 

现在我们的 kmp 算法以及完成大半了,只需再写一个 函数用来比较两个字符串,在匹配失败的时候用next数组找到 模式串回退的位置,然后继续比较即可。

代码实现:

int KMP(char* s1, char* s2, int len1, int len2)
{int* next = getNext(s2, len2);int i = 0;int j = 0;while (i < len1 && j < len2){//注意处理j为-1的情况if (j == -1 || s1[i] == s2[j]) {i++;j++;}else{j = next[j];}}if (j == len2){//j == len2 说明匹配成功了,返回s1中匹配成功时,匹配的第一个字符的位置return i - j;}//匹配失败返回-1return -1;
}

至此我们的 kmp 算法就完成了,如果觉得对你有帮助的话,请给一个免费的赞,有什么问题也可以在下方讨论。

Java代码:
 

    //求next数组public static int[] getNext(String s) {int n = s.length();int[] next = new int[n];next[0] = -1;//处理特殊值,以便代码实现int i = 2;//第二个字符匹配失败一定是回到 0 位置开始比较所以直接从 2 位置开始int k = 0; // 代表前一个下标的 next 值,我们从2开始,所以k初始为next[1] = 0while (i < n) {//判断当前位置前一个字符是否等于 k 位置的 字符,注意处理 k == -1if(k == -1 || s.charAt(i-1) == s.charAt(k)) {//相等则当前位置的next值为 k + 1next[i] = k + 1;//让k的值更新k++;//让 i++ 进入下次循环继续获取next数组i++;}else {//匹配失败,让当前字符与,next[k] 下标下的字符进行比较//我们这里直接更新k的值,在下一次循环比较,注意不需要 i++k = next[k];}}return next;}public static int KMP(String s1, String s2) {int n = s1.length();int m = s2.length();int i = 0;int j = 0;int[] next = getNext(s2);while(i < n && j < m) {//注意处理 j == -1if(j == -1 || s1.charAt(i) == s2.charAt(j)) {i++;j++;}else{j = next[j];}}if(j == m) {return i - j;}return -1;}

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

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

相关文章

Java实现minio

配置Dapplication.yml minio:access-key: minioadminsecret-key: minioadminbucket-name: file #指定桶名称endpoint: http://localhost:9000 实现代码minioContriller.java package com.setsail.setsailcusserver.controller;import com.alibaba.fastjson.JSONObject; impo…

万界星空科技五金家具行业MES解决方案

MES系统如何与家具企业生产相匹配&#xff1f;相较于其它大多数工业软件&#xff0c;MES系统无疑是受企业欢迎的软件之一。MES系统处于制造生产企业信息化的核心领域&#xff0c;有着承上启下的作用。那MES系统如何与家具企业生产相匹配&#xff1f; 五金家具行业的工艺特点&am…

最简单的pixel刷机和安装面具、lsposed

一 下载手机对应的系统 1&#xff0c;手机usb连接然后重启进入Fastboot模式&#xff1a;adb reboot bootloader2&#xff0c;找到你下载的系统&#xff0c;Windows 系统 直接运行 flash-all.bat上图 &#xff1a;左边就是安卓11和12的系统&#xff0c;右边是对应的手机型号 下…

mysql:修改整数字段的显式长度不生效

例如&#xff0c;我使用mysql 8.2.0版本&#xff0c;想修改整数字段的显式长度&#xff0c;不会生效&#xff0c;提醒整数的显示长度已经废弃&#xff0c;会在将来某个版本去掉&#xff1a; mysql官网中也有说明&#xff1a; https://dev.mysql.com/doc/refman/8.2/en/numeric…

带阻滤波器:原理、应用及性能分析?|深圳比创达电子EMC

在现代电子技术和通信领域中&#xff0c;滤波器是一种常见的电路元件&#xff0c;用于处理信号&#xff0c;去除不需要的频率成分或者增强感兴趣的频率成分。本文将重点探讨带阻滤波器&#xff0c;它是一种特殊类型的滤波器&#xff0c;具有在特定频率范围内抑制信号的功能。我…

SSD自己也能复制粘贴?浅谈NVMe 2.0 Copy Command命令

复制粘贴&#xff08;CtrlC/V&#xff09;作为现代打工人日常办公的必备生存技能&#xff0c;想必大家都非常熟悉。但你知道吗&#xff0c;其实SSD自身也能进行这个非常实用的操作。可能有的读者要说了&#xff1a;这有什么稀奇&#xff0c;复制粘贴这么简单的功能&#xff0c;…

腾讯字节常考的linux命令

1 ps 1.1 ps -ef 有哪些字段 ps -ef 命令在Unix/Linux系统中用于显示当前运行的进程。输出的字段通常包括&#xff1a; UID&#xff1a;启动进程的用户ID。PID&#xff1a;进程ID。PPID&#xff1a;父进程ID。C&#xff1a;CPU利用率。STIME&#xff1a;进程启动时间。TTY&a…

安卓上比iOS快捷指令更强大的工具——MacroDroid

使用 MacroDroid (Android) 自动化您的日常生活——一个简单的自动化应用程序&#xff0c;用于在 Android 上自动执行任务以及如何在其上自动执行任务。 iOS 和 Android 之间的区别? iOS和Android是两种不同的移动操作系统&#xff0c;iOS由苹果公司开发&#xff0c;于2007年…

conda配环境问题合集

&#xff08;CtrlF&#xff0c;请&#xff09; 问题&#xff1a; File "F:\Anaconda3\envs\YOLOv5\lib\ssl.py", line 773, in __init__ raise ValueError("check_hostname requires server_hostname") ValueError: check_hostname requires server_h…

Vue2解决pinia刷新后数据丢失的问题

Pinia&#xff1a;官网 Pinia 是一个 Vue.js 状态管理库&#xff0c;如果你在组件中修改了 store 中的数据并刷新了界面&#xff0c;Pinia 会将 store 中的数据重置为初始值&#xff0c;从而导致数据丢失的问题。 这里给出vue2的解决方案&#xff1a; 可以使用 Pinia 的 Per…

当接口要加入新方法时,我后悔没有早点学设计模式了

&#x1f4e2; 声明&#xff1a; &#x1f344; 大家好&#xff0c;我是风筝 &#x1f30d; 作者主页&#xff1a;【古时的风筝CSDN主页】。 ⚠️ 本文目的为个人学习记录及知识分享。如果有什么不正确、不严谨的地方请及时指正&#xff0c;不胜感激。 直达博主&#xff1a;「…

PP材料粘接ABS材料使用UV胶的好处?

跟随着现阶段材料的不断发展更迭&#xff0c;PP材料应用越来越广&#xff0c;生产效率要求越来越高&#xff0c;为了加快生产&#xff0c;提高效率&#xff0c;PP材料的粘接上使用UV胶粘接PP&#xff08;聚丙烯&#xff09;和ABS&#xff08;丙烯腈-丁二烯-苯乙烯共聚物&#x…

python Open3D加载obj

pip安装Open3D python -m pip install open3d示例代码 import numpy as np import open3d as o3dpath_obj test/assimp-5.2.5/test/models/OBJ/box.objmesh o3d.io.read_triangle_mesh(path_obj, enable_post_processingTrue)print(np.asarray(mesh.vertices))mesh.compute…

Jenkins:持续集成与持续交付的自动化利器

随着软件开发行业的快速发展&#xff0c;持续集成&#xff08;Continuous Integration&#xff0c;简称CI&#xff09;和持续交付&#xff08;Continuous Delivery&#xff0c;简称CD&#xff09;已经成为了现代软件开发的重要理念。Jenkins作为一款开源的持续集成和持续交付工…

企业可以利用SD-WAN打破网络限制,实现高效稳定的应用访问

在当今数字化时代&#xff0c;我们面临着越来越多复杂应用和各种类型的数据传输。企业需要实时访问云应用、视频会议等关键应用&#xff0c;不断增长的访问流量&#xff0c;导致应用访问速度变得越来越慢&#xff0c;给工作效率和用户体验带来了很大困扰。 SD-WAN是否能够解决这…

javaSwing酒店管理

一、介绍 在这篇博客中&#xff0c;我们将介绍一个基于MySQL数据库、Java编程语言和Swing图形用户界面的简单酒店管理系统。该系统包括了查询房客信息、查询房客状态、修改房客信息、添加房间信息、添加住户、退房管理、预定管理、退订管理、入账管理、出账管理、修改资料等多…

0009Java程序设计-ssm微信小程序在慢性疾病管理中的应用

文章目录 **摘要**目录系统实现开发环境 编程技术交流、源码分享、模板分享、网课分享 企鹅&#x1f427;裙&#xff1a;776871563 摘要 首先,论文一开始便是清楚的论述了小程序的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例…

极坐标曲线@典型的4种曲线

文章目录 abstract典型曲线心形线玫瑰线阿基米德螺线伯努利双扭线 abstract 除了圆和圆锥曲线外,还有许多曲线用极坐标描述会简单得多 典型曲线 分析下列曲线时,线分析是否含有三角函数(周期性) 利用描点法做出单个周期内的图形 作图:可以打开geogebra https://www.geogebr…

记:vite3+vue3+axios前端项目跨域问题解决【前端和服务器nginx配置】

前言&#xff1a;什么是跨域&#xff0c;网上一搜一大把&#xff0c;所以这里直接跳过&#xff0c;直入主题。 处理方式&#xff1a;不通过后端处理跨域&#xff0c;通过前端服务器nginx处理。 1.前端涉及处理跨域的必要配置&#xff08;开发环境、生产环境&#xff09;&…

银行插件导致的Outlook客户端无法连接服务器问题

问题现象 最近遇到好些同事出现outlook客户端无法连接服务器的情况&#xff0c;具体现象就是右下角一直显示【正在尝试连接…】或者【需要密码】&#xff0c;点击【需要密码】按钮&#xff0c;输密码的弹窗是一个完全空白的页面。 此时打开word&#xff0c;右上角那里去登录o…