最近看了一些关于KMP算法的资料,在此写一篇博客总计一下。

1.KMP算法介绍

KMP算法是一种字符串搜索的改进算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。

举个例子:

有两个字符串,我们要在第一个字符串(主串)中寻找第二个字符串(模式串):

bacbabababacaab
ababca

寻找的方法很简单,就是逐位进行比较,要是不相等就把模式串右移。

考虑下面这种情况:

bacbabababacaab
    ababaca

绿色的字符串表示匹配的部分,红色的字符串表示不匹配的部分。

此时我们的字符串并没有完全匹配,因此我们需要把模式串往右移。

此时一般的字符串匹配算法会这么做:

bacbabababacaab
     ababaca  

但这么做就浪费了我们绿色部分匹配所获得的信息。我们可以看到,对于绿色匹配部分,我们拥有两个相同的前缀与后缀:

后缀:ababa

前缀:  ababa

因此在这里我们的模式串是可以向右移动两位的:

bacbabababacaab
      ababaca  

这也就是KMP算法的思想:利用匹配失败后的信息,尽量减少模式串与主串的匹配次数

因此我们会在KMP算法中维护一个next数组,该数组的下标表示了主串与模式串匹配相同的长度(也就是绿色部分字符串的长度,同时也是匹配失败的位置),而数组中则存储了该绿色字符串相同前后缀的长度。因此当我们匹配失败时我们可以移动:绿色字符串长度 - 绿色字符串前后缀长度(如上面的例子就是5 - 3 = 2)

2.KMP算法的实现

要想实现KMP算法,我们先得把关键的next数组计算出来。计算next数组的方法如下图所示:
假设我们要求next[i+1],那么我们考虑模式串的最后一个字符,即第i位字符。
如果第i位字符与第next[i]位字符相等,那么显而易见next[i+1]的值就是next[i]+1。
但如果第i位字符与第next[i]位字符不等,那么我们就必须寻找字符串前缀中的前缀,就必须比较第i位字符与第next[next[i]]位字符了,直到前缀为0则停止比较。(此处确实绕口……)
因此,根据上面的思想我们可以写出如下Java代码:
  1. /**
  2. * 输入模式字符串返回其对应的next数组
  3. * @param p 模式字符串
  4. * @return next数组
  5. */
  6. private static int[] KMPNext(String p) {
  7. // 初始化
  8. int len = p.length();
  9. int next[] = new int[len];
  10. next[0] = next[1] = 0;
  11. for (int i = 1; i < len-1; i++) {
  12. int j = next[i]; // 相同前缀的最后一位字符
  13. while (j > 0 && p.charAt(i) != p.charAt(j)) // 如果第i位字符与前缀最后一位字符不相等,则去寻找前缀的前缀,如果没有前缀则退出循环
  14. j = next[j];
  15. if (p.charAt(i) == p.charAt(j)) // 如果相等,则最长前后缀长度加一
  16. next[i+1] = j+1;
  17. }
  18. return next;
  19. }
 

有了next数组,我们就可以写出KMP算法了:

 
  1. /**
  2. * KMP搜索字符串
  3. * @param m 主字符串
  4. * @param p 模式串
  5. * @param next next数组
  6. */
  7. private static void KMP(String m, String p, int next[]) {
  8. int j = 0; // 模式串索引
  9. for (int i = 0; i < m.length(); i++) {
  10. while (j > 0 && m.charAt(i) != p.charAt(j)) // 字符不相等,模式串右移,由于字符串已有next[i]个相同的前后缀,因此比较索引为next[i]的字符串即可
  11. j = next[j];
  12. if (m.charAt(i) == p.charAt(j)) // 字符相等,索引加一
  13. j++;
  14. if (j == p.length()) { // 已找到结果
  15. System.out.println("find the string in " + (i - j + 1));
  16. break;
  17. }
  18. }
  19. }

最后附上检测用的例子:

  1. public static void main(String[] args) throws Exception {
  2. String m = "bacbabababacaab";
  3. String p = "ababaca";
  4. int next[] = KMPNext(p);
  5. KMP(m, p, next);
  6. }

结果如下:

