KMP

JiCanDuck發表於2024-08-30

呼——終於看懂了KMP——磕了三天了。

題目直達


Q: KMP是幹什麼的?

  • 是查詢字串用的,可以查詢到 \(S2\) 字串在 \(S1\) 字串中出現的位置(當然,你可以統計出次數)。

Q: 那複雜度是多少的?

  • 通常,我們認為他的複雜度是 \(O(|S1|)\), 雖然有點常數。

常規的的比較方法是直接比較,列舉一個頭,然後逐個比較,複雜度(喜提)\(O(|S1|\times|S2|)\)。慢就在於每次都要從頭掃。很浪費時間 (浪費生命),那實在是太浪費了,有沒有快點的辦法呢?如果一個串,的開頭已經確定了和我是一樣的,那不就可以少用時間了嗎?

我們希望(它/他/她)跳的儘可能遠,但是不能漏掉,這樣就可以節約大把的時間 (生命)。哪麼可以找到 \(S2\) 對於每個 \(i( 1 \le i \le n)\) \(next_i\) 表示 \(S2[0, i - 1]\)最大 / 字首和字尾相同 / 的長度,那麼我們就可以在字首匹配失敗後跳轉到字尾匹配。 \(next\) 陣列之和 \(S2\) 相關, 所以可以預處理。

現在問題就變成了如何預處理上了。

假設紅色[0 ~ j]的和橙色的 [i-j-1 ~ i-1] 已經匹配出來了,相等。那麼接下來要匹配 \(j + 1\)\(i + 1\)。假設最好情況 $ S2[j + 1] == S2[i]$, j++就好了。但是如果不等於呢?

那麼我們就要比較[0, j - 1] 和 [i - j] 了, 但是又變回了 \(O(n ^ 2)\)。其實我們可以轉換一下,反正橙色[i-j-1 ~ i-1]的和紅色[0 ~ j]的一致,我們不妨把橙色的字尾移到前面來。

那麼,這時我們就會發現我們要找的 \(j\) 應該變換的值我們之前求過是 \(next[j]\), 直接呼叫,但是為了避免沒法匹配 while 就好了。


上程式碼吧,有註釋的啦

#include <iostream>

using namespace std;

const int kMaxN = 1e6 + 5;

string s1, s2;
int l1, l2, net[kMaxN]; //最大字首和字尾相同的長度, 因為next是關鍵詞,所以用net替代

int main() {
    cin >> s1 >> s2;
    l1 = s1.size(), s1 = " " + s1; //舒服一點
    l2 = s2.size(), s2 = " " + s2; 
    int j = 0; //此時最長(既匹配到哪一位)
    for (int i = 2; i <= l2; i++) { //預處理
        while (j && s2[i] != s2[j + 1]) { //j已經為0就不用跳了
            j = net[j]; //不行就往回跳
        }
        if (s2[i] == s2[j + 1]) {//如果比較成功,j++
            j++;
        }
        net[i] = j;//賦值上
    }
    j = 0;
    for (int i = 1; i <= l1; i++) {
        while (j && s1[i] != s2[j + 1]) {
            j = net[j]; //匹配失敗就往回跳
        }
        if (s1[i] == s2[j + 1]) { //如果比較成功,j++
            j++;
        }
        if (j == l2) { //成功就輸出
            cout << i - l2 + 1 << '\n';
            j = net[j];
        }
    }
    for (int i = 1; i <= l2; i++) { //題目最後的要求
        cout << net[i] << ' ';
    }
    return 0;
}

相關文章