KMP 讲解 和 KMP的 strstr 实现

来源:互联网 发布:premiere软件免费下载 编辑:程序博客网 时间:2024/05/02 21:15

网上关于KMP的讲解已经够多了,但我感觉很多的文章对于一些关键点的解释还不够清晰,如果你还不知道KMP算法,那建议你先百度了解一番KMP,如果了解完后感觉大脑还是塞塞的,思路不够清晰的话再来看看我这篇文章。这里就不再对KMP从头到尾讲述了。毫无疑问,KMP的关键点就是求next数组,我只针对如下两点做解释以及给与数学证明。

针对字符串str求它的next数组:

1 next[i]的意义:

next[i]的意义是当下标为i的元素不匹配时,它的前缀和后缀能够匹配的最长长度,计算前缀和后缀的时候是不把当前字符考虑进去的,因为我是当前字符失配时跳转到下一个位置(也就是说这个字符之前的字符都匹配了),以及计算前缀时不能把最后一个字符包括,计算后缀时当然也不能把第一个字符考虑进来,比如“AAABC”,当计算第四个字符B的时候,它的前缀和后缀匹配的最长长度是2,因为第一个和最后一个字符不能互相包括嘛。 所谓的前缀 和 后缀 都是从左到右计算的,也就是下标从小到大的,比如字符串“ABABB”,计算最后一个字符B的next值时,后缀为AB。

好了,理解了next[i]的意义后,我们说说next[0]和next[1]的值,这两个的值是确定的,next[0]=-1,next[1]=0。我来解释为什么这么设置它们的值。我们在在计算第J个字符的next值时,让i=J-1,如果str[ next[i]  ] 与 str[ J-1 ]不匹配时,我们会进行跳转,也就是让i=next[i](待会会解释为什么这么跳转),但是我们需要一个终止条件,也就是前缀长度为1时也不匹配的时候,这时应该终止,而当第0个元素与str[J-1]不匹配时,此时恰好i=next[0],i=-1了,此时我们知道前后缀能匹配的最长长度为0,因此我们把next[0]设置为-1,当然设置为-2,-3都可以,因为它只是一个终止条件,好让我们判断i=该值时,匹配长度为0,可以计算下一个字符的next值了。

为什么next[1]=0呢,因为,next[1]之前只有一个字符,那个字符既是它的前缀也是后缀,而next数组的定义中已经说明了,计算的时候不能互相包括,所以next[1]直接设置为0,真正的next数组从2开始计算起。这里我给出当str [next[i]]==str[ J-1 ]时,next[ j ]=next[i]+1的数学证明:

采用反证法: 

假设第J个字符存在更大的next值,设这个值为K,那么K>next[i]+1,则 K-1>next[ i ] ,(此处i=j-1) 根据next数组的求法我们知道,也就是存在某个值P和L,使得 str[0~P]=str[L~i],也就是存在str[0~P-1]=str[L~ i-1],这个值恰好是next[i]的值,等于K-1,而根据前置条件K-1>next[i],两者矛盾,故当str [next[i]]==str[ J-1 ]时,next[ j ]=next[i]+1。(原谅我写这么多文字而不带一张图,请耐下心来)


2 next[i]的跳转:

上面已经说了,让i=J-1,如果str[ next[i] ] 与 str[ J-1 ]不匹配时,我们会进行跳转,也就是让i=next[i],然后继续比较直到求得next[J]的值。(没错,在求next[J]的值时,每次都是与str[J]比较) 这么跳转的原因还得从next数组每个值的意义说起,就是前后缀匹配的最长长度。

next [ i ]的意义是存在两个值P,K使得str[ 0~P ]和str[ K ~ i-1 ]匹配(那么也就是next[i]=P+1)。

令i=J-1,假设我们的str[ next[i] ] 与 str[ J-1 ] 不匹配,

即 str[P+1]!=str[J-1],根据next数组的定义,我们现在要找两个新的值M,N来组成前缀和后缀,使得str[ 0 ~ M ]=str[ N ~ i-1 ]匹配再比较str [ M+1 ]与str [ i ]是否相等以此来求next[J]的值,我们知道新求得的匹配长度必然是小于next[i]的(待会会证明),也就是说M<P,N>k,  因为str [ 0~P ]与str [ k ~ i-1 ]相等,因此str [ N ~ i-1 ]是str [ 0 ~ P ]的一个子串,那么我们现在其实就是在求str[ P+1 ]也就是str[ next[i] ] 的next值。所以才让i=next[i].

现在我证明,新求得的匹配长度必然是小于next[i]的(注意这里我证明的是什么,不清楚的看上面那段话中,我说的待会会证明)。

反证法:

假设新求得的长度为L,且L>=next[i]。那么存在str [ 0~L-1 ]=str [ J-L+1 ~  i-1 ],而我们next [ i ]的值的定义就是第i个字符前的最长前后缀匹配长度,如果还存在新的长度L满足这种关系的话,那么不满足我们的next数组的定义,两者矛盾,因此命题得证。


本来很想画图,但苦于没有没有合适的画图工具,导致我只能纯文字的叙述,希望能够帮到对KMP尚还不清晰的朋友,另外,我的一贯原则是talk is cheap show me the code 所以 这里还是提供一个小例子,通过kmp来实现strstr函数


#include <iostream>using namespace std;//求next数组void getNext(char* str,int* arr,int n){arr[0]=-1;if(n<=1)return;arr[1]=0;int j=2,i;while(j<n){i=j-1;while(1){if(arr[i]==-1||str[j-1]==str[arr[i]]){arr[j]=arr[i]+1;j++;break;}elsei=arr[i];}}}//统计字符串长度int strlen(char* str){int len=0;while(*str++!='\0')len++;return len;}//返回在源串中第一个与子串匹配的字符的起始地址,若没有匹配则返回NULLchar* strstr(char* src,char* match){int len1=strlen(src);int len2=strlen(match);if(len1==0||len2==0||len1<len2)return NULL;int* next=new int[len2];getNext(match,next,len2);int j=0,i=0;while(i<len1&&j<len2){if(j==-1||src[i]==match[j]){i++;j++;}elsej=next[j];}delete[] next;if (j==len2)return (src+i-j);return NULL;}int main(int arg1,char** arg2){char str1[]="today is sunny and all is right";char str2[]=" al";char* res=strstr(str1,str2);if(res!=NULL)cout<<res<<endl;elsecout<<"res is null"<<endl;getchar();}



我们知道strstr是返回源串中第一个与模式串匹配的地址。

假设我现在更改需求,需要返回所有与模式串匹配的下标呢(源串中的每个字符只能用一次,也就是说各个子串在源串中的位置不重叠)

look is cheap show me the code。

可以把下标都存在vector中,将其作为返回值,有兴趣的朋友可以写一写。



若有错误,或是建议,欢迎留言。







1 0
原创粉丝点击