KMP算法小结的更多相关文章

  1. KMP算法(转载)

    转载http://blog.csdn.net/yutianzuijin/article/details/11954939 kmp算法又称“看毛片”算法,是一个效率非常高的字符串匹配算法.不过由于其难以 ...

  2. KMP算法学习(详解)

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

  3. 时序分析:KMP算法用于序列识别

    考研基础资料之一的<算法与数据结构>,KMP算法作为串匹配的基本算法,为必考题目之一.对于算法入门来说,也是复杂度稍高的一个基本算法. KMP算法作为串匹配的非暴力算法,是为了减少回溯而设 ...

  4. 第4章学习小结_串(BF&KMP算法)、数组(三元组)

    这一章学习之后,我想对串这个部分写一下我的总结体会. 串也有顺序和链式两种存储结构,但大多采用顺序存储结构比较方便.字符串定义可以用字符数组比如:char c[10];也可以用C++中定义一个字符串s ...

  5. KMP算法学习以及小结(好马不吃回头草系列)

    首先请允许我对KMP算法的三位创始人Knuth,Morris,Pratt致敬,这三位优秀的算法科学家发明的这种匹配模式可以大大避免重复遍历的情况,从而使得字符串的匹配的速度更快,效率更高. 首先引入对 ...

  6. 浅析KMP算法

    浅析KMP算法 KMP算法是一种线性字符串的匹配算法,将主串S与模式串T匹配. 首先朴素算法大家都会,就是直接从S的每一个位置开始,枚举比较,时间效率为O(nm),现在要想到一种化简的方式,使得时间复 ...

  7. KMP算法中next函数的理解

    首先要感谢http://blog.csdn.net/v_july_v/article/details/7041827以及http://blog.chinaunix.net/uid-27164517-i ...

  8. 单模式串匹配----浅谈kmp算法

    模式串匹配,顾名思义,就是看一个串是否在另一个串中出现,出现了几次,在哪个位置出现: p.s.  模式串是前者,并且,我们称后一个 (也就是被匹配的串)为文本串: 在这篇博客的代码里,s1均为文本串, ...

  9. 字符串模式匹配算法系列(二):KMP算法

    算法背景: KMP算法是由Donald Knuth和Vaughan Pratt于1970年共同提出的,而James H.Morris也几乎同时间独立提出了这个算法.因此人们将其称作“克努特-莫里斯-普 ...

随机推荐

  1. python pickle 模块的使用

    用于序列化的两个模块 json:用于字符串和Python数据类型间进行转换 pickle: 用于python特有的类型和python的数据类型间进行转换 json提供四个功能:dumps,dump,l ...

  2. HTTP协议扫盲(七)请求报文之 GET、POST-FORM 和 POST-FILE

    一.get 1.页面代码 2.请求报文 3.小结 get请求没有报文体,所以请求报文没有content-type url上的query参数param11=val11&param12=val12 ...

  3. ELK学习总结(4-2)关于导入数据

    用REST API的_bulk来批量插入,可以达到5到10w条每秒 把数据写进json文件,然后再通过批处理,执行文件插入数据: 1.先定义一定格式的json文件,文件不能过大,过大会报错 2.后用c ...

  4. python中导入模块的本质, 无法导入手写模块的解决办法

    最近身边一些朋友发生在项目当中编写自己模块,导入的时候无法导入的问题. 下面我来分享一下关于python中导入模块的一些基本知识. 1 导入模块时寻找路径 在每一个运行的python程序当中,都维护了 ...

  5. Python/MySQL(一、基础)

    Python/MySQL(一.基础) mysql: MYSQL : 是用于管理文件的一个软件 -socket服务端 (先启动) -本地文件操作 -解析 指令[SQL语句] -客户端软件 (各种各样的客 ...

  6. wrapper x64 版本发布到centos

    背景: 项目需要在spark任务提交服务器节点上自动提交任务到spark集群上.因此创建了一个固定时间监控任务项目,使用timer定时监控oracle数据库中是否有spark提交任务,如果有spark ...

  7. 谈谈spring-boot不同包结构下,同样的类名冲突导致服务启动失败解决方案

    项目背景: 某日,有需求要在三天的时间内完成两个大项目的项目合并,因为之前两个项目的包结构和类名都很多相同,于是开始考虑使用加一级包进行隔离,类似于这种结构 但是在启动的过程中,抛出来这样的异常: C ...

  8. POJ-3069 Saruman's Army---区间选点

    题目链接: https://vjudge.net/problem/POJ-3069 题目大意: 在一条直线上,有n个点.从这n个点中选择若干个,给他们加上标记.对于每一个点,其距离为R以内的区域里必须 ...

  9. .NET面试资料整理

    1.WCF和Web Api的区别答:1WCF是.NET平台开发的一站式框架,Web Api的设计和构建只考虑一件事情,那就是Http,而WCF的设计主要考虑是SOAP和WS-*:Web Api非常轻量 ...

  10. 发布你的程序包到Nuget

    1.新建一个.NET Standard 的类库项目 2.选择项目熟悉,在 package 栏目下填写我们的nuget包信息 3.选择我们的项目,点击"Pack" 打包 主要注意的是 ...