給初學(xué)者,最易讀懂的 KMP 模式匹配算法詳解

目的

  • 已知字符串 S 和 T,查找 T 在 S 中的位置。

暴力匹配法

  • 1-1、從下標(biāo) 0 位置開(kāi)始匹配,S[0]-->T[0]、S[1]-->T[1]、S[2]-->T[2]

    KMP_1.jpeg

  • 1-2、當(dāng) S[i]-->T[j] 失配時(shí),T 相對(duì)于 S 向右移動(dòng)一位再按照步驟1開(kāi)始匹配,S[1]-->T[0]...

    KMP_2_1.jpeg

  • 1-3、依此反復(fù),直到匹配成功或者失敗...

我們注意到,既然在1-1中已經(jīng)知道了 S[1] == T[1],而通過(guò) T 自身就可以知道 T[0] != T[1],因此即使不進(jìn)行步驟1-2中 S[1]-->T[0] 的比較我們也知道 S[1] != T[0]。因此這樣顯然做了很多不必要的操作。

KMP 匹配推導(dǎo)

  • 2-1、圖1中,匹配到 S[2]-->T[2] 時(shí)失配,說(shuō)明 S[0] == T[0],且 S[1] == T[1],即 S[0]...S[i] == T[0]...T[j]。假設(shè)有一個(gè)串 X 與 T[0]...T[j] 不匹配,那么這個(gè)串自然也與 S[0]...S[i] 不匹配。如圖3所示。

    KMP_3.jpeg

  • 2-2、回頭看步驟1-2,這里用 T[0]('A')與 S[1]('B')比較,其實(shí)可看做 T[0] 與 T[1] 比較,如果 T[0] 與 T[1] 不匹配,依據(jù) 2-1 的推理,那么需要一個(gè)已經(jīng)匹配到的子串 T[0]...T[j](步驟1-1中的j值為1)的前綴和后綴進(jìn)行比較的過(guò)程(T[0]...T[j-1]與T[1]...T[j])。

    KMP_4.jpeg

  • 2-3、"前綴"指除了最后一個(gè)字符以外,一個(gè)字符串的全部頭部組合;"后綴"指除了第一個(gè)字符以外,一個(gè)字符串的全部尾部組合。

  • 2-4、圖4中的“前綴”有:0,01,012

  • 2-5、圖4中的“后綴”有:3,23,123

  • 2-6、如果“前綴”和“后綴”中沒(méi)有匹配的,則步驟1-2的“向右移動(dòng)一位”變成了向右移動(dòng):

    KMP_5.jpeg

  • 2-7、步驟1-1中已經(jīng)匹配了的長(zhǎng)度為2,前后綴匹配最大值為0(前綴只有'A',后綴只有'B')。因此步驟1-2可以直接將 T 向右移動(dòng) 2 位進(jìn)行匹配。

  • 2-8、計(jì)算 T 的子串(T[0]、T[0]...T[1]、~、T[0]...T[strlen(T)])每一個(gè)的最大匹配值存儲(chǔ)到數(shù)組 next 中。

  • 2-9、整個(gè)匹配過(guò)程就可以寫(xiě)作:從頭開(kāi)始匹配、遇到失配的字符就到 next 數(shù)組獲取當(dāng)前 T 的下標(biāo)對(duì)應(yīng)的值 k、根據(jù) k 值決定 T 向后移動(dòng)多少位然后開(kāi)始新一輪的匹配...,所以圖2-1可修改為圖2-2

    KMP_2_2.jpeg

next 數(shù)組

  • 3-1、定義用 k 表示某一子串的前后綴最大匹配值,next 數(shù)組依次存放 k 值。

  • 3-2、任何一個(gè)字符串下標(biāo)為0時(shí)都沒(méi)有“前綴”和“后綴”,因此所有 next[0] = 0(為了不同的算法計(jì)算方便也有取為 -1 的)

  • 3-3、假設(shè)我們知道了 next[j-1] 的值為 k,可以推斷出 T[0]...T[k-1] == T[j-k]...T[j-1],接下來(lái)看 T[k] 和 T[j] 是否相等。

    KMP_6.jpeg

  • 3-4、從圖6可以看出,如果 T[k] == T[j],next[j] = next[j-1] + 1。如果 T[k] != T[j] 呢?為了更加直觀,我們把 T[0]...T[K]移到 T[j-k]...T[j] 的下方:

    KMP_7.jpeg

  • 3-5、這樣確實(shí)可以達(dá)到目的,但是這種方式有沒(méi)有似曾相識(shí)?沒(méi)錯(cuò)!這不就是跟上面一樣的流程嘛,只是在子串中找子串的子串然后進(jìn)行匹配。

    KMP_8.jpeg

  • 3-6、我們知道 k 一定比 j 小,也比 j-1 小,既然 next[j-1] 是已知的,那么 next[k] 也一定已經(jīng)確認(rèn)過(guò)了。而我們又可以很輕易地推導(dǎo)出圖中圈出的三個(gè)小串是相互匹配的(這三個(gè)小串的長(zhǎng)度為 next[k])。

  • 3-7、因此我們這時(shí)候可以用 T[next[k]+1] 去和 T[j] 比較,如果相等,則 T[j] = next[k] + 1。如果不相等,則遞歸執(zhí)行 3-6、3-4,直到找到 T[j] 相匹配的字符或者直到子串長(zhǎng)度為0。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容