KMP算法介绍和实现(转载)
时间:2011-04-13 来源:吾爱易逝
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)