JAVA实现KMP算法理论和示例代码

 更新时间:2013年11月14日 11:49:49   作者:  
本文从理论到代码讲解了JAVA对KMP算法的实现,大家可以参考一下
一.理论准备
KMP算法为什么比传统的字符串匹配算法快?KMP算法是通过分析模式串,预先计算每个位置发生不匹配的时候,可以省去重新匹配的的字符个数。整理出来发到一个next数组, 然后进行比较,这样可以避免字串的回溯,模式串中部分结果还可以复用,减少了循环次数,提高匹配效率。通俗的说就是KMP算法主要利用模式串某些字符与模式串开头位置的字符一样避免这些位置的重复比较的。例如 主串: abcabcabcabed ,模式串:abcabed。当比较到模式串'e'字符时不同的时候完全没有必要从模式串开始位置开始比较直接从模式串的'c'字符开始比较就可以了。并且主串也不用回溯了。
传统的匹配算法没有利用匹配过的信息(模式串是知道的,那么部分匹配主串也是知道的),每次都从头开始比较,速度很慢。
先介绍前缀数组(我自己这么叫的,不知道对不对)是如何产生的。首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
来看一个例子:chi表示模式串的前i个字符组成的前缀, next[i] = j表示chi中的开始j个字符和末尾j个字符是一样的(注意下标是字符数目),而且对于前缀chi来说,这样的j是最大值。next[i] = j的另外一个定义是:有一个含有j个字符的串,它既是chi的真前缀,又是chi的真后缀。
 规定:next[1] = next[0] = 0,这个规定不像0!=1那样,而是确实是这样子,不懂得看上面的前后缀概念。注意:next数组里并不是首尾回文串,而是前缀等于后缀,理解这个对于递推求next数组很重要哟。next[i]就是前缀数组,下面通过1个例子来看如何构造前缀数组。
 例:cacca有5个前缀,求出其对应的next数组。前缀2为ca,显然首尾没有相同的字符,next[2] = 0,前缀3为cac,显然首尾有共同的字符c,故next[3] = 1,前缀4为cacc,首尾有共同的字符c,故next[4] = 1,前缀5为cacca,首尾有共同的字符ca,故next[5] = 2。如果仔细观察,可以发现构造next[i]的时候,可以利用next[i-1]的结果。比如abcdabc,模式已求得next[7] = 3,为求next[8],可以直接比较第4个字符和第8个字符,如果它们相等,则next[8] = next[7]+1 = 4,这是因为next[7] = 3保证了前缀ch7的末尾4个字符的前3个字符是一样的。但如果这两个字符不想等呢?那就继续迭代,利用(k=3)k = next[k]的值来求,直到k=0(next[8] = 0)或者字符相等(next[8] = k+1)。
二.算法实现
复制代码 代码如下:

import java.util.ArrayList;
public class KMP {
 //主串
 static String str = "1kk23789456789hahha";
 //模式串
 static String ch = "789";
 static int next[] = new int[20];

 public static void main(String[] args) {
  setNext();
  ArrayList<Integer> arr = getKmp();
  if(arr.size()!=0) {
   for(int i=0; i<arr.size(); i++) {
    System.out.println("匹配发生在:"+arr.get(i));
   }
  }else {
   System.out.println("匹配不成功");
  }
 }
 private static void setNext() {
  // TODO Auto-generated method stub
  int lenCh = ch.length();
  next[0] = 0;
  next[1] = 1;
  //k表示next[i-1]的值
  int k = 0;
  for(int i=2; i<=lenCh; i++) {
   k = next[k];
   /*
    * 这个while循环的作用找个例子看看就好理解了
    * 我认为是每次找最长,一旦成功就停止,保证找到的是当前最长
    */
   while(k!=0 && ch.charAt(i-1)!=ch.charAt(k)) {
    k = next[k];
   }
   if(ch.charAt(i-1)==ch.charAt(k)) {
    k++;
   }//else就是k=0
   //不是next[k] = k,i表示有几个字符的前缀
   next[i] = k;
  }
 }
 private static ArrayList<Integer> getKmp() {
  // TODO Auto-generated method stub
  ArrayList<Integer> arr = new ArrayList<Integer>();
  int lenStr = str.length();
  int lenCh = ch.length();
  //主串开始的匹配位置
  int pos = 0;
  //模式串每次匹配位置
  int k = 0;
  //循环条件不是k<lenCh,这样的话可能死循环(没有匹配发生)
  while(pos<lenStr) {
   /*
    * 首次进入没什么大作用,做要是为提高以后的匹配效率
    * 写在最后一行也行
    */
   k = next[k];
   while(k<lenCh && str.charAt(pos)==ch.charAt(k)) {
    pos++;
    k++;
   }
   if(lenCh==k) {
    arr.add(pos-k);
   }else if(0==k) {
    /*
     * 不加这一句死循环
     * 因为next[0] = 0
     * 比如abcd和abce,到de不匹配,此时执行k = next[k](k=3),
     * k变为0,发现d和a不匹配,此时k还是0,重复执行以上步骤,那么死循环了
     */
    pos++;
   }//实际上else就是k = next[k],所以才说k = next[k]写在最后一行也行
  }
  return arr;
 }

}

