KMP算法

  KMP算法是字符串处理算法的一种经典算法。字符串中的一些算法在C++中需要编程者自己实现,在C#中的话String类的功能及其强大,编程者自己调用该类一些功能完成字符串处理。那么可能就导致错过这些字符串基本的经典算法。而在字符串处理这些算法中,KMP算法可谓是经典算法。

  那么首先看下面一个一般求子串在主串中的位置的算法。

模式匹配

  有两个字符串S1(长度为n)和S2(长度为m)(n>m),求S2在S1中的字符串匹配的第一个位置。子串的定位操作通常称做串的模式匹配。其中S2成为模式串,S1为主串

  一般基本思想:从主串S1第pos个字符起和模式的第pos个字符比较,若相等,则继续依次比较后续字符;否则从主串S1的下一个字符起在重新和模式字符相比较。以此类推,直至模式S2中的每个字符依次和主串S1中的一个连续的字符序列相等,那么就匹配成功,否则就没有匹配成功。

  代码:

 1 void getNext(char *pstr,int *next)
 2 {
 3     int i=1;
 4     *next = -1; 
 5     int j = -1; 
 6     while(*(pstr+i)!='\0')
 7     {   
 8         if( -1 == j || *(pstr+i) == *(pstr+j))
 9         {   
10             ++i;
11             ++j; 
12             if(*(pstr+i)!=*(pstr+j))
13                 next[i]=j;
14             else
15                 next[i]=next[j];
16         }   
17         else
18         {   
19             j=next[j];
20         }   
21     }   
22 }

 

假设这里给出主串:a b a b c a b c a c b a b 

  子串(模式):a b c a c

  第一趟匹配(i从0开始)

  i=2

  a b a b c a b c a c b a b

  a b c

  j=2

  第二趟

  i=1

  a b a b c a b c a c b a b

  _ a

  j=0

  第三趟

  i=6

  a b a b c a b c a c b a b

  _ _ a b c a c

  j=4

  第四趟

  i=3

  a b a b c a b c a c b a b

  _ _ _ a

  j=0

  第五趟

  i=4

  a b a b c a b c a c b a b

  _ _ _ _ a

  j=0

  第六趟

  i=10

  a b a b c a b c a c b a b

  _ _ _ _ _ a b c a c _

  j=5

  性能分析:

  情况好:时间复杂度O(m+n)

  情况差:时间复杂度O(m*n)  

KMP算法

  KMP算法其实是上面介绍模式匹配的一种改进。可以发现上面的算法,每一趟匹配过程中出现字符不等时,回溯指针,如果将其改进,指针不回溯,利用已经得到的部分匹配的结果将模式向右移动的更远一些,然后继续比较。那么算法性能会得到大大的提高。

  看到上面的过程,在第三趟的匹配过程中,当i=6,j=4字符不等时,又从i=3,j=0重新开始比较。其实可以容易发现,在i=3和j=0,i=4和i=0以及i=5和j=0这3次比较都是不必进行的。因为从第三趟部分匹配结果就可以得出,主串中第3,4,5个字符是’b’,’c’,’a’。而模式中第一个字符是’a’,因此无需和这3个字符进行比较了,紧需要向右移动3个字符继续进行i=6,j=1时字符串比较就行了。

那么一种理想的模式匹配就可以的出来了。

  第一趟

  i=2

  a b a b c a b c a c b a b

  a b c

  j=2

  第二趟

  i=6

  a b a b c a b c a c b a b

  _ _ a b c a c

  j=4

  第三趟

  i=10

  a b a b c a b c a c b a b

  _ _ _ _ _ a b c a c _

  j=5

  该过程是结合上面例子给出来的,下面给出一般情况。

  假设(n>m)

  主串:s0 s1 s2 s3 s4 s5 s6 …… s(n)

  模式:p0 p1 p2 p3 p4……….p(m)

  当匹配过程中产生失配(s(i)!=p(j))时,主串的第i个字符应与模式中的哪个字符相比较?

  假设此时与模式中的第k(k<j)个字符相比较,那么就有

  'p0p2…p(k-1)'='s(i-k)s(i-k+1)…s(i-1)'     --式1

  (就好像上面中绿的的字符'a',这里是从模式中第1个字符开始比较与主串中字符'a'相同)。

  当匹配失配时(s(i)!=p(j)),可以得到

  'p0p1p2p3…p(j-1)'='s(i-j)s(i-j+1)…s(i-1)'   --式2

  从式2可以得到

  'p(j-k)p(j-k+1)…p(j-1)'='s(i-k)s(i-k+1)..s(i-1)' --式3

  由式1和式3可以得到

  'p0p1…p(k-1)'='p(j-k)p(j-k+1)…p(j-1)'      --式4

  若令next[j]=k,则next[j]表明当模式中第j个字符与主串中相应字符失配时,在模式中需要重新和主串中该字符进行比较的字符位置。那么next 函数定义为:

        (1)-1 当j=0时

      next[j]=  (2)max{k|0<k<j 且式4成立}

        (3)0 其他情况

      那么此时next值如何求得呢?

      由定义知道

      next[0]=-1;

      设next[j]=k,这表明在模式串中有这样关系

      'p0p1…p(k-1)'='p(j-k)p(j-k+1)…p(j-1)'   (0<k<j)    --式5

      此时next[j+1]的值有两中情况:

      (1)       若p(k)=p(j),则:

           'p0p1…p(k)'='p(j-k)p(j-k+1)…p(j)'        --式6

                即next[j+1]=k+1

      (2)       若p(k)!=p(j),则:

               'p0p1…p(k)'!='p(j-k)p(j-k+1)…p(j)'       --式7

    此时可以把该问题看成模式匹配的问题,整个模式串既是主串又是模式串,这里应将模式向右移动next[k](模式中第k个字符与主串失配时,需要移动  的位置)位置,和主串中的第j个字符相比较。若next[k]=k’,且p(j)=p(k’),则可以得到

              next[j+1]=next[k]+1即 next[j+1]=next[next[j]]+1

    那么还要注意下当模式中上一个字符串与下一个字符串相等时候,它们next值是相等的。

代码:

 1 int IndexKmp(char *desc,char *src,int pos)
 2 {
 3    int srcLength = strlen( src ); 
 4    int i=pos;
 5    int j=0;
 6    int index=0;
 7    int *next=new int[srcLength];
 8    getNext(src,next);
 9    while( desc[i]!='\0' && src[j]!='\0' )
10    {
11        if( desc[i] == src[j] )
12        {
13            ++i;
14            ++j;
15        } 
16        else
17        {
18            index +=j-next[j];
19            j=next[j];
20            if(j== -1)
21            {
22                ++i;
23                j=0;
24            }
25        }
26    }
27    delete [] next;
28    if(j>=srcLength)
29    {
30        return index;
31    }
32    else
33        return -1;
34 }

性能分析:时间复杂度O(n+m)

posted @ 2011-04-06 08:48  胡佳180815  阅读(3329)  评论(3编辑  收藏  举报