KMP算法原理描述,告诉你为什么要“j = next[j]”

研究KMP算法的起因,是在刷leetcode的 214.最短回文串时,一开始使用了 O ( n 2 ) O(n^2) O(n2)时间复杂度的暴力算法,结果在一条 n = 40000 n=40000 n=40000的测试数据上超时了,后来在题解的启发下,用了KMP算法双95%+成功过了该题。但是在用KMP算法的时候,对于next数组的计算方式,尤其是回退的那一步始终没想明白,看了许多博客也没有描述清楚,当初上课学习KMP算法的时候对这块就是一知半解,今天花了点时间,终于算是把最令人困惑的"j = next[j]"这里搞清楚了。

0. KMP算法简介

KMP 算法是 一种字符串匹配算法,D.E.Knuth、J,H,Morris 和 V.R.Pratt 共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于暴力算法的最核心优化点是在匹配过程中,通过某些规则移动模式串指针,使得主串指针可以保持不回退,从而提升匹配效率。

1. 暴力算法的缺陷

在暴力算法中,当主串的某一段与模式串的前i-1位相同,但第i位不同时,本次匹配失败。模式串会向后移动一位、指针回退到0,而主串的指针也会回退到与模式串首位相同的位置上,如下图所示:

每次主串指针都会回退到上一次开始地方的后一位,模式串的指针也会回退到0,这样的时间复杂度是 O ( m ∗ n ) O(m * n) O(m∗n)。

2. KMP算法的核心概念:最长公共前缀子串

当一次匹配失败后,我们每次将模式串右移一位,并且将主串和模式串的指针全部回溯,其实是浪费了我们在本次匹配中已经发现的一些经验。考虑下面的一种情况:

如图,在这种情况下,我们可以将原来模式串头部深绿色的部分,挪动到模式串没有匹配成功的红色前面的深绿色部分去,这样主串的指针就不用回退了,模式串的指针也不再需要回退到开头的位置了。

模式串中,重复的两个深绿色的部分具有这样的特点:
1. 第一个深绿色的部分,从模式串的头部开始。为什么是从模式串的头部开始?因为我们要保证,模式串右移后,和主串仍有一部分是匹配的,我们可以从模式串的这个前缀后面继续判断,这是模式串指针不需要回退到开头的原因;
2. 第二个深绿色的部分,以模式串当前未匹配成功的位置为结尾。为什么是以模式串当前未匹配成功的位置为结尾?因为只有这样,当我们右移模式串,使得这两个深绿色的部分“重合”之后,我们仍可以从主串出错的位置(红色部分)和模式串(第一个深绿色部分后的下一个字符)进行匹配,这是主串指针不需要回退的原因。

至此,KMP算法的核心概念已经呼之欲出了:深绿色的部分,就是浅绿色部分的“最长公共前缀子串”。“公共”,代表着两个深绿色部分的内容相同;“前缀”,代表着从模式串的头部开始。

3. 恼人的“next数组”

你可能已经意识到了,“最长前缀公共子串”和主串无关,它完全是模式串的一个属性。而KMP算法求“最长前缀公共子串”的方法,使用的就是“next数组”的概念,这个也是大多数人在学习KMP算法上面最大的一个坎。next数组的概念如下:

next[i] = j,即模式串的前i个字符组成的字符串中,最长前缀公共子串的长度为j。

是不是有点困惑?看看下面的例子:

在上一个例子中,next数组元素的值分别为:

next[0] = 0;
next[1] = 0;
next[2] = 0;
next[3] = 0;
next[4] = 1;
next[5] = 2;
next[6] = 0;

再抽象一点,next数组的含义是这样的:

emmm,与其叫它”next“数组,还不如叫它”最长公共子串前缀长度数组“呢。。。

那么问题来了,怎么求这个next数组呢?

