奇淫巧技的KMP算法–详解

花了一下午时间,看了十几个博客,终于拿下了KMP高地,现在总结下下自己对KMP的理解和实现。
情景1
假如你是一名生物学家,现在,你的面前有两段 DNA 序列 S 和 T,你需要判断 T 是否可以匹配成为 S 的子串。

你可能会凭肉眼立即得出结论:是匹配的。可是计算机没有眼睛,只能对每个字符进行逐一比较。

对于计算机来讲,首先它会从左边第一个位置开始进行逐一比较:

这样,当匹配到 T 的最后一个字符时,发现不匹配,于是从 S 的第二个字符开始重新进行比较:

仍然不匹配,再次将 T 与 S 的第三个字符开始匹配…不断重复以上步骤,直到从 S 的第四个字符开始时,最终得出结论:S 与 T 是匹配的。

你发现这个方法的弊端了吗?我们在进行每一轮匹配时,总是会重复对 A 进行比较。也就是说,对于 S 中的每个字符,我们都需要从 T 第一个位置重新开始比较,并且 S 前面的 A 越多,浪费的时间也就越多。假设 S 的长度为 mmm,T 的长度为 nnn,理论上讲,最坏情况下迭代 m−n+1m - n + 1m−n+1 轮,每轮最多进行 nnn 次比对,一共比较了 (m−n+1)×n(m - n + 1)\times n(m−n+1)×n 次,当 m>>nm >> nm>>n 时,渐进时间复杂度为 O(mn)O(mn)O(mn)。

而 KMP 算法的好处在于,它可以将时间复杂度降低到 O(m+n)O(m + n)O(m+n),字符序列越长,该算法的优势越明显。它是怎么实现的呢?

情景 2
再来举一个例子,现在有如下字符串 S 和 P,判断 P 是否为 S 的子串。

我们仍然按照原来的方式进行比较,比较到 P 的末尾时,我们发现了不匹配的字符。

注意,按照原来的思路,我们下一步应将字符串 P 的开头,与字符串 S 的第二位 C 重新进行比较。而 KMP 算法告诉我们,我们只需将字符串 P 需要比较的位置重置到图中 j 的位置,S 保持 i 的位置不变,接下来即可从 i,j 位置继续进行比较。

为什么?我们发现字符串 P 有子串 ACT 和 ACY,当 T 和 Y 不匹配时,我们就确定了 S 中的蓝色 AC 并不匹配 P 右侧的 AC,但是可能匹配左侧的 AC,所以我们从位置 i 和 j 继续比较。

换句话说,Y 对应下标 2,表示下一步要重新开始的地方。

既然如此,如果每次不匹配的时候,我们都能立刻知道 P 中不匹配的元素,下一步应该从哪个下标重新开始,这样不就能大大简化匹配过程了吗?这就是 KMP 的核心思想。

KMP 算法中,使用一个数组 next 来保存 P 中元素不匹配时,下一步应该重新开始的下标。由于计算机不能像我们人类一样,通过视觉来得出结论,因此这里有一种适合计算机的构造 next 数组的方法。

Next数组求解
好了,到这就来到了核心部分,前面kmp原理介绍就偷了个懒,全文引用了LeetCode 上的KMP原理介绍。KMP的复杂之处,也就是核心之处在于如何求解next数组。求解next数组就需要先理解字符串的最长公共前后*缀。

前缀和后缀定义

前缀:指除了最后一个字符以外一个字符串的全部头部组合。如字符串aabaaac的前缀{a,aa,aab,aaba,aabaa,aabaaa }
后缀:指除了第一个字符以外,一个字符串的全部尾部组合。如字符串aabaaac的前后缀{abaaac,baaac,aaac,aac,ac,c }
next[0]: a 的最长公共前后缀,没有前后缀。为0
next[1]: aa 的最长公共前后缀,前缀{a},后缀{a}。为1
next[2]: aab 的最长公共前后缀,前缀{a,aa},后缀{ ab,b}为0
next[3]: aaba 的最长公共前后缀,前缀{a,aa,aab},后缀{aba,ba,a}。为1
next[4]: aabaa 的最长公共前后缀,前缀{a,aa,aab,aaba},后缀{abaa,baa,aa,a}。为2
next[5]: aabaaa 的最长公共前后缀,前缀{a,aa,aab,aaba,aabaa},后缀
{abaaa,baaa,aaa,aa,a}为2
next[6]: aabaaac 的最长公共前后缀,前缀{a,aa,aab,aaba,aabaa,aabaaa},后缀{abaaac,baaac,aaac,aac,ac,c}。为0
next[i]代表字符串0到i位置(包括i)的子串的最长公共前后缀的长度。

