-
前缀和后缀的最大相等长度
为了更好的理解我接下来所说的回溯值的求法,这里先介绍一下,如何求一个字符串的前缀和后缀相等的最大长度,为了便于说明记为k。
注意:前缀和后缀不能为字符串本身!!!!!!
如字符串“abcab”的k为2、字符串“a”的k为0、“aaaaa”的k为4、“abcaabc”的k为3。
-
回溯值
在主串T中寻找模式串P的过程称为模式匹配。若匹配成功,则返回P在T中的位置,匹配失败,返回0;常用的算法有两种,一个是朴素的模式匹配算法,这里就不做过多介绍。另一个就是看书看到头晕的KMP算法~~
这里首先来大概讲一讲KMP算法思想的关键在于那里,先通过图来看一下,KMP算法匹配的过程
上图:i=0,j=0;i=1,j=1;i=2,j=2时字符都是匹配的,当i取3,j取3的时候,字符不匹配,这时候,如果按朴素的模式匹配算法来求,这时候需要将i退回i=1处,j退回j=0处。但我们观察发现,j之前的字符串和主串中一部分已经完全匹配了,而“aba”的k(上一个标签中出现)为1,这时i不动,让j移到k处,即j=1;“aba”的前缀a已经和后缀a相等,而后缀a已经和主串中的a匹配了,所以这个时候已经不需要再匹配a。如下图:
以这样一个小小的例子我们可以发现,关键就是求得当p[j]与T[i]不匹配时,j应该退回到模式串的什么位置。模式串中每一个位置都有它对应的回溯值。这样一个和模式串下标有关的数组记为next[]。
手算快速求出next【】呢???
如“abaabcac”,默认当j=0时,next[0]=-1。
当j=1是,拿出他之前的子串,即“a”,k值为0,所以next[1]=0;
当j=2时,拿出他之前的子串,即“ab”,k为0,所以next[2]=0;
当j=3时,拿出他之前的子串,即“aba”,k为1,所以next[3]=1;
当j=4时,拿出他之前的子串,即“abaa”,k为1,所以next[4]=1;
当j=5时,拿出他之前子串,即“abaab”,k为2,所以next[5]=2;
当j=6时,拿出他之前子串,即“abaabc”,k为0,所以next[6]=0;
当j=7时,拿出他之前的子串,即“abaabca”,k为1,所以next[7]=1;
所以模式串P的失效值的数组为[-1,0,0,1,1,2,0,1];
-
失效函数值的两种实现
-
求模式失效函数值的递归算法
int getNext(int j,char p[]){
if(j==0){
return -1;
}
if(j>0){
int k=getNext(j-1,p);
while(k>=0){
if(p[k]==p[j-1]){
return k+1;
}else{
k=getNext(k,p);
}
}
return 0;
}
return 0;
}
求模式失效函数值的递推算法
void getNext(char p[],int next[]){
int j=0;
int k=-1;
next[0]=-1;
while(p[j]){
if(k==-1||p[k]==p[j]){
j++;
k++;
next[j]=k;
}else{
k=next[k];
}
}
}
-
优化的递推算法
void getNext(char p[],int nexval[]){ int j=0,k=-1; nexval[j]=k; while(p[j]){ if(k==-1||p[j]==p[k]){ j++; k++; if(p[j]==p[k]){ nexval[j]=nexval[k]; }else{ nexval[j]=k; } }else{ k=nexval[k]; } } }
-
完整的KMP算法
#include <iostream> #include <cstring> using namespace std; int getNext(int j,char P[]){ if(j==0){ return -1; } while(P[j]){ int k=getNext(j-1,P); while(k>=0){ if(P[j-1]==P[k]){ return k+1; }else{ k=getNext(k,P); } } return 0; } return 0; } void getNext(char P[],int next[]){ int j=0,k=-1; next[0]=-1; while(P[j]!='\0'){ if(k==-1||P[j]==P[k]){ j++; k++; next[j]=k; }else{ k=next[k]; } } return; } void getNextval(char P[],int nextval[]){ int j=0,k=-1; while(P[j]!='\0'){ if(k==-1||P[j]==P[k]){ k++; j++; if(P[j]==P[k]){ nextval[j]=nextval[k]; }else{ nextval[j]=k; } }else{ k=nextval[k]; } } } int kmpCheck(char T[],char P[],int index[]){ int n=strlen(P); int next[n]; int k=0; getNext(P,next); int i=0,j=0; while(j==-1||T[i]&&P[j]){ if(j==-1||T[i]==P[j]){ i++; j++; }else{ j=next[j]; } if(j!=-1&&P[j]=='\0'){ index[k++]=i-j; j=0; } } return k; } int main(){ char t[]="abceeeabcyyyabc"; char p[]="abc"; int index[10]; int n=kmpCheck(t,p,index); for(int i=0;i<n;i++){ cout<<index[i]<<'\t'; } cout<<endl; return 0; }
本来想详细讲解一下KMP算法的原理,后来发现我自己的语言表达确实有点拙劣,倒是花了很长的时间画图,也简单说了一下如何手算一个kmp算法的回溯值矩阵。至于算法的实现就是草草的贴了几个代码。能力有限,还希望不当之处大家及时和我讲出来。