KMP算法

KMP算法

KMP算法用於在文本串S內查找模式串P的出現位置 時間複雜度爲O(M + N)

最大長度表

前綴和後綴

前綴:除了最後一個字符以外,一個字符串的全部頭部組合

後綴:除了第一個字符以外,一個字符串的全部尾部集合

前綴後綴的最長公共元素長度

舉例說明,假如模式串爲P = "ABCABD"

模式串 A B C A B D
最大前綴後綴公共元素長度 0 0 0 1 2 0
  1. “A” :前綴爲空集,後綴爲空集,共有元素長度爲0
  2. “AB”:前綴爲[A],後綴爲[B],共有元素長度爲0
  3. “ABC”:前綴爲[A, AB],後綴爲[C, BC],共有長度爲0
  4. “ABCA”:前綴爲[A, AB, ABC],後綴爲[A, CA, BCA],共有長度爲1
  5. “ABCAB”:前綴爲[A, AB, ABC, ABCA],後綴爲[B, AB, CAB, BCAB],共有長度爲2
  6. “ABCABD”:前綴爲[A, AB, ABC, ABCA, ABCAB],後綴爲[D, BD, ABD, CABD, BCABD],共有長度爲0

next數組

next數組是KMP算法的核心部分,其中next[i]的定義爲:在模板串P[0, i - 1]範圍內的前綴後綴最長公共元素長度

根據定義可以知道next數組可以由最大長度表向右移動一位得到

爲了計算方便next[0]通常設爲-1

示例

模板串 A B C A B D
next[i] -1 0 0 0 1 2

next[i+1]的推導

假如已經匹配到了next[i],接下來要得到next[i+1],要分兩種情況考慮:

1. P[i] = P[k]

模板串 A B C A B C D
next[i] -1 0 0 0 1 2 3

i = 5時,next[i-1] = 1,前綴後綴最長公共子串長度爲1,此時k = 0P[k+1] = BP[i] = B,可以看到前綴後綴最長公共子串爲"AB",長度爲2,所以next[5] = 2

結論:next[i+1] = next[i] + 1

2. P[i] != P[k]

模板串 A B C A B D D
next[i] -1 0 0 0 1 2

i = 5時,next[i] = 2,前綴後綴最長公共子串爲"AB",此時k = 2P[k] = CP[i] = DP[k] != P[i],但是因爲已知前面有相同的"AB",可以找P[0,k-1] = P[0, 1]P[i-k+1, i] = P[4,5]的最長重合串,根據next數組的定義可知即爲求next[k]

k = next[k] = 0,重新比較P[i] = CP[k] = A,兩者不匹配,此時next[k] = -1,說明P[0, i ]的前綴後綴最長公共子串長度爲0,所以next[6] = 0

推薦看參考2的博客,這部分講的很詳細

結論:k = next[k]

個人對於k = next[k]的理解:

p[i] != p[k]時,就需要找出比next[i] + 1小的新前綴後綴最長公共子串長度。next數組本身的作用是找出[0,i-1]部分的前綴和後綴的最長公共子串長度,假設要找的新前綴後綴最長公共子串長度爲l1l_1,相比於逐個減一去判斷,可以利用next數組做優化, k = next[k],判斷的前綴和後綴也相應的縮短到P[0,k]P[i-k,i],如果得到了新的前綴和後綴,結束迭代,否則繼續k = next[k]直至k == -1爲止

實現

public int[] createNext(String P) {
    char[] p = P.toCharArray();
    int[] next = new int[p.length];
    int i = 0, k = -1;
    while (i < p.length - 1) {
        if (k == -1 || p[i] == p[k]) {
            i++;
            k++;
            next[i] = k;
        } else {
            k = next[k]; // 重點是這一行
        }
    }
}

KMP算法

KMP算法就是利用next數組對暴力算法進行了優化。暴力算法在失配之後會回到最開始重新匹配,在S[i]P[j]失配的時候,利用next數組將模板串P往右移動j - next[j]

思路

設置兩個變量i = 0j = 0,文本串S和模式串P

  1. 如果S[i]P[j]匹配,文本串和模式串繼續向後匹配:i++;j++;
  2. 如果S[i]P[j]不匹配,保持i不變,將模板串向右移動j - next[j]j = next[j];
  3. 如果j = -1,說明當前S[i]開頭的字符串無法與P匹配,j++讓模板串P回到開頭P[0]i++S[i+1]開頭的字符串與P開始匹配

實現

public int KMP(String text, String template) {
    char[] textChar = text.toCharArray();
    char[] templateChar = template.toCharArray();
    int[] next = createNext(templateChar);
        
    int i = 0, j = 0;
        
    while (i < textChar.length && j < templateChar.length) {
    	if (j == -1 || textChar[i] == templateChar[j]) {
            i += 1;
            j += 1;
        } else if (j != -1 && textChar[i] != templateChar[j]) {
            j = next[j];
        }
    }
    if (j >= templateChar.length) {
        return i - t.length; // 匹配成功
    } else {
        return -1;
    }
}

優化

i 0 1 2 3 4 5 6
模式串 A B C D A B D
next[i] -1 0 0 0 0 1 2

i = 5時匹配失敗,此時應該把i = 1處的字符拿來繼續比較,但是P[5] == P[1] == B,兩個字符一樣

實現

public int[] createNext(String P) {
    char[] p = P.toCharArray();
    int[] next = new int[p.length];
    int i = 0, k = -1;
    while (i < p.length - 1) {
        if (k == -1 || p[i] == p[k]) {
            i++;
            k++;
            if (P[i] != P[k]) {
                next[i] = k;
            } else {
                next[i] = next[k]; // 相同情況下就繼續往前找
            }
        } else {
            k = next[k];
        }
    }
}

參考

  1. 從頭到尾徹底理解KMP(2014年8月22日版)
  2. KMP算法—終於全部弄懂了
  3. 字符串匹配的KMP算法
  4. KMP 算法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章