KMP演算法——快速求失效函式值及其程式碼實現
-
字首和字尾的最大相等長度
為了更好的理解我接下來所說的回溯值的求法,這裡先介紹一下,如何求一個字串的字首和字尾相等的最大長度,為了便於說明記為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]; } } }
-
完整的KMP演算法
#include <iostream> #include <cstring> using namespace std; 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]; } } } int kmpCheck(char T[],char P[]){ int n=strlen(P); int next[n]; getNext(P,next); int i=0,j=0; while(T[i]&&P[j]){ if(j==-1||T[i]==P[j]){ i++; j++; }else{ j=next[j]; } } if(P[j]=='\0'){ return (i-j); }else{ return -1; } } int main(){ char T[]="absabaabcac"; char p[]="abaabcac"; int index=kmpCheck(T,p); cout<<index<<endl; }
本來想詳細講解一下KMP演算法的原理,後來發現我自己的語言表達確實有點拙劣,倒是花了很長的時間畫圖,也簡單說了一下如何手算一個kmp演算法的回溯值矩陣。至於演算法的實現就是草草的貼了幾個程式碼。能力有限,還希望不當之處大家及時和我講出來。