KMP与枚举算法的实现例子以及性能分析(Java)

来源:互联网 发布:在线视频教育网站源码 编辑:程序博客网 时间:2024/06/04 18:19

在上一篇《KMP算法的Java实现例子以及测试分析》中已经介绍了KMP概念以及原理,这一篇中结合枚举字符串算法比较一下两者的性能,并且给出了KMP、枚举算法的Java实现,以及测试代码与结果,放在这里供大家参考、交流,希望对大家有所帮助。

KMP算法的优势主要有两点:1,无回溯,即对于主串的循环是线性的,也就是说,一直朝前招,指针是递增的,而不会回退;2,对子串加以预处理,从而找到匹配失败时子串回退的位置,即找到匹配失败时的最合适的回退位置,而不是回退到子串的第一个字符。

枚举算法就没有上面两个优点,它是基于回溯的,并且子串匹配失败时直接会退到子串的第一个字符。

下面给出具体例子:

一、三个文件源代码

KMP.java(KMP算法的实现)

源代码为:

package algorithm.kmp;

/**
 * KMP算法的Java实现例子与测试、分析
 * @author 崔卫兵
 * @date 2009-3-26
 */
public class KMP {
 static int[] P;
 /**
  * 对子串加以预处理,从而找到匹配失败时子串回退的位置
  * @param B,待查找子串的char数组
  * @return
  */
 public static int[] preProcess(char [] B) {
  int size = B.length;
  int[] P = new int[size];
  P[0]=0;
  int j=0;
  for(int i=1;i<size;i++){
   while(j>0 && B[j]!=B[i]){
    j=P[j];
   }
   if(B[j]==B[i]){
    j++;
   }
   P[i]=j;
  }
  return P;
 }
 
 /**
  * KMP实现
  * @param parStr
  * @param subStr
  * @return
  */
 public static void kmp(String parStr, String subStr) {
  int subSize = subStr.length();
  int parSize = parStr.length();
  char[] B = subStr.toCharArray();
  char[] A = parStr.toCharArray();
  int j=0;
  int k =0;
  for(int i=0;i<parSize;i++){
   while(j>0 && B[j]!=A[i]){
    j=P[j-1];
   }
   if(B[j]==A[i]){
    j++;
   }
   if(j==subSize){
    j=P[j-1];
    k++;
   }
  }
 }
 
 public static void main(String[] args) {
  //回退位置数组为P[0, 0, 0, 0, 0, 0]
  kmp("abcdeg, abcdeh, abcdef!这个会匹配1次","abcdef");
  //回退位置数组为P[0, 0, 1, 2, 3, 4]
  kmp("Test ititi ititit! Test ititit!这个会匹配2次","ititit");
 }
}

SubStrFind.java(枚举算法的实现)

源代码为:

package algorithm.kmp;

/**
 * 字符串查找(枚举方法查找)
 * 在一个字符串中查找是否包含某一子串
 * @author 崔卫兵
 * @date 2009-3-26
 */
public class SubStrFind {
 /**
  * 字符串查找(枚举方法)
  * @param parStr
  * @param subStr
  * @return
  */
 public static void strFind(String parStr, String subStr) {
  int parSize = parStr.length();
  int subSize = subStr.length();
  
  char[] B = subStr.toCharArray();
  char[] A = parStr.toCharArray();
  
  boolean flag=true;
  int times = 0;
  int j=0;
  int k =0;//k记录父串匹配正确的位置或者匹配不正确的回退位置
  //i记录父串的当前比较字符的位置
  for(int i=0;i<parSize;i++){
   if(B[j]==A[i]){
    j++;
    //第一次时记录父串回退位置
    if(flag){
     k=i;
     flag=false;
    }
   }else{
    //不匹配时回退位置重置,比较继续进行
    i=++k;
    j=0;
    flag=true;
   }
   if(j==subSize){
    j=0;//匹配时只需把子串回退位置重置,比较继续进行
    flag=true;
    times++;
   }
  }
 }
}

TimeConsumer.java(性能测试代码)

源代码为:

package algorithm.kmp;

/**
 * 字符串查找时间计算(KMP算法与枚举查找法)
 * @author 崔卫兵
 * @date 2009-3-26
 */
public class TimeConsumer {
 private static int times = 1000000;
 public static void test(String parStr, String subStr) {
  long start = 0;
  long end = 0;
  System.out.println("父串: "+parStr);
  System.out.println("子串: "+subStr);
  start = System.currentTimeMillis();
  KMP.P = KMP.preProcess(subStr.toCharArray());
  for(int i=0;i<times;i++){
   KMP.kmp(parStr,subStr);
  }
  end = System.currentTimeMillis();
  System.out.println("Time for KMP: "+(end-start));
  
  start = System.currentTimeMillis();
  for(int i=0;i<times;i++){
   SubStrFind.strFind(parStr,subStr);
  }
  end = System.currentTimeMillis();
  System.out.println("Time for Enumeration: "+(end-start));
  System.out.println("-------------------------------------");
 }
 
 public static void main(String[] args) {
  test("abcdeg, abcdeh, abcdef!这个会匹配1次","abcdef");
  test("Test ititi ititititd! Test ititititd!这个会匹配2次","ititititd");
 }
}

 

二、输出结果

父串: abcdeg, abcdeh, abcdef!这个会匹配1次
子串: abcdef
Time for KMP: 953
Time for Enumeration: 797
-------------------------------------
父串: Test ititi ititititd! Test ititititd!这个会匹配2次
子串: ititititd
Time for KMP: 1109
Time for Enumeration: 1344
-------------------------------------

 

 

三、总结

从输出结果中可以看出,对于第一次比较,子串为"abcdef",里面没有任何重复的字符,所以其回退位置数组的每一个元素值全为0,即匹配失败时,自然回退到子串的第一个字符,前文已经说过KMP算法有两个优势:无回溯、回退位置数组,在子串里没有任何重复字符的情况下,回退位置数组就失去作用了,而且还会带来一些额外开销(要计算出回退数组),并且KMP中还有更多的判断分支,这也许就是测试一KMP时间更多的原因所在,对于测试二,KMP自然表现优秀,两个优势都凸显出来了。

对于子串很简单的,譬如10个字符以内,而且又没有字符重复,用枚举字符串查找即可,对于子串较长、重复多时,用KMP较好。

最近在分析Java String的indexOf方法的实现,希望那里有更好的方法。

原创粉丝点击