人生如戏,全靠演技
享受编程之乐,Coding美好生活

   

 

序:很久没做算法题了,为了回顾一下自己的算法知识,方便下次理解,特地记录自己一些对一些算法的理解。

 

约定:

模式串  ababcd

文本串  abababcd

M代表模式串,W代表文本串

 

 

kmp算法包括两个部分,1.计算模式串的next数组。 2.kmp主程序,模式串与主串(即文本串)的匹配。

 

Next数组

next[i]表示字符串第i个字符可匹配的最近的下标(挺拗口的),作用是记录已经遍历过的字符内的信息。

先看计算next数组的程序,

 

    private static int[] getNext(char[] str) {
        int length = str.length;
        int[] next = new int[length];
        next[0] = -1;
        
        int k = -1;
        for(int i=1; i<length; i++) {
            while(k > -1 && str[i] != str[k+1]){
                k = next[k];
            }
            if(str[i] == str[k+1]) {
                k++;
            }
            next[i] = k;
        }
        return next;
    }

字母下方即是模式串的next数组

a  b  a  b   c  d

-1 -1  0  1  -1  -1

从结果来看,我们可以看到next[3] = 1,含义是它的可匹配的最近的下标是1。其实从这时候来看还是觉得没什么作用,我们下面在匹配文本的时候具体分析。

 

kmp主程序

下面是kmp主算法:

   1:      public static int kmp(final char[] text, final char[] pattern) {
   2:          // 检查是否为空
   3:          if(isEmpty(text) || isEmpty(pattern)) {
   4:              return -1;
   5:          }
   6:          
   7:          int lengthText = text.length;
   8:          int lengthPattern = pattern.length;
   9:          int[] next = getNext(pattern); // 获取模式串的next数组
  10:          
  11:          int k = -1;
  12:          for(int i=0; i<lengthText; i++) {
  13:              while(k > -1 && text[i] != pattern[k+1]) {
  14:                  k = next[k];           // 若不相等则找出与k可匹配的最近的下标
  15:              }
  16:              if(text[i] == pattern[k+1]) {
  17:                  k++;
  18:              }
  19:              if(k == lengthPattern - 1) {
  20:                  // 表示找到了,再赋值k,继续下次寻找
  21:                  System.out.println("find position is " + (i - lengthPattern + 1));
  22:                  k = next[k];
  23:              }
  24:          }
  25:           
  26:          return -1;
  27:      }

看了上面的代码,可以发现其实和计算next数组的代码差不多。其实他们的本质是一样的,计算next数组是模式串匹配自己,而kmp主程序是模式串匹配文本串。

 

匹配过程

从文本串的第一个字母开始匹配,文本串下标为int  i,初始化为0。模式串的下标为k+1,k为int型,k初始化为-1。

下面是遍历到i = 4, k = 3,时的情况,当发现两者不相同时,k=next[k],所代表的意义是模式串的下标k位置的字符与前面的字符串哪一个字符最近可匹配,就是这个步骤可以省去和前面字符比较的时间(因为前面都相同)。所以当你发现文本串i位置和模式串j位置匹配不成功时,你可以比较模式串(next[j-1] +1)和文本串的i位置是否相等,如不相等如此循环查找(kmp主程序的13~15行)。所以此时的情况是k = next[k]之后k = 1,然后比较M(k+1)与W(i),我们发现此时是相等的,这一步就是kmp和普通模式串匹配的差别,它省去了再去从模式串的开头进行比较,而是直接从下标2开始比较。

0 1  2 3  4 5  6  7

a  b  a  b a b  c  d

a  b  a  b c  d

 

kmp的算法复杂度(O(n))

设主串的长度为n,模式串为m,n>m。

我们可以这样计算,首先是对主串的遍历,复杂度是n,主串的循环内的while循环,我们可以追踪k的变化,k++的过程只能在每次循环时进行,k=next[k]是k的减少,k++最多发生n次,k=next[k]发生最多的次数是n/m *m = n,所以总的算法复杂度是3*n,就是O(n)的。

 

kmp的用处场景

1.查找模式串是否在目标串

2.计算字符串是由哪个或哪几个字符串循环而来

3.查找模式串在目标串的哪些位置

4.最长公共子串

 

kmp的优化

以上我对kmp的理解来自于《算法导论》这本书,其实kmp还可以优化。一位大牛在网上论述了他对kmp的优化,下面是他的链接http://www.if-yu.info/2010/11/23/kmp-complexity.html

他的kmp思想跟导论不同在于next数组--直接记录每个字符匹配失败后应该去匹配哪个字符,这样就可以优化重复匹配失败的字符,而导论检查的是前一个字符。

 

后记:转载请标明出处,谢谢。  ----- doubleHHHH

posted on 2012-05-29 15:35  doubleHHHH  阅读(2185)  评论(4编辑  收藏  举报