KMP算法的剖析与实现

来源:互联网 发布:实惠猪软件下载 编辑:程序博客网 时间:2024/05/17 08:13

KMP算法的实现

字符串的匹配主要是查找子串在主串中出现的位置,主要的算法有两种Brute-Force及其改进的KMP算法,本文将详解之。

 

1.Brute-Force的主要思想

    若有主串S=“abaababaddecab”,子串T=“abad”要在S中找到T的正确匹配位置。

第一次匹配

a   b   a  a   b   a   b   a d   d   e  c   a   b

a   b   a   d (此时S.i(3)!=T.j(3))

第二次匹配则回退i 指向b

a   b   a  a   b   a   b   a d   d   e  c   a   b

     a   b  a   d(i=1,j=0此时依然S!=T)

第三次匹配 从 i=2开始

a   b   a  a   b   a   b   a d   d   e  c   a   b

         a   b  a   d(i=3时不相等)

第四次匹配从i=3开始

a   b   a  a   b   a   b   a d   d   e  c   a   b

              a  b   a   d(i=6时不匹配)

第五次匹配回退i=4

a   b   a  a   b   a   b   a  d   d   e  c   a   b

                  a   b   a   d

第六次匹配i=5

a   b   a  a   b   a   b   a  d   d   e  c   a   b

                       a   b   a   d(S=T匹配成功)

这种思路是从主串S的第一个字符开始不断地匹配,如果匹配失败则就回退到i=i-j+1的位置继续匹配,直至成功。

 

具体的测试代码如下:

#include<iostream>

#include<string>

using namespace std;

 

 

int findtins(string S,string T)

{

     int i=0,j=0;

     while(i<S.length()&& j<T.length())

     {

         if(S[i]==T[j])

         {

              i++;

              j++;

         }

         else

         {

              i=i-j+1;

              j=0;

         }

 

     }

     if(j>=T.length())

         returni-j+1;

     else

         return-1;

}

 

 

int main()

{

     stringS="abaababaddecab";

     stringT="abad";

     intresult=findtins(S,T);

     cout<<"子串在主串中的位置是:\n"<<result<<endl;

     return 0;

}

运行结果:


这就是Brute-Force算法匹配过程实现。

2.KMP算法

KMP算法是在Brute-Force上的改进,其主要思想是:每当一次匹配过程中出现字符不相等时,不需要回退主串的指针,而是利用前面已经得到的“部分匹配”的结果,将子串向右移动若干个字符后,继续与主串的当前字符比较。

例如假设有主串S=“abaababaddecab“,子串T=”abad“,则KMP的匹配过程:

a   b   a  a   b   a   b   a  d   d   e  c   a   b(S.i)

a   b   a   d(T.j)(i=3,j=3时不匹配)

这时候如果是Brute-Force算法则会将i回退到i=1,j=0的位置比较S.1与T.0,但其实这是没有必要的因为T.0!=T.1,而T.1==S.1

所以T.0一定不等于S.1,又因为S.2==T.2,T.0==T.2,所以T.0一定等于S.2,所以直接比较S.3和 T.1。

所以下一次比较为:

a   b   a  a   b   a   b   a  d   d   e  c   a   b(S.i)

         a   b  a   d (T.j)(此时比较S.3!=T.1)

当前的j=1和主串不匹配,next[1]=0(后面介绍),则从j=0,i=3开始匹配S.3和T.0

a   b   a  a   b   a   b   a  d   d   e  c   a   b(S.i)

             a   b  a   d (T.j)(此时i匹配到i=6,j=3失配)

此时的next[3]=1,所以下一次比较S.6和T.1即

a   b   a  a   b   a   b   a  d   d   e  c   a   b(S.i)

                       a   b  a   d (T.j)(i=8,j=3,完全匹配)

 

这就是KMP的匹配过程,可以看到这种匹配方式只需要四次就可以找到子串的位置。

 

KMP算法的关键在于如何确定next[j]=k的值,这个表达式的意思是在子串的地j个元素和主串当前字符不匹配时,直接把当前的S串中的字符和子串的第k个字符开始比较(也可以说是把子串向右移动到k的位置,然后把k和当前的S中的字符比较)。问题的关键在于k值如何确定?这就是next函数的意义了。

 

若主串S=”S0S1S2S3S4…….Sn”,T=”T0T1T2T3T4………Tn”

若比较过程中有以下关系式1

Si-jSi-j+1…..Si-kSi-k+1….Si-1Si

 

T0T1…..Tk-2Tk-1Tk…..Tj-k-1Tj-kTj-k+1….Tj-iTj

