KMP算法

来源:互联网 发布:货运信息部软件 编辑:程序博客网 时间:2024/06/13 06:27

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

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

模式匹配

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

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

  代码:

int index(char *pstr1,char *pstr2,int pos)
{
//求两个字符串的长度不包括'/0'
int length1=0;
int length2=0;
int i=0;
while(*(pstr1+i)!='/0')
{
i
++;
}
length1
=i;
i
=0;
while(*(pstr2+i)!='/0')
{
i
++;
}
length2
=i;

int j=0;
i
=pos;
while(i<length1&&j<length2)
{
if(*(pstr1+i)==*(pstr2+j))
{
j
++;
i
++;
}
else
{
i
=i-j+1;j=0;
}
}
if(j>=length2)
{
return i-j;
}
else
{
return -1;
}

}

假设这里给出主串: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值是相等的。

代码:

//求出子串next数组
void get_next(char *pstr,int *next)
{
int i=0;
*next=-1;
int j=-1;
while(*(pstr+i)!='/0')
{
if(j==-1||*(pstr+i)==*(pstr+j))
{
i
++;
j
++;
if(*(pstr+i)!=*(pstr+j))*(next+i)=j;
else *(next+i)=*(next+j);
}
else
{
j
=*(next+j);
}
}

}

int index_KMP(char *pstr1,char *pstr2 ,int pos)
{
//求两个字符串的长度不包括'/0'
int length1=0;
int length2=0;
int i=0;
while(*(pstr1+i)!='/0')
{
i
++;
}
length1
=i;
i
=0;
while(*(pstr2+i)!='/0')
{
i
++;
}
length2
=i;

int j=0;
i
=pos;
int *next;
next
=new int[length2];
get_next(pstr2,next);
while(i<length1&&j<length2)
{
if(*(pstr1+i)==*(pstr2+j)||j==0)
{
j
++;
i
++;
}
else
{
j
=next[j];
}
}
if(j>=length2)
{
return i-j;
}
else
{
return -1;
}
}

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

 

转自:http://www.cnblogs.com/couhujia/archive/2011/04/06/2006307.html