KMP算法的概述

一:KMP算法是一個模式匹配算法,他最原始的方法就是從主串進行一個個的進行匹配,然後返回主串中模式串的第一個字母在主串中的位置,依次進行返回就能查出有多少子串。然後對於有些模式串返回時會有一些無必要的比較,所以要用算法進行優化處理,使其算法的時間複雜度由O(n*m)簡化爲O(n+M);

對於O(n*m)這個算法只能承受10的5次方以下的題。

1:next數組。

假設有一個字符串s(下標從0開始),那麼他的第i號位作爲結尾的子串就是s【0……i】。對該子串來說,長度爲k+1的前綴與後綴分別是s【0,……k】和s【i-k……i】。現在定義一個int型數組next,其中next表示 使子串s【0……i】的前綴s【0……k】等於後綴s【i-k……i】的最大k(注意前綴和後綴可以部分重疊,但不能是s【0……i】本身);如果找不到相等的前綴和後綴,那麼令next【i】=-1.顯然,next【i】就是所求最長相等的前後綴中前綴最後一位的下標。

舉一個例子:對於模式串ababaab,next數組計算過程是:

i=0時,子串s[0……i]爲‘a’,所以找不到相等的前後綴,所以next【0】=-1;

i=1時,子串s【0……i】爲‘ab’,next【1】=-1;

i=2時,子串s【0……i】爲‘aba’,所以當k=0時,s【0,k】=‘a’,s【i-k,i】=‘a’;k=1時,s【0,k】=‘ab’,s【i-k,i】=‘ba’;所以next【2】=0;

i=3時,子串s【0……i】爲‘abab’,所以k=1時,s【0,k】=‘ab’,s【k,i】=‘ab’,所以next【3】=1;

i=4時,子串s【0……i】爲“ababa”,所以k=0時,s【0,0】=‘a’,s【4,4】=‘a’;k=1時,s【0,1】=‘ab’,s【3,4】=‘ba’,不成立;k=2時,s【0,2】=‘aba’

s【2,4】=‘aba’;k=3時,s【0,3】=‘abab’,s【1,4】=‘baba’,不成立。所以next【4】=2;

i=5時,子串s【0……i】爲‘ababaa’,所以當k=3時,s【0,3】=‘abab’,s【2,5】=‘abaa’,不成立等等,所以next【5】=0;

i=6時,子串s【0……i】爲‘ababaab’,所以當k=1時,s【0,1】=‘ab’,s【5,6】=‘ab’,所以next【6】=1;

注意:next數組中的k的值是最後一位的前綴的最後一個位置,要注意前綴加後綴可能不相等。

 對於這個例子,我們求解next數組時用暴力進行計算時是非常耗時的,下面就是用遞推的方法進行計算。

作爲舉例,假設我們求出next【0】=-1,next【1】=-1,next【2】=0,next【3】=1,現在求解 next【4】。當我們已經的到next【3】=1,最長後綴爲

‘ab’,由於s【4】=‘a’=s【next【3】+1】所以next【4】=next【3】+1,最長後綴爲‘aba’;並讓j指向next【4】;

接着在此基礎上求解next【5】,當知道next【4】=2時,由於s【5】!=s【next【4】+1】,所以不能擴展最長相等的前後綴。次失敗,要用別的方法

進行處理了。此時若找到一個j使得s【5】=s【j+1】。然後怎樣找到這個j呢,你就需要不斷的用j=next【j】進行返回只要找到就使next【5】=j+1;

具體的實現代碼:

void next(char s[],int len){
int j=-1;
next[0]=-1;
for(int i=1;i<len;i++){
while(j!=-1&&s[i]!=s[j+1]){
j=next[j];//如果不相等的話,就不斷返回j=next【j】 
}
if(s[i]==s[j+1]){//如果相等的話 ,就是next【i】=j+1; 
j++;
}
next[i]=j;//令next【i】等於j 
}
}

這段代碼每次只求一個next值,然後就是對於這個理解最難。對於每次求解next【i】時,總是用j指向next【i】,以方便求解next【i+1】之用。

還有一個從1開始的代碼;

#include<stdio.h>
#include<string.h>
int next[25];
/*
//這個next函數是用來求從1開始的next值 
void get(char t[]){
int i=1;
next[1]=0;
int j=0;
while(i<strlen(t)){
if(j==0||t[i]==t[j]){
i++;
j++;next[i]=j;
//printf("i=%d,j=%d,ne[%d]=%d\n",i,j,i,next[i]); 
}
else{
j=next[j];
//printf("nex=%d\n",j);
}
}
for(int i=1;i<strlen(t);i++){
printf("%d",next[i]);
}
}*/

//這個next函數時更簡便的求一個函數的值
void  nextnal(char t[]){
int i=1;
next[1]=0;
int j=0;
while(i<strlen(t)){
if(t[i]==t[j]||j==0){
i++,j++;
if(t[i]!=t[j]){
next[i]=j;
}
else{
next[i]=next[j];
}
}
else{
j=next[j];
}
}
for(int i = 1;i<strlen(t);i++){
printf("%d",next[i]);
}
}
int main(){
char t[20];
scanf("%s",t+1);
// get(t);
    nextnal(t);
return 0;
}

然後就是KMP算法:

//計算a中是否存在b 
bool kmp(char a[],char b[]){
int j=-1;
int n=strlen(a),m=strlen(b);
next(b,m);
for(int i=0;i<n;i++){
while(j!=-1&&a[i]!=b[j+1]){
j=next[j];
}
if(a[i]==b[j+1]){
j++;
}
if(j==m-1){
return true;
}

return false;
}

對於這個KMP算法就發現求解next數組就是求解KMP算法的實現。

求解next數組的過程其實就是模式串pattern進行自我匹配的過程。

//計算a中存在b的數量 
bool kmp(char a[],char b[]){
int j=-1,ans=0;
int n=strlen(a),m=strlen(b);
next(b,m);
for(int i=0;i<n;i++){
while(j!=-1&&a[i]!=b[j+1]){
j=next[j];
}
if(a[i]==b[j+1]){
j++;
}
if(j==m-1){
ans++;
j=next[j];
}

return ans;
}對於求解next數組時,有些特殊例子:如aaaab,用next數組求就是01234,但我們發現這有些不必要的操作。所以還有更優的方法。

對於這個函數代碼只有最後一步與前面不同。

void next(char s[],int len){
int j=-1;
next[0]=-1;
for(int i=1;i<len;i++){
while(j!=-1&&s[i]!=s[j+1]){
j=next[j];//如果不相等的話,就不斷返回j=next【j】 
}
if(s[i]==s[j+1]){//如果相等的話 ,就是next【i】=j+1; 
j++;
}
if(j==-1||s[i+1]!=s[j+1]){
next[i]=j;

else{
next[i]=next[j];
}
}
}

從代碼中可以看出,只是對next【i】=j做了補充而已。

二:從有限狀態自動機的角度看待KMP算法

事實上,有限狀態自動機可以看做一個有向圖,其中頂點表示不同的狀態,邊表示狀態之間的轉移。

發佈了38 篇原創文章 · 獲贊 10 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章