算法介紹
字符串匹配是編程常遇到的一個問題,最樸素簡單粗暴的匹配方法需要
KMP算法是一種改進的快速的字符串匹配算法,是由D.E.Knuth與V.R.Pratt和J.H.Morris同時發現,算法的時間複雜度只需要O(n)。
算法思想其實很簡單,但是有時候會被人們解釋的很複雜,因此我想根據我的經驗來簡單的總結一下KMP算法以便我自己更好地理解。
字符串匹配過程
我們的目標是
- 判斷能否在主串S中找到一段和模式串P匹配的子串
現在簡單看一下傳統的字符串匹配的過程
由於繪圖工具不給力暫時就提供這樣的圖,上面是主串S,下面是模式串P
分別從S和P的第一位開始匹配那麼我們看到兩個串迅速匹配了三個字符。到第四個字符不匹配的時候,需要將模式串P移位,先看樸素簡單粗暴的移位方式
也就是移動了一位從S的第二位和P的第一位重新開始匹配,這也符合我們最直觀的想法,不過結果很顯然A和B不匹對..需要繼續講P串後移
那麼對於KMP算法來說,直觀的高效體現就是不匹對的時候移位會更多一些,對於以上情況移位如下
模式串P會自動的跳過B和C直接與S串的第四位A開始匹配,這其實是模式串P對自身的字符內容有着足夠的瞭解,它知道當不匹配的時候利用之前匹配信息來決定如何進行下一步匹配。
KMP算法
KMP算法的關鍵是利用匹配失敗後的信息,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。 —— [ 百度百科 ]
那麼如何利用之前匹配失敗的信息呢?
首先就是求字符串子串中既是前綴串同時又是後綴串中最長的那個。這句話很繞口,但是理解了就很簡單,比如說對於字符串ABABA,A是該串的前綴串同時又是後綴串,但是AB只是前綴串不是後綴串,因爲長度爲2的後綴串蔚BA,那麼同時BA就只是後綴串不是前綴串,那麼對於字符串ABABA的最長的既是前綴串又是後綴串的子串爲 ABA。
那爲什麼利用這個信息能達到快速匹配的效果呢?看一下如下圖:
此時ABAB子串被匹配,但是第五個字符不匹配需要移動,那麼該移動到哪裏呢,ABAB中最長既是前綴串又是後綴串的子串爲AB,那麼我保證移動兩位後字符串A和模式串P前兩位還是能夠匹配的。
若是隻移動一位字符串A和模式串P還要能匹配,那麼ABAB的最長既是前綴串又是後綴串的子串長度起碼要3!
我們爲了用上之前匹配過的信息,那麼我們就希望移動模式串P之後利用之前的信息儘可能讓之前的字符串更多的匹配,也就是減少不必要的匹配過程。(這句話能懂嗎)
求字符串子串中既是前綴串同時又是後綴串中最長的那個就是求解next[]數組。
代碼
以下是求解KMP中next數組的過程:
string par
int par_size=par.size();
vector<int> next(par_size,0);
next[0]=-1;
int k=-1;
for(int i=1;i<par_size;i++)
{
while(k>-1 && par[k+1]!=par[i])//不好理解?自己寫個例子就明白了
k=next[k];
if(par[k+1] ==par[i])
k++;
next[i]=k;
}
求解完next數組我們就可以利用該數組的信息進行快速的字符串匹配,
int result=0;//匹配成功的次數
int m=org.size();
int q=-1;
for(int i=0;i<m;i++)
{
while(q>-1 && par[q+1] != org[i])
q= next[q];
if(par[q+1] == org[i])
q++;
if(q == par_size-1)//匹配成功
{
result++;
q=next[q];
}
}
其中q的最多增長m-1次,而造成q值降低的原因都在while循環中,因此用聚合思想分析while循環最多執行m-1次,因此整個算法用時複雜度還是O(m)。