根据我们前面分析的next数组的定义,可以找到下面的规律:

  1. 如果新加入的字符与前一个最长公共前缀子串的后一个字符相同:

    这里的深绿色部分长度是可以为0的,对后面计算没有影响。

    这种情况下,很简单,next[i] = next[i-1] + 1。这个很好理解,相信大家一看就能想到。

  2. 如果新加入的字符与前一个最长公共前缀子串的后一个字符不同:
    很多人看KMP算法就是卡在了这一步上,我最开始看的时候也是对这个地方百思不得其解,在其他人的代码中,那个最让人头痛的回退操作——j = next[j],就是在这里出现的。为了说明白这个问题,我们看下这张图:

    在这种情况下,原来的深绿色部分就不能用了,显然,我们要缩小深绿色的部分,还要满足最长公共前缀子串的要求,也就像下面这个样子:

    这个蓝色的部分怎么求呢?这就是很多博客和代码里面没有说清楚的,最让人头痛的回退操作,就是那个臭名昭著的”j= next[j]“。
    我们换一种理解方式,这个蓝色的部分,首先要内容相同,其次还要位于深绿色部分的开头和结尾,这不就是深绿色部分的最长公共前缀子串吗?

    也就是这样:

    更新j = next[j]后,又回到这个问题的起点了,然后再次判断s[i-1]和s[j]是否相同就可以了。
    至此,我们终于搞懂那个让人费解的回退操作到底是什么意思了,其实就是在当前的公共前缀已经不能用了,那就继续去尝试这个公共前缀的公共前缀,一直试到成功或者公共前缀的长度为0时为止。

4. Java实现

最后贴个Java实现代码。注意模式串长度小于2就没有必要用KMP了,这里简单先不考虑了,要抄代码的话记得检查,小心数组越界哦:

/*** 模式串长度小于2就没有必要用KMP了,这里简单先不考虑了,要抄代码的话记得检查,小心数组越界哦
*/
private int[] getNext(String s) {int[] next = new int[s.length()];//前0个字符,肯定没有最长公共前缀子串next[0] = 0;//前1个字符,那也没有嘛next[1] = 0;//上一个最长公共前缀子串长度int subPublicPreSubStrLength = 0;for (int subStrLength = 2; subStrLength < s.length(); ) {//需要求前i个字符组成的字符串中的前缀子串,这个是新加入尾部的字符char addedSubStrChar = s.charAt(subStrLength - 1);//位于最长公共前缀子串后的字符char addedPreSubStrChar = s.charAt(subPublicPreSubStrLength);   if (addedSubStrChar == addedPreSubStrChar) {//新加入的字符和之前的最长公共前缀子串后的字符相同//最长公共子串是在前一个的基础上,再加一个字符next[subStrLength] = ++subPublicPreSubStrLength;//继续求i+1个字符组成的字符串中的前缀子串subStrLength++;     } else {//新加入的字符和之前最长公共前缀子串后的字符不同,说明之前的公共子串已经不能用了if (subPublicPreSubStrLength == 0) {//前一个公共子串长度已经为0了,那说明当前情况下的公共子串也只能是0了next[subStrLength] = 0;//继续求i+1个字符组成的字符串中的前缀子串subStrLength++;     } else {//继续求前一个前缀子串的前缀子串,这里就是那个最让人搞不懂的,k = next[k]的回退subPublicPreSubStrLength = next[subPublicPreSubStrLength];}}}return next;
}

本来可以写的很简洁,但是我特意把各个变量的命名写的清楚一些,配合注释希望大家能看懂。建议大家要是抄代码的话,可以换个其他人的写法来抄,我这个为了看清楚写的很冗长,很多地方可以优化调整,主要是能看明白思路就好。

