KMP演算法入門講解

2022-05-02 01:30:08 字數 1515 閱讀 4338

字串匹配問題。假設文字是乙個長度為$n$的字串$t$,模板是乙個長度為$m$的字串$p$,且$m\leq n$。需要求出模板在文字中的所有匹配點$i$,即滿足$t[i]=p[0],t[i+1]=p[1],...,t[m-1]=p[m-1]$的非負整數$i$(注意字串下標從0開始)。如圖所示,$p$在$t$中有且只有乙個匹配點,即位置3。

最樸素的方法是依次判斷每個位置$s$是不是乙個匹配點。檢查匹配點需要$o(m)$時間,而可能的匹配點有$o(n-m)$個,所以最壞情況時間複雜度為$o(nm)$。有乙個簡單的優化:在檢查匹配點的合法性是只要有乙個字元不同,立刻停止比較,換下乙個匹配點。但最壞情況下時間複雜度沒變。

和樸素演算法相比,kmp演算法的時間效率就強多了。它首先用$o(m)$的時間對模板進行預處理,然後用$o(n)$的時間完成匹配。從漸進意義上來說,這樣的時間複雜度已經是最好的了(至少需要$o(m+n)$時間,因為至少需要檢查文字串和模板的每個字元)。

雖然**很短,但kmp的細節並不容易理解。考慮到網上已經有很多介紹kmp的資料,這裡只對它進行簡單介紹,作為學習aho-corasick自動機的鋪墊。

kmp演算法的精髓蘊含在下圖中。

假設在匹配過程中正在比較文字字串*位置的字元和模板字串abbaaba的最後乙個字元,發現兩者不同(稱為失配),這時,樸素演算法只會把模式串右移一位,重新比較abbaaba的第乙個字元和文字串!!位置的字元。

kmp演算法認為,既然!!位置已經比較過一次了,就不應該再比一次。事實上,我們已經知道灰色部分就是abbaab,應該可以直接利用模板串本身的特性判斷出右移一位一定不是匹配的。同理,右移兩位或者三位也不行,但是右移四位是有可能的。這個時候,需要比較*處的字元和abbaaba的第三個字元。

上圖那條鏈是乙個狀態自動機,其中編號為$i$的結點表示已經匹配了$i$個字元。匹配開始時當前狀態是0,成功匹配時狀態加1(表示多匹配了乙個字元),而失配時沿著「失配邊」走。比如在這個例子中,如果在狀態6時失配,應該轉移到狀態2.為了方便起見,這裡用失配函式(failure function)$f[i]$表示狀態$i$失配時應轉移到的新狀態,要特別注意的是$f[0]=0$。

有了失配函式後,kmp演算法不難寫出,**如下:

void find(char* t, char* p, int *f)

}

這個**的時間複雜度如何?答案可能並不明顯。失配的時候也許會反覆向左走很多次,會不會太慢?不會。可以這樣計算時間複雜度。每次$j$的時候伴隨乙個$i$,而每次$j=f[j]$的時候$j$至少會減1。最壞情況下$j$增加了$n$次,因此$j=f[j]$的次數不會超過$n$,因此總時間複雜度為$o(n)$。

狀態轉移圖的構造是kmp演算法的關鍵,也是它最巧妙的地方。演算法的思想是「用自己匹配自己」,根據$f[0],f[1],...,f[i-1]$遞推$f[i]$,**和匹配部分非常相似,如下所示。

void getfail(char* p, int*f)

}

kmp演算法講解

kmp演算法本身,解決的是判斷模板字串t,是否是字串s的子串的問題。當只需判斷子串首次出現的位置,或是否包含子串。可以用庫函式strstr s,t 代替,判斷t是否為s的子串來代替。該函式的返回值是t首次出現的位置,如果t不是s的子串則返回null。該函式的複雜度與kmp類似。kmp演算法主要分為兩...

KMP演算法講解

第一次寫部落格有點小激動,今天要講一下kmp演算法,這個查詢演算法比較難理解,我也是想了很久才想明白,我們在str2中的pos位置開始查詢str1,當找到的時候返回str2中的下標,沒有找到的時候返回 1 首先我們來參考一下我們最初寫的查詢字串的函式,定義i 0為str2的下標,j 0為str1的下...

擴充套件kmp演算法講解

參考文章 一.擴充套件kmp得到的是什麼 字串a,b。b是模式串 子串 求解a i lena 1 與b的最長公共字首,記錄在陣列ex i 中 二.演算法實現 分為兩步,第一步是對b模式串進行處理也是求b i,blen 1 與b的最長公共字首,用next陣列儲存,第二部是將a與b進行匹配,其實他們的方...