**既next[i] 表示
**str.subString(0,i+1).subString(0,next[i]) == str.subString(0,i+1).subString(i+1-next[i],i+1)这个地方的理解对后面求解next数组很重要

知道next数组的含义之后就是,我们就来求next数组。

 public int[] next(String str){int length= str.length();int[] next = new int[length];int head =1;int tial = 0;next[0] = 0;while(head<length){if(str.charAt(head)== str.charAt(tial)){tial++;next[head] = tial;//最长公共前缀后缀的长度,tial+1代表当前最长公共前后缀的长度head++;}else{if(tial==0){//此时代表最长公共前后缀的长度为0;head++;}else {tial = next[tial-1];}}}return next;}

在求next数组的时候,最难理解的就是tial = next[tial-1]; ,这也是KMP算法奇淫巧技的地方,绝大部分的讲解也没有去将为什么处理这步,这也导致了很难去理解KMP算法。
因为str.charAt(tail) != str.charAt(head) (此时最长公共前后缀无法满足增长了,只能是往回找),tial此时需要往回调。
如果tial!=0则表示此时0到tial的位置的子串与head-tial到head位置的子串是一样的。
next[tial-1]表示str.subString(0,tial)这个字符串的最长公共前后缀,而str.subString(0,tial)与str.subString(head-tial,head)是完全一样的,所以tial 跳转 next[tila-1]位置,再将str.charAt(tial) 与str.charAr(head)进行比较,如果相等则next[head] = tial++;tial表示字符的位置。

主要是要理解tial = next[tial-1],巧妙的地方就是tial的往回跳转,当tial!=0时是满足当前head之前有tail-1个元素是与0到tial-1是最长的公共前后缀然后再比较str.charAt(head)== str.charAt(tial)tial==0就表示即到了当前字符串开始位置和最后的head的位置进行比较。

 public int strStr(String haystack, String needle) {if(needle.length()==0){return 0;}int []next = next(needle);for (int i = 0; i <next.length ; i++) {System.out.println(next[i]);}int S_index = 0;int M_index = 0;while(S_index<haystack.length()&&M_index < needle.length()){if(haystack.charAt(S_index)== needle.charAt(M_index)){M_index++;S_index++;}else{if(M_index ==0){//M_index ==0 代表此时在匹配字符串的第一个位置,因// 为needle与haystack第一个位置匹配不上,所以haystack的指针往后移S_index++;}else{// M_idnex!=0,根据next的值,needle的指针往回移next[M_index-1]的位置上。M_index = next[M_index-1];}}}return M_index == needle.length()?S_index-M_index:-1;}

由于不好画图,建议在阅读的过程中画图理解tial = next[tial-1]的跳转思路。

注:也可以BF求解next数组,但是在LeetCode上超时了

public int[] get_Next(String str){int length = str.length();int[] next = new int[length];next[0] = 0;for(int i =1;i<length;i++){int len =0;for(int j =1;j<=i;j++) {//枚举前后缀进行比较if (str.substring(0, j).equals(str.substring(i + 1 - j, i + 1))) {if (len < j) {len = j;}}}next[i] =len;}return next;}

参考文献:
链接:https://blog.csdn.net/V_0617/article/details/79114860
链接:https://leetcode-cn.com/leetbook/read/array-and-string/cpoo6/
来源:力扣(LeetCode)

奇淫巧技的KMP算法--详解相关推荐

  1. KMP算法详解及各种应用

    KMP算法详解: KMP算法之所以叫做KMP算法是因为这个算法是由三个人共同提出来的,就取三个人名字的首字母作为该算法的名字.其实KMP算法与BF算法的区别就在于KMP算法巧妙的消除了指针i的回溯问题 ...

  2. 字符串匹配之KMP算法详解

    kmp算法又称"看毛片"算法,是一个效率非常高的字符串匹配算法.不过由于其难以理解,所以在很长的一段时间内一直没有搞懂.虽然网上有很多资料,但是鲜见好的博客能简单明了地将其讲清楚. ...

  3. KMP算法详解P3375 【模板】KMP字符串匹配题解

    KMP算法详解: KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt(雾)提出的. 对于字符串匹配问题(such as 问你在abababb中有多少个 ...

  4. KMP算法详解及代码

    KMP算法详解及代码 KMP算法详解及代码 定义及应用 理论 基本概念 next 数组 总结 注意 代码 KMP算法详解及代码 最近正好在看字符串相关的算法内容,就顺便把KMP算法回顾了一下.相应的代 ...

  5. KMP 算法详解(CPP 实现)

    转载请标明出处:https://blog.csdn.net/kiss0tql/article/details/81416283 本文来自:deemo的博客 说明 kmp 算法思想 next 数组计算 ...

  6. 【KMP算法详解——适合初学KMP算法的朋友】

    相信很多人(包括自己)初识KMP算法的时候始终是丈二和尚摸不着头脑,要么完全不知所云,要么看不懂书上的解释,要么自己觉得好像心里了解KMP算法的意思,却说不出个究竟,所谓知其然不知其所以然是也. 经过 ...

  7. 【转】KMP算法详解

     原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任. http://billhoo.blog.51cto.com/2337751/411486 ...

  8. [数据结构]模式匹配算法--KMP算法详解

    目录 一. 模式匹配 二. 模式匹配算法 1. 朴素模式匹配算法 2. KMP算法 1). KMP算法的优势 2). KMP算法的原理 3). next数组的构造 4). 利用next数组匹配的过程 ...

  9. KMP算法详解 转帖

    个人觉得这篇文章是网上的介绍有关KMP算法更让人容易理解的文章了,确实说得很"详细",耐心地把它看完肯定会有所收获的--,另外有关模式函数值next[i]确实有很多版本啊,在另外一 ...

最新文章

  1. 10.05 最初对Linux的了解,对Shell的认识
  2. python实现文件下载-Python+django实现文件下载
  3. linux命令tcpdump
  4. byte数组转字符串_leetcode刷题844比较含退格的字符串(带代码解析,带知识点回顾)...
  5. 【RS码2】RS码的BM迭代译码原理详解及MATLAB实现(不使用MATLAB库函数-代码见CSDN同名资源)
  6. IDEA的创建javaWeb工程以及修改默认的class文件输出
  7. nginx服务器防sql注入/溢出***/spam及禁User-agents
  8. Spring中bean实例化的三种方式:默认构造、静态工厂、实例工厂
  9. zoom 更改安装位置_如何以Zoom更改会议主持人
  10. cacti mysql 压缩 备份_CactiEZ备份和恢复
  11. java smali代码 解读_Smali代码语法
  12. 方舟服务器在线人数查询软件,方舟生存进化怎么查看在线人数
  13. 年龄识别之AgeNet
  14. vue实现在canvas画布上实现绘制涂抹功能
  15. 《实时控制软件设计》第二个编程作业
  16. PCIe扫盲——PCIe总线怎样做到在软件上兼容PCI总线
  17. 计算机历史博物馆观后感:阿达·洛芙莱斯生平1
  18. 来看看小夏の算法入门——前缀和差分
  19. 按键精灵MsgBox信息换行方法
  20. 泛微OA 注册自定义接口格式

热门文章

  1. 护照证件识别OCR多种模式
  2. 3D开发-全景技术方案
  3. 鬼画符门之点点大阵(python)
  4. Android人脸对比
  5. matlab裁剪图片超过数组维度,再次求问,这个位置1的索引超出数组范围到底要怎么弄?...
  6. 【Java面经】阿里三面被挂!幸获内推,历经5轮终于拿到口碑offer
  7. Mac下python及其库的安装 (附最新pycharm激活)
  8. 恒源云(GPUSHARE)_GPU服务器租用 | Spyder连接使用教程
  9. 【问题求助】PS调色师要学的东西!
  10. 购物单--最大满意度