KMP算法原理描述,告诉你为什么要“j = next[j]”相关推荐

  1. 算法 - KMP算法原理顿悟有感

    算法 - KMP算法原理顿悟有感 KMP? KMP核心思想 举个栗子 上点代码 next数组 (1)若P~j~ == P~t~ (2) 若P~j~ 和 P~t~不相等 改进上面的KMP算法 nextv ...

  2. 详解KMP算法原理,以及完整java与C++实现

    点击此处学习更多算法与通信知识 作者 | labuladong 来源 | labuladong KMP 算法(Knuth-Morris-Pratt 算法)是一个著名的字符串匹配算法,效率很高,但是确实 ...

  3. (转)KMP算法原理讲解及模板C实现

    原作者:v_JULY_v 1. 引言 本KMP原文最初写于2年多前的2011年12月,因当时初次接触KMP,思路混乱导致写也写得混乱.所以一直想找机会重新写下KMP,但苦于一直以来对KMP的理解始终不 ...

  4. KMP算法原理详解_论文解读版

    1. KMP算法 KMP算法是一种保证线性时间的字符串查找算法,由Knuth.Morris和Pratt三位大神发明,而算法取自这三人名字的首字母,因而得名KMP算法. 那发明这样的字符串查找算法又有什 ...

  5. KMP 看毛片算法原理及其实现

    kmp算法 前言: 如何匹配字符串??? 一. 暴力匹配字符串 1.1 暴力算法描述 1.2 暴力算法实现 二. KMP算法 匹配字符串 2.1 三个概念: 最长前缀; 最长后缀; 最长公共前后缀? ...

  6. 字符串匹配问题 ----- KMP算法

    题意: 任意给定一段字符串str("123abc123abc00abc") 再输入一个关键字key("abc") 要求返回str中包含key的所有子串的头下标 ...

  7. 图解算法:KMP算法

    目录 第一章 暴力匹配实现 第二章 KMP算法介绍 第三章 KMP算法原理 第四章 KMP的匹配表 第五章 KMP算法实现 项目地址:https://gitee.com/caochenlei/algo ...

  8. C语言-模式匹配(KMP算法)

    什么是KMP算法? KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,简称KMP算法.KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与 ...

  9. BF算法和KMP算法

    给定两个字符串S和T,在主串S中查找子串T的过程称为串匹配(string matching,也称模式匹配),T称为模式.这里将介绍处理串匹配问题的两种算法,BF算法和KMP算法. BF算法 (暴力匹配 ...

最新文章

  1. mysqluc安装MYSQL_安装mysql几种方法
  2. linux处理邮件编码
  3. 使用React、Node.js、MongoDB、Socket.IO开发一个角色投票应用的学习过程(一)
  4. java 接口与抽象类的区别
  5. Web开发者必备的12款超赞jQuery插件
  6. “双碳”目标下新型数据中心的方向
  7. 太阳能板如何串联_太阳能的吸热板是什么
  8. c# 从地址拷贝byte_面试必备的 “零拷贝” 问题!从头给你说!
  9. python执行的命令_如何在Python中执行外部命令
  10. 39套漂亮的后台模板
  11. robotframework自动化测试修炼宝典_软件测试工程师必备:Robot Framework实现接口自动化实践!...
  12. ES deeping pageing
  13. 人智导(二):启发式搜索
  14. 好用的各种文件在线转换工具,文件加密解密等Speedpdf
  15. 利用python绘制太阳花
  16. php alt什么意思,img标签的alt作用是什么
  17. Opencv相机校准之棋盘格标定
  18. 银行账户管理程序(二)
  19. .NET Standard中配置TargetFrameworks输出多版本类库
  20. 滴滴夜莺发布v3.3.0版本

热门文章

  1. iframe 加载完成后回调事件(怎么判断iframe是否加载完成)
  2. idea将java导出word文档,Java导出word/execl文档
  3. 网络优化(六)——超参数优化
  4. 联想小新Air14 2023和2022区别对比评测
  5. 【聆听】汪国真诗集(二)
  6. 消息称苹果下调印度市场iPhone XR售价 降价近25%
  7. 自己做了一个USB电风扇
  8. mybatis中mapper文件中的动态sql语句
  9. 网易云邮箱发送邮件 java
  10. 分享学习摄影的几个源