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)