若此时有Si!=Tj

而上图中红色的部分分别都相等则下一次比较从Tk开始,此时的next[j]=k。

从以上的分析可以知道next函数的值和主串没有关系,只和子串有关,下面具体分析一下如何在一个子串中求出next函数的值。

假设有子串T=“abaabacaba“,求其next[]的过程如下:

j         0   1   2  3   4   5  6   7   8   9

子串     a  b   a   a  b   a   c  a   b   a

Next[j] -1 0   0   1  1   2   3  0   1   2

 

下面解释一下这九个next值是如何得到的

Next[0]=-1,规定。next[1]=0,若T.j=b失配则之前的a与之前的没有相等的,所以必须从j=0,即netx[1]=0开始匹配。Next[2]=0,若此时T.2失配,他之前的元素为b,前面没有与b相等的元素,所以也必须从头开始匹配。Netx[3]=1,因为3之前有字母a,与第一个a相等,所以此时应该从1位置匹配(结合前面的那个关系式1)。

Next[4]=1,4之前的元素为a,与第一个a相等,所以下一个比较的字符是1.现在来求next[5],其前面的T.4=b,next[4]=1,又因为T.4=T.1=b,所以next[5]=next[4]+1=2;现在求next[6],T.6之前的数为T.5=a,因为netx[5]=2;T.5==T.2,所以next[6]=next[5]+1=3;接下来求next[7],因为next[6]=3,但是T.6!=T.3,再继续比较T.6和T.(next[3]=1),显然T.6!=T.1

在继续比较T.6和T.(next[1]=0),也不相等,下一次比较时next[0]=-1了则next[7]=next[0]+1=0(此时相当于从头开始匹配),next[8],next[9]以此类推。

这就是next函数的求解过程。

Getnext()函数的代码:

void getnext(string T,int next[])//求T中的next值放入next数组中

{

     int j,k;

     j=0;

     k=-1;

     next[0]=-1;

     while(j<T.length())

     {

         if(k==-1|| T[j]==T[k])

         {

              j++;

              k++;

              next[j]=k;

         }

         else

         {

              k=next[k];//向右移动子串

         }

     }

}

KMP的实现代码:

 

#include<iostream>

#include<string>

using namespace std;

 

void getnext(string T,int next[])

{

     int j,k;

     j=0;

     k=-1;

     next[0]=-1;

     while(j<T.length())

     {

         if(k==-1|| T[j]==T[k])

         {

              j++;

              k++;

              next[j]=k;

         }

         else

         {

              k=next[k];

         }

     }

    

    

    

}

 

int kmpfind(string S,string T,int next[])

{

     int i=0,j=0;

     while(i<S.length()&&j<T.length()  )

     {

         if(S[i]==T[j])

         {

              i++;

              j++;

         }

         else

         {

              j=next[j];

         }

        

     }

     if(j>=T.length())

     {

         returni-T.length()+1;

     }

     else

     {

         return-1;

     }

}

int main()

{

     stringS="abaababaddecab";

     stringT="abad";

     intnext[100];

     getnext(T,next);

     intresult=kmpfind(S,T,next);

     cout<<"kmp查找子串在主串的位置为:\n"<<result<<endl;

    

     return 0;

}

运行结果:



下面是next函数的改进版本:

void getnextval(string T,int nextval[])

{

     int j,k;

     j=0;

     k=-1;

     next[0]=-1;

     while(j<T.length())

     {

         if(k==-1|| T[j]==T[k])

         {

              j++;

              k++;

                         if(T[j]!= T[k])

                           {

                              nextval[j]=k;

                            }

                          else

                   {

                            nextval[j]=nextval[k];

                             } 

         }

         else

         {

              k=next[k];

         }

     }  

}

关于nextval的说明:

               j          0   1  2   3   4  5   6   7  8   9   10

               子串      a   b  c   d   a   b   c  d   a   b    d

               Next[j]  -1 0   0   0  0   1   2  3   4    5  6 

             Nextval[]-1  0  0   0   -1 0   0   0  -1   0   6

其中nextval[]存放的是改进后的next函数的值,如果主串的对应的字符S.i和T.8失配,则应该取T.next[8]与主串S.i比较,因为T.4=T.8=a,所以一定与S.i失配,则取T.next[4]与S.i比较,即T.0和S.i比较,因为T.0等于a,所以也失配,则取next[0]=-1,这时模式串停止向右滑动。其中T.4,T.0和S.i的比较是没有意义的,所以需要修改next[8]和next[4]的值为-1,同理用这种方法修正其他的next函数值。
0 0
原创粉丝点击