1. 程式人生 > 實用技巧 >kmp演算法/子串匹配

kmp演算法/子串匹配

17.kmp演算法/子串匹配

a.整體框架

講解

講解連結

首先宣告所有陣列第一個下標從0開始。(有些教程會選擇從1開始,先說清楚,以免搞混了)。

這裡我們使用一個例子來簡單說明一下

例如

如果我們用暴力尋找的話,那麼過程是

總共比對了 4 + 1 + 1 + 1 + 7 + 1 +1 + 1 + 3 + 1 + 1 + 8 +1 +1 +1 +8 = 41 (次)

如果改進成KMP的話,那麼運算過程就是(灰色地方是KMP演算法不去考慮的地方,紅點是每次比較的字串位置)

總共比對了 4 + 1 + 7 + 1 + 1 +8 + 4 = 26 (次)

如果我們看紅點在abcxabcdabxabcdabcdabcy的位置,我們就會發現紅點一直在向前移動,不會往後退/回頭。這就是KMP演算法的優點即不會倒退(也有人稱作回溯),所以就能避免不必要的匹配檢查。

讓我們依次看看KMP演算法在上面的例子中都做了些什麼。

首先讓我們看一下第一個例子。

綠框中是KMP演算法跳過的地方,那麼我們就來對比一下兩個紅框裡面的內容。

這裡我們可以看到,深綠色方框那裡是不相同字元的位置與新一輪判定的開始位置。

深橘黃色方框裡的是已經匹配成功的字串 abc。

關鍵的地方來了,因為已經匹配成功的字串abc中沒有相同的前後綴,所以下一次比對要從abcdabcy的首位開始比較。

我們簡單地來看一下abc的前後綴情況(ac寫錯了,是bc)

要注意,這裡我們看的前後綴的長度要小於已匹配到的字串長度,因為如果長度一樣了那就不用分前後綴了,也沒有比較的意義了。

因為沒有相同的前後綴,我們就不用擔心錯過什麼,直接從配對失敗的地方開始新的匹配就行了。

這個很好理解,讓我們假設一下如果在上面這個例子中間有這麼一種情況

在這裡如果符匹配條件我們至少需要滿足方框內的字元相同。

讓我們看看方塊內的字元處於abc中的什麼位置

在黃色框內,bc屬於abc的字尾,ab屬於abc的字首,所以如果條件符合的話,abc需要有相同的前後綴。

不理解為什麼的同學不用擔心,現在只要記住我們在尋找相同前後綴就行了,一會看完應該就能想通了。

讓我們看看下一個例子。

在這個例子中KMP演算法跳過了綠色方框的部分,直接運行了紅色方框裡的內容。讓我們看看紅色方框裡發生了什麼。

深綠色方框位置是不相同字元位置與新一輪比較的位置。

深橘黃色方框裡的是已經匹配的字串 abcdab。

讓我們來找一下 abcdab的前後綴吧。

我們發現abcdab有相同的前後綴。

重點又來了,如果有相同的前後綴,我們就需要把字首移動到字尾的位置上。

這樣abcdabcy就向右移動了四位,然後開始比較abcdabcy[相同前後綴長度] 上的字元,即第3個字元c(預設索引從0開始)。

不明白沒有關係,我們再看兩個例子。

在上面這個例子中,綠色依然是被忽略的部分,紅色方框是KMP演算法執行的部分。

讓我們繼續關注紅色方框裡的內容

深綠色的地方是匹配到不一樣字元的位置,也是下一次比較的開始位置。

深橘色的地方是已經成功匹配的字串ab。

由於ab沒有相同的前後綴,所以下一次比較從abcdabcy[0] 開始。

最後我們看看這個例子

依舊只看紅色方框部分

深綠色的地方是匹配到不一樣字元的位置,也是下一次比較的開始位置。

深橘色的地方是已經成功匹配的字串abcdabc。

讓我們來看看abcdabc的前後綴吧。

我們發現abcdabc有相同的前後綴abc,我們就需要把字首移動到字尾的位置上。

這樣abcdabcy就向右移動了四位,然後開始比較abcdabcy[相同前後綴長度] 上的字元,即第3個字元d(預設索引從0開始)。

最後我們比對發現找到了目標字串。

通過上面的例子,我們發現每當我們匹配失敗,就需要尋找匹配成功的字串中有沒有相同的前後綴(最長的前後綴),然後再判定下一次比較要從哪一位開始。

邏輯實現:
還是回到上面的例子,如果每次匹配失敗都去判定一次是否有相同前後綴的話,那麼就太麻煩了,所以我們可以在匹配前就把各種情況的前後綴找出來。

上面是我們能列舉出來的所有情況,KMP演算法需要的關鍵資訊就是最左邊的匹配數與最右邊的前/字尾長度。

因為8匹配就匹配完成,所以我們其實只需要考慮0~7匹配的情況,總共8種情況。

我們可以用一組陣列來儲存此資料,我們命名此陣列為next陣列。

int[] next = new int[] { 0,0,0,0,0,1,2,3};

所以每當我們匹配失敗的時候,我們就可以通過next陣列來快速定位下一個需要對比的索引位置。

這樣我們的KMP演算法可以理解為

KMP(string target, string txt){
1)計算next陣列
2)通過迴圈來對比target與txt字串
}

結論與演算法

總之,在不匹配的時候主串不會前移,要麼模式串前移到前後綴長度的位置,要麼主串後移一位。

public void kmp(String haystack, String needle){
        int[] next = getNext(needle);
        int hi = 0;  // 主串的索引
        int ni = 0;  // 模式串的索引
        while (hi < haystack.length()){
            if (haystack.charAt(hi) == needle.charAt(ni)){
                hi++;
                ni++;  // 相等,各進一步
            }else if(ni > 0){
                ni = next[ni - 1];  // 失配,若模式串索引大於0,則根據next陣列移動模式串
            }else {
                hi++;  // 失配,若模式串索引等於0,則將主串後移一位
            }
            if (ni == needle.length()) {
                System.out.println(hi - ni);
                ni = next[ni - 1];  // 多個匹配位置
            }
        }
    }