KMP 字符串查找

来源:互联网 发布:全新硬盘重装mac系统 编辑:程序博客网 时间:2024/05/30 23:03


本文地址:http://blog.csdn.net/spch2008/article/details/9097371


KMP算法解析

    这篇文章讲解的很详细:http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html

   

KMP算法实现

    1. 匹配过程

         先假设next数组值为 -1 0 0 0 0 1 2 0,即数组前面有一个-1。

                A    B    C    D    A    B    D

               -1    0     0    0    0     1    2     0

         我认为可以这样理解,当匹配失败的时候,让前缀移动到后缀的位置,从前缀的下一个字符开始比较,比如:

        

          当D比较失败的时候,将(AB)前缀移动到后缀(AB)位置,从前缀的下一个字符开始比较。

        

         这样,将next数组错位,即加上-1以后的数组。这样,每一个元素对应的是前面已经匹配的字符个数。比如:当比较D失败的时候,

         查next数组,D对应的值为2,我们认为前面已经有两个元素匹配上了,这样,只需要将j(模式串匹配下标)移动到前缀(AB)后面,j = 2(索引从0开始)。

         而j为负数的时候,意味着此趟模式匹配结束,主串需要移动,开始一个新的比较。



         下面观察匹配过程

        

          模式串匹配到D的时候失败, 此时模式串索引j=6。失败时,j应该回退,j = next[j],即j为2。从模式串下标为2的地方继续与主串进行匹配。

        

        模式串与主串比较失败,此时j = 2。则j = next[j],j的新值为0。

       

         失败,此时j = 0, 则j = next[j], j的新值为-1。当为-1时,模式串已经回退到头,不能进行回退了,此时需要开始一个新的比较啦。

         主串索引加1,继续与模式串进行比较。

        

        当主串与模式串匹配的时候,主串与模式串同时递增,进行下一个字符比较。

 
         匹配过程代码:        

    /* Perform the search */    for (int i= 0, j=0; text[i] != '\0'; ) {        if (j < 0 || text[i] == pattern[j]) {            ++i, ++j;            if (pattern[j] == '\0') {                result = text+i-j;                break;            }        }        else j = next[j];    }

    2. next数组

         next数组的计算仅与模式串有关而与主串没有关系。next[j] 可以理解为:匹配 Pj 时候,前面已经匹配上的元素的个数,next[j]。

        

         P为模式串,Pj在next数组中的值为K,因为到Pj这,既是真前缀又是真后缀的最大长度为K。Pj之前的匹配数目。


         next数组定义:j=0时,next[j] = -1; 当前缀与后缀相当时,next[j]为最大串长;其他情况next[j] = 0。(下标与长度差1)

     

 

          可以通过上述定义获得next数组,但可以用递推法求next数组,使计算简化:

          已知next[0] = -1,next[j] = k,则next[j+1] = ?

          next[j] = k的时候,有‘P0P1...Pk-1’ = ‘Pj-k...Pj-1

  •  若Pk = Pj, 则next[j+1]  = k + 1 = next[j] + 1
  •  若Pk != Pj,则 next[j+1] = ?

               Pk != Pj 时,显然 ‘P0P1...Pk-1Pk’  !=  ‘Pj-k...Pj-1Pj’,那么我们猜想是否有一个p  < k,

               使得'P0P1…Pp' = 'Pj-p…Pj-1Pj' 成立?即next[j+1] = p + 1(p索引,p+1个数)。那么p应该为多少呢?显然应该为p = next[k]。

               因为这相当于用next函数进行模式串与模式串的匹配。

              主串:  P0P1P2…Pk…………Pj-k…PjPj+1

              模式串: P0P1…Pk

              当模式串Pk与主串Pj匹配失败时,根据KMP匹配原理,主串Pj应和模式串的next[k]位置上的元素进行比较。

              那么k的值为多少呢?next[j] 为 Pj 前已经匹配的元素个数,k = next[j]。

              总结一下: next[j+1] = p + 1,p = next[k], k = next[j], j已知。所以 next[j+1] = next[next[j]] + 1


     代码:        

    /* Construct the lookup table */    int *next = new int[strlen(pattern)+1];    next[0] = -1;    for (int j=0; pattern[j] != '\0'; j++) {        next[j+1] = next[j] + 1;        while (next[j+1] > 0 && pattern[j] != pattern[next[j+1]-1])            next[j+1] = next[next[j+1]-1] + 1;    }

      主要思想:首先假设Pk = Pj,则存在递推关系next[j+1] = next[j] + 1。然后,验证此递推关系是否正确。

                  

        

            上图中,next[j] = k,表示匹配到 Pj 时,前面已经有k个元素成功匹配。现在假设Pk = Pj, 则next[j+1] = k + 1 = next[j] + 1,表示匹配到Pj+1的时,

            已经匹配的元素数目为 (k + 1),包含Pj,不包含Pj+1。若检查发现不匹配,则 Pj 需要与 Pp进行比较。此时P0P1…Pk为模式串,则p= next[k]。

            现在要求k的值,而next[j+1] = k + 1, 则k = next[j+1] - 1, 所以 p = next[ next[j+1] - 1]。同样,我们假设Pj = Pp, 则next[j+1] = p + 1。

         

           Pj = Pp,next[j+1] = p + 1,表示匹配到Pj+1时,前面已经匹配了p个元素。所以next[j+1] = p + 1 = next[ next[j+1] - 1] + 1。

     
           注意:比较的基准发生了变化,next[j+1] = next[j] + 1,这种情况是在两个元素相等的时候,通过递推得到的。而上图中,Pj 与Pk不等的时候,

                       不能用P0P1P2…Pk-1建立起来的基准了,要另起炉灶,重新开始。所以next[j+1]的取得只能通过定义,而不能通过递推关系式得到。

     


全部代码:

const char *kmp_search(const char *text, const char *pattern){        const char *result = NULL;    if (pattern[0] == '\0')        return text;    /* Construct the lookup table */    int *next = new int[strlen(pattern)+1];    next[0] = -1;    for (int j=0; pattern[j] != '\0'; j++) {        next[j+1] = next[j] + 1;        while (next[j+1] > 0 && pattern[j] != pattern[next[j+1]-1])            next[j+1] = next[next[j+1]-1] + 1;    }    /* Perform the search */    for (int i= 0, j=0; text[i] != '\0'; ) {        if (j < 0 || text[i] == pattern[j]) {            ++i, ++j;            if (pattern[j] == '\0') {                result = text+i-j;                break;            }        }        else j = next[j];    }    delete next;    return result;}