简介

  KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。

提取加速匹配的信息

  上面说道 KMP 算法主要是通过消除主串指针的回溯来提高匹配的效率的,那么,它是则呢样来消除回溯的呢?就是因为它提取并运用了加速匹配的信息!
  这种信息就是对于每模式串 t 的每个元素 t j,都存在一个实数 k ,使得模式串 t 开头的 k 个字符(t 0 t 1…t k-1)依次与 t j 前面的 k(t j-k t j-k+1…t j-1,这里第一个字符 t j-k 最多从 t 1 开始,所以 k < j)个字符相同。如果这样的 k 有多个,则取最大的一个。模式串 t 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。
  
  加速信息,即数组 next 的提取是整个 KMP 算法中最核心的部分,弄懂了 next 的求解方法,也就弄懂了 KMP 算法的十之七八了,但是不巧的是这部分代码恰恰是最不容易弄懂的……
  
先上代码

void Getnext(int next[],String t)
{int j=0,k=-1;next[0]=-1;while(j<t.length-1){if(k == -1 || t[j] == t[k]){j++;k++;next[j] = k;}else k = next[k];//此语句是这段代码最反人类的地方,如果你一下子就能看懂,那么请允许我称呼你一声大神!}
}

ok,下面咱们分三种情况来讲 next 的求解过程

  1. 特殊情况
    当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。

  2. 当 t[j] == t[k] 的情况
    举个栗子

    观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。

  3. 当t[j] != t[k] 的情况
    关于这种情况,在代码中的描述就是“简单”的一句 k = next[k];。我当时看了之后,感觉有点蒙,于是就去翻《数据结构教程》。但是这本书里,对于这行代码的解释只有三个字:k 回退…!于是我从“有点蒙”的状态升级到了“很蒙蔽”的状态,我心想,k 回退?我当然知道这是 k 退回,但是它为什么要会退到 next[k] 的位置?为什么不是回退到k-1???巴拉巴拉巴拉…此处省略一万字。

    我绞尽脑汁,仍是不得其解。于是我就去问度娘…
    在我看了众多博客之后,终于有了一种拨云见日的感觉,看下图

       由第2中情况可知,当 t[j] == t[k] 时,t[j+1] 的最大子串的长度为 k,即 next[j+1] = k+1。但是此时t[j] != t[k] 了,所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看上图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串(这里所说“最长重合串”实不严谨,但你知道是符合 k 的子串就行…),那么就相当于求 next[k](只不过 t[k] 变成了 t[j],但是 next[k] 的值与 t[k] 无关)!!!。所以才有了这句 k = next[k],如果新的一轮循环(这时 k = next[k] ,j 不变)中 t[j] 依然不等于 t[k] ,则说明倒数第二大 t[0~next[k]-1] 也不行,那么 k 会继续被 next[k] 赋值(这就是所谓的 k 回退…),直到找到符合重合的子串或者 k == -1。

至此,算是把求解数组 next 的算法弄清楚了(其实是,终于把 k = next[k] 弄懂了…)

因为这个算法神奇难解之处就在k=next[k]这一处的理解上,网上解析的非常之多,有的就是例证,举例子按代码走流程,走出结果了,跟肉眼看的一致,就认为解释了为什么k=next[k];很少有看到解释的非常清楚的,或者有,但我没有仔细和耐心看下去。我一般扫一眼,就大概知道这个解析是否能说的通。仔细想了三天,搞的千转百折,山重水复,一头雾气缭绕的。搞懂以后又觉得确实简单,但是绕人,烧脑。

再此特别感谢昵称为“sofu6”的博客园主,正是他的博客,让我这愚笨的脑袋瓜开窍了

KMP算法实现

当你求出了 next 数组之后,KMP 算法就很轻易搞定了,下面我用三张图,让你明白 KMP 算法完成匹配的整个过程。
以目标串:s,指针为 i ;模式串:t 指针为 j ; 为例

上图表示:“si-j ~ si-1” == “t 0 ~ t j-1”,s i != t j(前面都相等,但比较到 t j 时发现不相等了)且next[j] == k。

根据 next 数组的定义得知 “t k ~ t j-1” == “t 0 ~ t k-1”,所以 “t 0 ~ t k-1” == “si-k ~ si-1

将模式串右移,得到上图,这样就避免了目标穿的指针回溯。

都明了之后就可以手写 KMP 的代码了

int KMP(String s,String t)
{int next[MaxSize],i=0;j=0;Getnext(t,next);while(i<s.length&&j<t.length){if(j==-1 || s[i]==t[j]){i++;j++;}else j=next[j];               //j回退。。。}if(j>=t.length)return (i-t.length);         //匹配成功,返回子串的位置elsereturn (-1);                  //没找到
}

改进后的 next 求解方法

先来看一下上面算法存在的缺陷:

显然,当我们上边的算法得到的next数组应该是[ -1,0,0,1 ]

所以下一步我们应该是把j移动到第1个元素咯:

不难发现,这一步是完全没有意义的。因为后面的B已经不匹配了,那前面的B也一定是不匹配的,同样的情况其实还发生在第2个元素A上。

显然,发生问题的原因在于t[j] == t[next[j]]。

所以我们需要谈价一个判断:

void Getnext(int next[],String t)
{int j=0,k=-1;next[0]=-1;while(j<t.length-1){if(k == -1 || t[j] == t[k]){j++;k++;if(t[j]==t[k])//当两个字符相同时,就跳过next[j] = next[k];elsenext[j] = k;}else k = next[k];}
}

本文对sofu6的博客多有借鉴,所以在此特别鸣谢,并附上他的博客:想看点这里

KMP算法—终于全部弄懂了相关推荐

  1. 终于完全弄懂了KMP(个人理解篇)

    前阵子在学习KMP相关的内容,其他部分都挺好理解的,最后在next数组和k=next[k]这个递推公式上迷糊了好久,看了不少人写的博客,有的写着写着最后的结论又跳跃了,有的是写清楚了,但是感觉写的有过 ...

  2. 算法串匹配C++实现字符串匹配的KMP算法

    新手发帖,很多方面都是刚入门,有错误的地方请大家见谅,迎欢批评指正 之前看<算法导论>符字串匹配分部讲到KMP算法,被弄得云里雾里.天今看到阮一峰写了一篇博客<符字串匹配的KMP算法 ...

  3. KMP算法代学习之(二)代码深入学习

    前段时间学习了KMP算法,感觉略懂略懂!但算法这个东西理解一定要深入,不是略懂略懂就能敷衍过去的!真真做题的时候,才发现仅仅套模板是根本没用的,必须自己写得来代码,这样的话才能从细节,从根本上了解KM ...

  4. 终于弄懂KMP算法了

    1.简例弄懂KMP-点此链接查看 看了上面的文章,你肯定大概明白了KMP的运作原理,但是你可能对于文章提到的"部分匹配值"的又来还存在疑惑,那么请继续往下看: 我们先抛出两个问题, ...

  5. No.5终于搞懂了kmp算法(精髓为next数组的求解过程,此文next数组未经过优化)

    背景 KMP 是经典的字符串匹配算法,在大学课本里面都是讲到过的,不过我感觉课本都讲的太生硬了,很容易忘记.大多数博客也是讲的云里雾里的,一开始就来个公式+部分匹配表,反正我感觉对于没有计算机基础的童 ...

  6. 终于能理解kmp算法了

    啃了几天了,终于是把next数组神奇的求解原理弄懂了,next数组的求解确实比kmp算法本身要难理解得多,现在不靠模版也能敲出kmp了,只是不熟练,以后模版只是来应付比赛的,平时练习能不用模版尽量不用 ...

  7. 写日历的程序员,你必须弄懂的中国农历算法。

    写日历的程序员,你必须弄懂的中国农历算法. NANSHAN即时通讯,我们知道农历通过置闰月的方式协调农历年和回归年长度不相等的问题,也知道了置闰的方法是"中气置闰"法,那么到底什么 ...

  8. 字符串:你看的懂的KMP算法(带验证)

    前言 KMP算法可以说说许多学习算法的同学的第一道坎,要么是领会不到KMP算法的思想,要么是知道思想写不出代码,网上各种查找.关于算法的书籍上也都有KMP算法的实现,可为啥自己写不出来呢?博主看得大话 ...

  9. 快速傅里叶变换(研二的我终于弄懂了)

    研二的我仍然对快速傅里叶变换一知半解,于是乎,本着待在家里,能耗时间就多耗点,不知道何年马月我才可以在外面快乐的奔跑~~ 快速傅里叶变换的实现(c++版本) 在做项目的时候,需要用到matlab里的f ...

  10. 【一文弄懂】优先经验回放(PER)论文-算法-代码

    [一文弄懂]优先经验回放(PER)论文-算法-代码 文章目录 [一文弄懂]优先经验回放(PER)论文-算法-代码 前言: 综合评价: 继续前言唠叨 per论文简述: 参考博客: 背景知识 A MOTI ...

最新文章

  1. 正则表达式(javascript)
  2. 通过SWD J-Link使用J-Link RTT Viewer来查看打印日志
  3. 入门干货之用DVG打造你的项目主页-Docfx、Vs、Github
  4. python getopt模块_python之getopt模块使用
  5. Java笔记-Spring-rabbitmq中设置receiveMessage参数为Message
  6. Binary classification - 聊聊评价指标的那些事儿【回忆篇】
  7. left join 和inner join关联查询区别
  8. 微信潜规则:月薪过万的90后,过年都在看什么?
  9. Linux添加相对库路径,Linux C编程(8) 使用相对路径加载动态库-rpath和$ORIGIN
  10. Eset易视Smart灵巧护卫Security
  11. hdu--1077--Catching Fish
  12. 十、垃圾回收策略概览
  13. Glide加载长图;WebView加载富文本(图片自适应屏幕大小)
  14. 实战之8051驱动8位数码管
  15. 小米开发版安装magisk_小米9SE不刷recovery直接安装Magisk面具的详细教程
  16. 数据分析师工资高达50万,正在进入每一个行业!
  17. CorelDraw手表实例实操作让你全面了解技术的综合应用
  18. 百度云直链下载-IDM+网页解析(三)
  19. Java学习--多线程案例--模拟火车票销售(线程安全问题)
  20. maven发布SNAPSHOT版本到私服仓库

热门文章

  1. OC中__kindof的用法
  2. 2016腾讯实习生招聘_基础研究 面试心得
  3. 切换IE浏览器的版本
  4. LintCode 吹气球
  5. 顺利通过2021年上工信部的网络工程师考试,在此感悟一下
  6. 移动硬盘插入提示需要格式化RAW_学会自己判断移动硬盘故障!如何在保数据的情况下进行正确处理!...
  7. 问:舵机可以作为智能小车的驱动轮吗
  8. 51单片机汇编语言点亮一位数码管,并计时(超详细分析汇编语言)
  9. mac打开注册机显示“您没有权限来打开应用程序
  10. 计算机保研面试知识点,保研面试(附面试笔试机试)