KMP算法思路

題目

給定一個字符串\(S\),求\(M\)字符串是不是\(S\)字符串中的子串.若是是,返回\(M\)對應\(S\)的第一個下標,不然返回-1.java

例如:S串爲a b c d a b c d a b c d e算法

M串爲a b c d e數組

結果:返回S串下標8.測試

我的想法

以前看過這種求子串的題,可是隻在腦海中想象了一下,沒有動手寫出算法.spa

看到的時候內心就嘀咕確定不能暴力求解,須要讓子串\(M\)進行跳躍,可是沒有具體地研究邊界問題,也沒有動手寫一個字符串驗證想法,致使想法很不靠譜.3d

以前的想法很粗糙:
code

挨個比較字符串,若是不相同,總體都跳躍,跳躍的方式是\(M\)串首位對其不匹配\(S\)串的下標位置.而後繼續比較
blog

大約就是這個樣子,想到這就沒繼續往下想也沒有去實現,這顯然是錯誤的.跳躍是應該跳躍,可是不是那麼粗糙的跳躍,假如字符串和子串是下面這種狀況這種狀況:
字符串

以我那種錯誤的想法是會產生bug的:
get

結果是\(S\)串不會包含\(M\)串,但事實上是包含的.(下文將目標串統一稱做\(S\),子串統一稱做\(M\))

那咱們就應該考慮如何正確的跳躍.

KMP算法

知道了以前的想法是錯誤的,咱們就應該找到符合各類邊界的跳躍方式.只要肯定了跳躍方式,KMP就很容易寫出來了.KMP中提到了前綴表(或者最大相同先後綴)這些概念.前綴表暫時先不考慮,只談談如何進行跳躍才能知足邊界.

繼續回到以前的圖,若是咱們想要成功地找到下標,正確的跳躍方式應該是下圖這樣:

注意\(M\)串錯誤匹配項(\(M[5]\)元素d)以前的元素(藍色填充的a,b).

好,爲何這麼跳呢?

當肯定\(S[5]\)\(M[5]\)不匹配的時候,咱們能夠肯定d以前的項都是匹配的.即\(S[0-4]\)\(M[0-4]\)是匹配的.

\(M[0-4]\)

對於已經匹配的\(M[0-4]\),前綴幾項與後綴幾項相同(即\(M[0]\),\(M[1]\)\(M[3]\),\(M[4]\)是相同的),那麼把\(M\)平移使\(M[0]\)對齊\(M[3]\)以前所處的位置,這必定能確保\(M[0]\),\(M[1]\)和以前\(M[3]\),\(M[4]\)\(S\)對應的\(S[3]\),\(S[4]\)是匹配的,即必定能確保\(M[0]\),\(M[1]\)\(S[3]\),\(S[4]\)是匹配的.

平移

那咱們如何利用這個特色跳躍呢,如今咱們生成一個輔助的先後綴相同的數組.

a,b,c,a,b,d爲例,

M產生的子串 相同先後綴個數 備註:就把相同先後綴數組稱爲\(D\)
a 0
a,b 0
a,b,c 0
a,b,c,a 1
a,b,c,a,b 2
a,b,c,a,b,d 0 最後一個匹配就能夠返回了,因此用處不大

最後生成一個相同先後綴的數組能夠與\(M\)對應起來

如今只須要解釋一下如何使用相同先後綴數組進行跳躍,應該就能寫出KMP的代碼了.

\(M[5]\)\(S[5]\)不相同時,看一下先後綴數組\(D\)前一個位置的值,即\(D[4]\)的值爲2,那麼只須要將\(M[2]\)平移至\(M[5]\)的位置上就能夠了.若是\(M[3]\)不匹配,後綴數組\(D\)的前一位\(D[2]\)是0,那麼就把\(M[0]\)平移對其\(M[3]\)的位置就能夠了.

假如\(M[0]\)不匹配的話,就直接日後跳一位,與\(S\)下一項比較.

KMP代碼實現(JAVA)

總體思路就大體如此了,若是理解了應該能夠按照思路寫出代碼了.

下面就貼一下代碼實現,我只是簡單地測試了一下,沒有大量測試,可是總體思路應該大體如此,可能有些邊界問題尚未考慮周全,歡迎指正.

public static int getIndexOf(String s, String m) {
        if (s == null || m == null || m.length() < 1 || s.length() < m.length()) return -1;
        char[] ss = s.toCharArray(), ms = m.toCharArray();
        int si = 0, mi = 0;
        int[] next = getMatchArray(ms);
        int q = next[mi];
        while (si < ss.length && mi < ms.length) {
            if (ss[si] == ms[mi]) {
                si++;
                mi++;
            } else {
                if (mi != 0) {
                    mi = next[mi - 1];
                } else {
                    si++;
                }
            }
        }
        return mi == ms.length ? si - mi : -1;
    }

    public static int[] getMatchArray(char[] ms) {
        if (ms.length == 1) return new int[]{0};
        int[] next = new int[ms.length];
        next[0] = 0;
        int j = 0, tail = 1;
        while (tail < ms.length) {
            j = next[tail] = ms[j] == ms[tail] ? (j + 1) : 0;
            tail++;
        }
        return next;
    }

    public static void main(String[] args) {
        String str = "abcabcababaccc";
        String match = "ababa";
        System.out.println(getIndexOf(str,match));
    }
相關文章
相關標籤/搜索