[演算法] KMP

KMP演算法是在一段文字中,找出指定的子字串出現位置(substring search)的演算法
比較詳細的說明我就不闡述,把資料留在文末。

名詞定義一下:
在一段文字中的文字,假設叫做 text
要搜尋的子字串,假設叫做 pattern
在 text 中比對到的位置使用 i,
在 pattern 中比對的位置使用 j

Prerequisites

  • 製作出 prefix table


什麼是 Prefix table
Prefix table 是這個 pattern 字串所有的子字串的最長前後綴的表

舉例:
pattern = ABCDABD

各個子字串的最長前後綴

子字串 前綴 (prefix) 後綴 (prefix) 最大公前後綴字串長度
A 0
AB A B 0
ABC A,AB C,BC 0
ABCD A,AB,ABC D,CD,BCD 0
ABCDA A,AB,ABC,ABCD A,DA,CDA,BCDA 1
ABCDAB A,AB,ABC,ABCD,ABCDA B,AB,DAB,CDAB,BCDAB 2

這時候就可以把最大公前後綴字串長度變成程式在用的模式
pattern

A B C D A B D
0 0 0 0 1 2 0
-1 0 0 0 0 1 2

為什麼要多下面
-1 0 0 0 0 1 2 ?
其實就是把 prefix table 往後移動一位,在最前面補上 -1
而且完整的 pattern 其實用不到,所以大部分的做法都會這樣做哦!

其實 prefix table 的用意就是說
假設在 pattern 的 j = 5時,所座落在的位置:B
也就是 ABCDABD
前面最大相同的字串長度一定至少有兩位是一樣的,也就是至少是 AB!

當把 prefix table 建立完成之後,剩下就比較單純了

假設 text = "ABC ABCDAB ABCDABCDABDE"
pattern = "ABCDABD"

i = 0, j = 0
text[0] == pattern[0] => A == A => 所以接著比對下一個 => i++; j++;
.
.
.
i = 3, j = 3
text[3] != pattern[3] => ' ' !== D => 這時候看一下 prefix table 的第j (j=3) 個位置
=> prefix[3] = 0
=> 所以把 j 移動到 0, 也就是 i = 3, j = 0 繼續比較
             1         2  
i: 01234567890123456789012
T: ABC ABCDAB ABCDABCDABDE
P: ABCDABD
j: 0123456

i = 3, j = 0
text[3] != pattern[0] => ' ' !== A => 這時候看一下 prefix table 的第j (j=0) 個位置
=> prefix[0] = -1 
=> 所以把 j 移動到 j = -1
=> j = -1 時就直接跳下一個
=> 直接跳下一個 => i++; j++
=> i = 4, j = 0
             1         2  
i: 01234567890123456789012
T: ABC ABCDAB ABCDABCDABDE
P:     ABCDABD
j:     0123456

i = 4, j = 0 接下來就一直比對到 i = 10, j = 6,
text[10] != pattern[6] => ' ' != D => 這時候看一下 prefix table 的第j (j=6) 個位置
=> prefix[6] = 2
=> 所以把 j 移動到 2, 也就是 i = 10, j = 2 繼續比較
             1         2  
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:         ABCDABD
i:         0123456
i = 10, j = 2
text[10] != pattern[2] => ' ' != C => 這時候看一下 prefix table 的第j (j=2) 個位置
=> prefix[2] = 0
=> 所以把 j 移動到 0, 也就是 i = 10, j = 0 繼續比較
             1         2  
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:           ABCDABD
i:           0123456
i = 10, j = 0
text[10] != pattern[0] => ' ' != A => 這時候看一下 prefix table 的第j (j=0) 個位置
=> prefix[0] = -1
=> j = -1
=> i++; j++;
=> i = 11, j = 0

i = 11, j = 0
沿路比對到
i = 17, j = 6
             1         2  
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:            ABCDABD
i:            0123456

i = 17, j = 6
text[17] != pattern[6] => C != D => 這時候看一下 prefix table 的第j (j=6) 個位置
=> prefix[6] = 2
=> 所以把 j 移動到 2, 也就是 i = 17, j = 2 繼續比較
             1         2  
m: 01234567890123456789012
S: ABC ABCDAB ABCDABCDABDE
W:                ABCDABD
i:                0123456

這次就剛好
i = 17, j = 2 沿路比對到 i = 21, j = 6 一路都相等且 j 為最後一位
這時候就找出 pattern 的位置為 i - j => 21 - 6 = 15!

function prefixTable(pattern) {
    let maxPrefixLength = 0;
    let prefixTable = [0];
    let i = 1;
    while ( i < pattern.length ) {
        if (pattern[i] === pattern[maxPrefixLength]) {
            maxPrefixLength++;
            prefixTable[i] = maxPrefixLength;
            i++;
        } else {
            if (maxPrefixLength > 0) {
                maxPrefixLength = prefixTable[maxPrefixLength-1];    
            } else {
                maxPrefixLength = 0;
                prefixTable[i] = maxPrefixLength;
                i++;
            }
        }
    }
    return prefixTable;
}

function movePrefixTable(prefixTable) {
    let newPrefix = [...prefixTable];
    for (i = newPrefix.length -1; i > 0; i--) {
        newPrefix[i] = newPrefix[i-1];
    }
    newPrefix[0] = -1;
    return newPrefix;
}

function kmpSearch(text, pattern) {
    let m = text.length;
    let n = pattern.length;
    let prefix = movePrefixTable(prefixTable(pattern));
    let i = 0;
    let j = 0;

    while (i < m) {
        if (text[i] === pattern[j] && j === (n-1)) {
            return i - j;
        }
        if (text[i] === pattern[j]) {
            i++; j++;
        } else {
            j = prefix[j];
            if (j === -1) {
                i++;
                j++;
            }
        }
    }
    return -1
}

補充資料:
https://www.youtube.com/watch?v=dgPabAsTFa8

留言

這個網誌中的熱門文章

[MySQL] schema 與資料類型優化

[翻譯] 介紹現代網路負載平衡與代理伺服器