三.问题扩展
 KMP算法的高效性往往是在模式串比较长的时候才能体现出来(看next数组的推导过程),而实际上模式串往往很短,回想自己使用办公套件时查找的字符串长度,所以实践上大多使用BM算法来实现,感兴趣的读者可以自己查阅相关资料,或许可以再看看多模匹配(在主串中一次查找多个模式串)的AC自动机、dictmatch算法。

相关文章

  • Java实现Json字符串与Object对象相互转换的方式总结

    Java实现Json字符串与Object对象相互转换的方式总结

    这篇文章主要介绍了Java实现Json字符串与Object对象相互转换的方式,结合实例形式总结分析了java基于Json-Lib、Org.Json、Jackson、Gson、FastJson五种方式转换json类型相关操作技巧,需要的朋友可以参考下
    2019-03-03
  • idea中解决maven包冲突的问题(maven helper)

    idea中解决maven包冲突的问题(maven helper)

    这篇文章主要介绍了idea中解决maven包冲突的问题(maven helper),小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-12-12
  • Spring原生Rpc六种的正确打开方式实现示例

    Spring原生Rpc六种的正确打开方式实现示例

    这篇文章主要为大家展示了Spring原生Rpc六种的正确打开方式实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助祝大家多多进步早日升职加薪
    2022-02-02
  • SpringBoot实现获取客户端IP地理位置

    SpringBoot实现获取客户端IP地理位置

    在当今互联的世界中,了解客户端的地理位置对于提供个性化服务和增强用户体验至关重要,使用本文为大家介绍了SpringBoot获取客户端IP地理位置的相关方法,需要的小伙伴可以参考下
    2023-11-11
  • java解析xml之jdom解析xml示例分享

    java解析xml之jdom解析xml示例分享

    JDOM是专门为Java打造的API,JDOM采用了Java中的Collection架构来封装集合,是Java爱好者更加熟悉的模式,下面看使用示例
    2014-01-01
  • springboot+vue 若依项目在windows2008R2企业版部署流程分析

    springboot+vue 若依项目在windows2008R2企业版部署流程分析

    这篇文章主要介绍了springboot+vue 若依项目在windows2008R2企业版部署流程,本次使用jar包启动后端,故而准备打包后的jar文件,需要的朋友可以参考下
    2022-12-12
  • 解读JVM的生命周期是怎么样的

    解读JVM的生命周期是怎么样的

    JVM的生命周期包括启动、运行和终止三个阶段,启动阶段包括创建JVM实例、加载和初始化核心类库、加载main方法所在的类和初始化类,运行阶段包括执行main方法、类加载、字节码执行、内存管理、线程管理和异常处理,终止阶段包括正常终止、异常终止和外部终止
    2025-03-03
  • JAVA 8 ''::'' 关键字详解

    JAVA 8 ''::'' 关键字详解

    这篇文章主要介绍了JAVA 8 '::' 关键字,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09
  • SpringBoot集成tensorflow实现图片检测功能

    SpringBoot集成tensorflow实现图片检测功能

    TensorFlow名字的由来就是张量(Tensor)在计算图(Computational Graph)里的流动(Flow),它的基础就是前面介绍的基于计算图的自动微分,本文将给大家介绍Spring Boot集成tensorflow实现图片检测功能,需要的朋友可以参考下
    2024-06-06
  • JVM堆内存溢出后,其他线程是否可继续工作的问题解析

    JVM堆内存溢出后,其他线程是否可继续工作的问题解析

    这篇文章主要介绍了JVM 堆内存溢出后,其他线程是否可继续工作?,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-08-08

最新评论