KMP 算法詳解

一、什麼是 KMP 算法

KMP 是一種字符串匹配算法。比如,一個字符串 ASDFASDFASD,我想知道其是否包含 ASDFS 這個子串,這個時候就用到了 KMP 算法啦。這時候可能有人就要問啦,不就是字符串匹配嘛,這有啥難的,逐個字符比較就完事了,兄弟,咱要考慮效率啊,KMP 算法相比於暴力匹配則擁有有更好的執行效率。接下來我們來學習它的實現思路。

二、KMP 字符串匹配過程推演

爲了方便大家理解,我們先通過一系列圖片,演示下 KMP 的比較過程:

假設我們有一個字符串 AD ASDFSD ASDFABBAA,我們需要校驗其是否包含 ASDFA 這個子串:

1)首先比較字符串與子串首個字符,此時是不相同的,於是子串後移一位。

2)由於 D 與 A 不同,則子串仍要後移一位。

3)就這樣不斷比較,直到出現字符串與子串首字符相同。

4)此時比較字符串與子串的下一個字符,是相等的則繼續比較。

5)直到遇到不同的字符,這時候 KMP 的優勢就會體現出來:

如果是暴力匹配,此時肯定是要將子串向後移動一位,繼續比較,但是這樣做效率真的太低了,我們通過前面幾輪的比較已經知道 ASDF 是相同的,那能否根據已知信息不再比較這些字符呢?這裏就需要引入【部分匹配表】,如下所示:

CxUcsR

這張表是 KMP 算法的核心,關於這張表如何計算後面會講解,大家先繼續往下看。

6)此時我們知道 S 與 A 不匹配,然後前面四個字符都是匹配的,最後一個匹配字符 F 的匹配值是 0,我們通過如下公式計算子串移動位數:4 - 0 = 4。

移動位數 = 已匹配的字符數 - 對應的部分匹配值

7)移動後仍然重複前面的流程逐個字符比較,最終找到匹配的子串:

我們可以看到 KMP 可以減少比較次數從而提高計算效率,那麼這個算法最核心的【部分匹配表】是如何得到的呢?

三、部分匹配表

首先需要介紹下前綴、後綴及部分匹配值,這三個名詞的概念:

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

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

部分匹配值:就是前綴和後綴的最長的共有元素的長度。

我們以 ASDFA 爲例進行說明:

7yQdGO

因此,我們可以明白 KMP 的實現思路就是:當一次匹配過程中在模式串 j 位置處出現字符不等時,不需要回溯主串上面的指針 i,而是利用模式串 P 在 j-1 位置的部分匹配值 k 將模式向右滑 j-k 個字符,然後繼續進行比較。

四、代碼編寫

最後我們以 Java 爲例,編寫 KMP 算法的代碼:

public class KMPDemo {
    private int[] next;
    private void initNext(String pattern) {
        int i = 0;
        int j = -1;
        int len = pattern.length();
        next = new int[pattern.length()];
        next[0] = -1;
        while(i < len - 1) {
            if (j == -1 || pattern.charAt(j) == pattern.charAt(i)) {
                i++;
                j++;
                next[i] = j;
            } else {
                j = next[j];
            }
        }
    }
    public int search(String s, String pattern) {
        int i = 0;
        int j = 0;
        int sLen = s.length();
        int pLen = pattern.length();
        initNext(pattern);
        while(i < sLen && j < pLen) {
            if (s.charAt(i) == pattern.charAt(j)) {
                i++;
                j++;
            } else {
                if (next[j] == -1) {
                    i++;
                    j = 0;
                } else {
                    j = next[j];
                }
            }
            if (j == pLen) {
                return i - j;
            }
        }
        return -1;
    }
    public static void main(String[] args) {
        String s = "AD ASDFSD ASDFABBAA";
        String pattern = "ASDFA";
        KMPDemo kmp = new KMPDemo();
        System.out.println(kmp.search(s, pattern));
    }
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/0bwzlzG_epm_2ouOiLOSug