字串匹配演算法KMP詳細解釋 深入理解

2021-07-25 00:20:58 字數 3789 閱讀 9881

字串匹配是乙個經典演算法問題,展開來講各類問題多達幾十種,有名稱的演算法也不下三十種,所以需要深入學習的東西有很多。這次我們來**乙個最簡單的問題,假設現在隨機輸入乙個長度為m的主串t,另外輸入乙個長度為n(n≤m)的字串p,我們來判斷字串p是否是主串t的乙個子串(即能否從t中隨機取出與p同長的一段字串,與p完全匹配)。

問題很簡單,當然也有最直接、最直觀也是最好想到的方法,蠻力串匹配。即兩個字串像物流傳送帶一般,主串固定,子串一步步像前移動,一位位匹配比較,直到完全匹配找到想要的結果的位置。效果即如下圖所示,將t長度為m的m - n+1個子串逐一和p進行比對,發現完全每一位匹配的位置即我們需要的結果。圖中p字串上黑色表示該位已成功匹配,而綠色表示當前匹配未成功的位置,白色表示未匹配字元位置。

蠻力解法的**如下:

#include

#include

int stringmatch(char* t,char* p,int nlent, int nlenp)

else

//如果不匹配則i位置+1,j從頭開始

}return -1;

}int main()

return

0;}

上面程式很好理解,就是從位置0開始,先固定當前主串的位置i,然後一一比較主串下面m個字元是否和p完全匹配,匹配則輸出主串中位置i。一旦這個m位比較過程中,如果發生當前字元不匹配的情況,則主串i的位置相比較這次匹配開始位置增加1,準備進行下一次比較,而j自然復位到字串p的首字元地方。一般情況下,我們知道m遠小於n的,這樣蠻力匹配演算法的總體時間複雜度為o(n×m)。當然,蠻力匹配演算法的寫法有很多,也不一定就是上面的形式,但是只要不發生質的改變,所有的蠻力匹配演算法寫法時間複雜度是不會發生改觀的。

但是,蠻力演算法明顯時間複雜度過高,不適合規模稍微大一些的應用環境,因此就需要改進。這裡我們觀察不難發現,蠻力演算法之所以需要大量的時間,是因為存在大量的區域性匹配,而且每次匹配一旦失配,主串和模式串的字元指標都需要回退,並從頭開始下一輪的嘗試。實際上,我們在整個過程中重複了很多操作,因為在完全成功匹配之前,我們曾經很大可能匹配成功過很多次部分字元。只要充分利用這些資訊,就可以不需要讓主串完全回退到上次開始比較的下乙個字元,模式串一樣的道理,這樣就可以大大提高匹配演算法的效率。下面我們就來看看關於這個問題改進的演算法kmp的原理。

kmp演算法是根據三位發明者 knuth、morris 和 pratt 名字的首字母命名的。在介紹之前,我們詳細看看下面這張圖:

當第一輪對比進行到最後一對字元的時候,由於』a』和』b』發生失配,如果是蠻力演算法將會讓這兩個字元指標回退(即主串i = i-j+1,和模式串j=0),然後又從頭一一對比。然而事實上,指標i完全不必要回退,通過第一輪比對我們清楚的知道,主串t的子串substr(t,i-j,j)的第三位和第四位其實和模式串p前兩位是完全匹配的,並且模式串p第一位也並不等於主串t第二位,所以可以直接將模式串p直接移到主串第三位對齊,並且前兩位不需要比較,直接從第三位開始繼續比較即可。

那麼一般性的問題就來了,模式串p在任何上次失配情況下,應該右移幾個單元,並且從第幾位開始比較呢?這就是kmp中next表應該完成的工作了。

一開始我就要強調一下,next表都是基於模式串,即子串來建立的。next表決定了當兩個字串乙個個字元匹配的時候出現失配,應該回退到哪,即失配回退是根據失配那一位的next值所決定。回退的規則簡單來說也就是一句話:返回失配位之前最長公共前字尾對應的字首後一位的地方。怎麼理解這句話呢?先看下圖:

上面的話需要分為「最長公共前字尾」和「字首後一位」兩部分來理解。當p和t匹配遇到」c」發生失配,那麼現在p應該從「c」回退到第幾個字元,完全由「c」前面子字串「beabe」所決定。我們看到這個字串是關於「a」前後對稱,也就是說最長的公共前字尾就是「be」,那麼下一次比較的開端就是字首「be」後一位「a」。下一次匹配移動如下:

公共前字尾的理解如下圖所示:

紅色部分就是公共的前字尾了!

next表的每一位反映該位失配後回退的地方,它是由前一位字元在整個前子字串中最長公共前字尾的長度值所決定(說的有些繞口。。),我們還是直接看下面的總結:

①、第一位字元的next值設定為-1,因為當第一位就開始失配,直接將模式串下移一位即可,無需多說。同樣道理,第二位也一樣,其前子字串僅乙個字元,所以next值即為0。

②、後面的,當某位前一位字元的前乙個字元對稱程度為0的時候,只要將該位前一位字元與子串第乙個字元進行比較即可。例如abcdae,因為「d」字元與前面無對稱項,所以只需要比較a和開頭字元比較即可。

③、以此推理,如果某位前一位字元的next值是1,即該位前一位字元的前乙個字元與開頭字元相等,那麼我們就把該位前一位字元與子串第二個字元進行比較,如果也相等,說明對稱程度就是2了,即該位的next值為2。

④、當然如果一直相等,就一直一位位累加繼承。但是絕大多數不可能會如此順利對稱下去,如果遇到下乙個不相等了,那麼說明不能繼承前面的對稱性了。這種情況只能說明沒有那麼多對稱了,但是不能說明一點對稱性都沒有,所以遇到這種情況就要重新來考慮,這個也是難點所在。

④、一旦發生不能累加繼承,則需要在對稱的前字尾字串中繼續尋找子對稱。如下圖所示:

「abadabab… …」中「b」不能繼續繼承前面的對稱序「aba」,所以下一步做的在對稱序中繼續找次對稱序,最後發現子對稱「ab」。如果未能成功尋找到則b後一位的next值為0。

#include 

#include

#include

#define n 100

void cal_next( char * str, int * next, int len )

}

int kmp( char * str, int slen, char * ptr, int plen, int * next )

else

} return ( p_i == plen ) ? ( s_i - plen ) : -1;

}

int main()

; char ptr[ n ] = ;

int slen, plen;

int next[ n ];

while( scanf( "%s%s", str, ptr ) )

return

0;

}

上面kmp程式摘自下面篇博文:

本文內容主要參考自:

博文:

鄧俊輝 《資料結構(c++)》

個人學習記錄,由於能力和時間有限,如果有錯誤望讀者糾正,謝謝!

字串匹配演算法KMP詳細解釋 深入理解

字串匹配是乙個經典演算法問題,展開來講各類問題多達幾十種,有名稱的演算法也不下三十種,所以需要深入學習的東西有很多。這次我們來 乙個最簡單的問題,假設現在隨機輸入乙個長度為m的主串t,另外輸入乙個長度為n n m 的字串p,我們來判斷字串p是否是主串t的乙個子串 即能否從t中隨機取出與p同長的一段字...

字串匹配演算法 KMP演算法簡單解釋

kmp演算法是一種改進的字串匹配演算法,由d.e.knuth,j.h.morris和v.r.pratt同時發現,因此人們稱它為克努特 莫里斯 普拉特操作 簡稱kmp演算法 kmp演算法的關鍵是利用匹配失敗後的資訊,儘量減少模式串與主串的匹配次數以達到快速匹配的目的。具體實現就是實現乙個next 函式...

KMP演算法 字串匹配

kmp演算法基本思想 我們在用常規的思想做 字串匹配時候是 如 對如 字元如果 t abab 用p ba 去匹配,常規思路是 看 t 第乙個元素 a 是否 和p 的乙個 b 匹配 匹配的話 檢視各自的第二個元素,不匹配 則將 t 串的 第二個元素開始 和 p 的第乙個匹配,如此 一步一步 的後移 來...