KMP算法解決字符串匹配問題


作者:Grey

原文地址: KMP算法解決字符串匹配問題

要解決的問題

假設字符串str長度為N,字符串match長度為M,M <= N, 想確定str中是否有某個子串是等於match的。返回和match匹配的字符串的首字母在str的位置,如果不匹配,則返回-1

OJ可參考:LeetCode 28. 實現 strStr()

暴力方法

從str串中每個位置開始匹配match串,時間復雜度O(M*N)

KMP算法

KMP算法可以用O(N)時間復雜度解決上述問題。

流程

我們規定數組中每個位置的一個指標,這個指標定義為

這個位置之前的字符前綴和后綴的匹配長度,不要取得整體。

例如: ababk 這個字符串,k位置的指標為2, 因為k之前位置的字符串為abab

前綴ab 等於 后綴ab,長度為2,下標為3的b的指標為1,因為b之前的字符串aba ,前綴a 等於后綴a, 長度為1。

人為規定:0位置的指標是-1,1位置的指標0

假設match串中每個位置我們都已經求得了這個指標值,放在了一個next數組中,這個數組有助於我們加速整個匹配過程。

我們假設在某個時刻,匹配的到的字符如下

image

其中str的i..j一直可以匹配上match串的0...m, str中的x位置和match串中的y位置第一次匹配不上。如果使用暴力方法,此時我們需要從str的i+1位置重新開始匹配match串的k位置,而KMP算法,利用next數組,可以加速這一匹配過程,具體流程是,依據上例,我們可以得到y位置的next數組信息,假設ynext數組信息是2,如下圖

image

如果ynext數組信息是2,那么0...k 這一段完全等於f...m這一段,那么對於match來說,當y位置匹配不上x位置以后, 可以直接讓x位置匹配ynext數組位置p上的值,如下圖

image

如果匹配上了,則x來到下一個位置,p來到下一個位置繼續匹配,如果再次匹配不上,假設p位置的next數組值為0, 則繼續用x匹配pnext數組位置0位置上的值,如下圖

image

如果x位置的值依舊不等於0位置的值,則宣告本次匹配失敗,str串來到x下一個位置,match串從0位置開始繼續匹配。

next數組求解

next數組的求解是KMP算法中最關鍵的一步,要快速求解next數組,需要做到當我們求i位置的next信息時,能通過i-1next數組信息加速求得,如下圖

image

當我們求i位置的next信息時,假設j位置的next信息為6,則表示

image

m...n這一段字符串等於s...t這一段字符,此時可以得出一個結論,如果:

x位置上的字符等於j位置上的字符,那么i位置上的next信息為j位置上的next信息加1,即為7。如果不等,則繼續看x位置上的next信息,假設為2,則有:

image

此時,判斷q位置的值是否等於j位置的值,如果相等,那么i位置上的next信息為x位置上的next信息加1,即為3,如果不等,則繼續看q位置上的next信息,假設為1,那么有

image

此時,判斷p位置的值是否等於j位置的值,如果相等,那么i位置上的next信息為q位置上的next信息加1,即為2,如果不等,則繼續如上邏輯,如果都沒有匹配上j位置的值,則i位置的next信息為0。

主流程代碼復雜度估計

public class LeetCode_0028_ImplementStrStr {
    public static int strStr(String str, String match) {
        if (str == null || match == null || match.length() > str.length()) {
            return -1;
        }
        if (match.length() < 1) {
            return 0;
        }
        char[] s = str.toCharArray();
        char[] m = match.toCharArray();
        int l = m.length;
        int[] next = getNextArr(m, l);
        int x = 0;
        int y = 0;
        while (y < s.length && x < l) {
            if (s[y] == m[x]) {
                y++;
                x++;
            } else if (x != 0) {
                x = next[x];
            } else {
                y++;
            }
        }
        return x == l ? y - x : -1;
    }

    // 求解next數組邏輯
    private static int[] getNextArr(char[] str, int l) {
        if (l == 1) {
            return new int[]{-1};
        }
        int[] next = new int[l];
        next[0] = -1;
        next[1] = 0;
        int i = 2; // 目前在哪個位置上求next數組值
        int cn = 0; // 前后綴最長字符的長度,也表示下一個要比的信息位置
        while (i < next.length) {
            if (str[i - 1] == str[cn]) {
                next[i++] = ++cn;
            } else if (cn > 0) {
                cn = next[cn];
            } else {
                next[i++] = 0;
            }
        }
        return next;
    }
}

next數組的求解流程時間復雜度顯然為O(N),現在估計主流程的復雜度,主流程中,x能取得的最大值為str字符串的長度N,定義一個變量x-y,能取得的最大值不可能超過N(即當x = N,y=0時候),在主流程的wile循環中,有三個分支

        while (y < s.length && x < l) {
            if (s[y] == m[x]) {
                y++;
                x++;
            } else if (x != 0) {
                x = next[x];
            } else {
                y++;
            }
        }

我們考慮這三個分支對於yy - x變化范圍的影響

分支 y y - x
x++; y++ 推高 不變
x = next[x] 不變 推高
y++ 推高 推高

如上分析,yy-x都不可能降低,且三個分支只能中一個,所以,而yy-x的最大值均為N,所有分支執行總推高的次數不可能超過2N。即得出主流程的復雜度O(N)

KMP算法應用

求一個字符串的旋轉詞(詳見:LeetCode 796)

思路

將這個字符串拼接一下, 比如原始串為:123456,拼接成:123456123456

如果匹配的字符串是這個拼接的字符串的子串,則互為旋轉詞。

一棵二叉樹是否為另外一棵二叉樹的子樹(詳見:LeetCode 572)

思路

先將兩棵樹分別序列化為數組A和數組B,如果B是A的子串,那么A對應的二叉樹中一定有某個子樹的結構和B對應的二叉樹完全一樣。

更多

算法和數據結構筆記

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM