KMP演算法淺顯理解
說明:轉載
KMP演算法看懂了覺得特別簡單,思路很簡單,看不懂之前,查各種資料,看的稀裡糊塗,即使網上最簡單的解釋,依然看的稀裡糊塗。
我花了半天時間,爭取用最短的篇幅大致搞明白這玩意到底是啥。
這裡不扯概念,只講演算法過程和程式碼理解:
KMP演算法求解什麼型別問題字串匹配。給你兩個字串,尋找其中一個字串是否包含另一個字串,如果包含,返回包含的起始位置。
如下面兩個字串:char *str = "bacbababadababacambabacaddababacasdsd";
char *ptr = "ababaca";str有兩處包含ptr
分別在str的下標10,26處包含ptr。“bacbababadababacambabacaddababacasdsd”;\
問題型別很簡單,下面直接介紹演算法
演算法說明一般匹配字串時,我們從目標字串str(假設長度為n)的第一個下標選取和ptr長度(長度為m)一樣的子字串進行比較,如果一樣,就返回開始處的下標值,不一樣,選取str下一個下標,同樣選取長度為n的字串進行比較,直到str的末尾(實際比較時,下標移動到n-m)。這樣的時間複雜度是O(n*m)。
KMP演算法:可以實現複雜度為O(m+n)
為何簡化了時間複雜度:
充分利用了目標字串ptr的性質(比如裡面部分字串的重複性,即使不存在重複欄位,在比較時,實現最大的移動量)。
上面理不理解無所謂,我說的其實也沒有深刻剖析裡面的內部原因。考察目標字串ptr:
ababaca
這裡我們要計算一個長度為m的轉移函式next。next陣列的含義就是一個固定字串的最長字首和最長字尾相同的長度。
比如:abcjkdabc,那麼這個陣列的最長字首和最長字尾相同必然是abc。
cbcbc,最長字首和最長字尾相同是cbc。
abcbc,最長字首和最長字尾相同是不存在的。**注意最長字首:是說以第一個字元開始,但是不包含最後一個字元。
比如aaaa相同的最長字首和最長字尾是aaa。**
對於目標字串ptr,ababaca,長度是7,所以next[0],next[1],next[2],next[3],next[4],next[5],next[6]分別計算的是
a,ab,aba,abab,ababa,ababac,ababaca的相同的最長字首和最長字尾的長度。由於a,ab,aba,abab,ababa,ababac,ababaca的相同的最長字首和最長字尾是“”,“”,“a”,“ab”,“aba”,“”,“a”,所以next陣列的值是[-1,-1,0,1,2,-1,0],這裡-1表示不存在,0表示存在長度為1,2表示存在長度為3。這是為了和程式碼相對應。下圖中的1,2,3,4是一樣的。1-2之間的和3-4之間的也是一樣的,我們發現A和B不一樣;之前的演算法是我把下面的字串往前移動一個距離,重新從頭開始比較,那必然存在很多重複的比較。現在的做法是,我把下面的字串往前移動,使3和2對其,直接比較C和A是否一樣。
程式碼解析:
void cal_next(char *str, int *next, int len) { next[0] = -1;//next[0]初始化為-1,-1表示不存在相同的最大字首和最大字尾 int k = -1;//k初始化為-1 for (int q = 1; q <= len-1; q++) { while (k > -1 && str[k + 1] != str[q])//如果下一個不同,那麼k就變成next[k],注意next[k]是小於k的,無論k取任何值。 { k = next[k];//往前回溯 } if (str[k + 1] == str[q])//如果相同,k++ { k = k + 1; } next[q] = k;//這個是把算的k的值(就是相同的最大字首和最大字尾長)賦給next[q] } }
KMP 這個和next很像,具體就看程式碼,其實上面已經大概說完了整個匹配過程。 int KMP(char *str, int slen, char *ptr, int plen) { int *next = new int[plen]; cal_next(ptr, next, plen);//計算next陣列 int k = -1; for (int i = 0; i < slen; i++) { while (k >-1&& ptr[k + 1] != str[i])//ptr和str不匹配,且k>-1(表示ptr和str有部分匹配) k = next[k];//往前回溯 if (ptr[k + 1] == str[i]) k = k + 1; if (k == plen-1)//說明k移動到ptr的最末端 { //cout << "在位置" << i-plen+1<< endl; //k = -1;//重新初始化,尋找下一個 //i = i - plen + 1;//i定位到該位置,外層for迴圈i++可以繼續找下一個(這裡預設存在兩個匹配字串可以部分重疊),感謝評論中同學指出錯誤。 return i-plen+1;//返回相應的位置 } } return -1; }
KMP演算法程式碼:
#include "iostream" #include "cstring" using namespace std; void cal_next(char *str,int *next,int len) { next[0]=-1; int k=-1; for(int i=1;i<=len-1;i++) { while(k>-1&&str[i]!=str[k+1]) k=next[k]; if(str[k+1]==str[i]); k++; next[i]=k; } } int KMP(char *str,int slen,char *ptr,int plen) { int *next=new int [plen]; cal_next(ptr,next,plen); int k=-1; for(int i=0;i<=slen-1;i++) { while(k>-1&&ptr[k+1]!=str[i]) k=next[k]; if(ptr[k+1]==str[i]) k++; if(k==plen-1) { return i-plen+1; } } return -1; } int main() { char str[1000],ptr[1000]; cin>>str; cin>>ptr; int a=KMP(str,strlen(str),ptr,strlen(ptr)); if(a==-1) { cout<<"找不到"<<endl; } else { cout<<a<<endl; } return 0; }
無關筆記:
class Calor { public: Calor(double a,double b) { weight=a; worth=b; } void Getin(const Calor &a); void Getout(const Calor &a); void Objshow(); static void init(); static void Allshow() ;//靜態函式不能含有cv限定符 const voletile static void remind(); private: double weight; double worth; static double Aweight;//static不可以重複使用 static double Aworth; }; double Calor::Aweight=1000; double Calor::Aworth=1000;//再類裡的靜態變數,定義如左邊 void Calor::Allshow() { // int a=5;靜態函式裡面使用變數和靜態變數都沒關係 // static int b=2; // int c=a+b; cout<<"*******************"<<endl; cout<<"所剩餘貨物重量為:"<<Aweight<<"千克"<<endl; cout<<"所剩餘貨物價值為:"<<Aworth<<"元"<<endl; cout<<"*******************"<<endl; } class A { public: A(int x,int y):a(x)//靜態變數不能成員初始化法 {//const函式不可以與靜態函式一起使用,只能是成員函式,而且函式內部只能實現輸入輸出 cout<<a<<' '<<b; } private: int a; static int b; }; int A::b=1;