最近看了一些关于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. }

结果如下:

最新文章

  1. 架构设计:一种远程调用服务的设计构思(zookeeper的一种应用实践)
  2. RDIFramework.NET — 基于.NET的快速信息化系统开发框架 — 系列目录
  3. 后缀数组 POJ 1743 Musical Theme
  4. Win10/UWP新特性系列—Launcher实现应用间的通信
  5. Content Delivery Network
  6. EDM营销算法:python自动批量发邮件
  7. 通过Curator操作Zookeeper的简单例子代码
  8. ASP.NET MVC 学习笔记(1)
  9. SparkMLlib回归算法之决策树
  10. python基础1 day2
  11. Java程序员的情书
  12. Django中的templates(你的HTML页面放哪里)
  13. 2018软工实践K班总结
  14. exe4j中&quot;this executable was created with an evaluation version exe4j&quot;的解决
  15. e3.7.2-MyEclipse-10.7安装SVN插件
  16. Python自学:第二章 合并(拼接字符串)
  17. 社会地位即服务, Status as a Service (一): 社交网络是一种 ICO 行为?
  18. mysql下float类型使用一些误差详解
  19. 1208: [HNOI2004]宠物收养所
  20. thinkphp3.2.3 ueditor1.4.3 图片上传操作,在线删除上传图片功能。

热门文章

  1. Python-函数-Day4
  2. Mock API是如何在开发中发光发热的?
  3. vue-cli webpack3扩展多模块打包
  4. 单点登录实现机制:桌面sso
  5. OAuth2.0学习(1-11)新浪开放平台微博认证-使用OAuth2.0调用微博的开放API
  6. JSON(二)——JavaScript中js对象与JSON格式字符串的相互转换
  7. Django实现 省 市 县 自关联的下拉级联
  8. python之路——初识函数
  9. SpringBoot(二):设置springboot同一接口程序启动入口
  10. Hibernate(十):n-n关联关系