【字符串】KMP

Algorithm

Task

給定一個文本串 \(S\) 和一個模式串 \(T\),求 \(T\)\(S\) 中出現的所有位置。

Limitations

要求時空複雜度均爲線性。

Solution

回頭重新學一遍看毛片 KMP 算法。

\(X\) 是一個字符串,則以下表述中,\(X_u\) 代表 \(X\) 的第 \(u\) 個字符,\(X_{u \sim v}\) 代表 \(X\) 的從 \(u\) 起到 \(v\) 結束的字串。

首先定義一個字符串的公共前後綴爲這個字符串的一個 \(border\),最長公共前後綴稱爲最長 \(border\)。特別的,不認爲字符串本身是自身的 \(border\)

性質:字符串 \(S\)\(border\)\(border\) 一定是 \(S\)\(border\),正確性顯然。因此不斷地跳最長 \(border\) 可以遍歷字符串的所有 \(border\)

例如,對於字符串 \(abaab\) 來說,其唯一的 \(border\)\(ab\)

暴力匹配兩個字符串,時間複雜度爲 \(O(|S||T|)\),考慮優化這個算法。

假設當前匹配時 \(S\) 掃描到了第 \(i\) 位, \(T\) 掃描到了第 \(j\) 位,且 \(S\)\(i\) 向前 \(j\) 位組成的字符串與 \(T\) 的前 \(j\) 位相同,而 \(S_{i + 1} \neq T_{j+1}\),我們稱爲發生了失配。

考慮失配時,指針 \(i\) 不變,只有將指針 \(j\) 前移,纔可能令下一位成功匹配。由於 \(i\) 不變,所以下一個可能發生匹配的字符串一定是 \(T_{1 \sim j}\) 的某個前綴 \(T_{1 \sim k}\) 滿足

\[T_{1 \sim k} = S_{i - k + 1 \sim i}\]

其中由於 \(T_{1 \sim k}\)\(T_{1 \sim j}\) 的字串,一定有 \(k < j\)。由於 \(S_{1 \sim i}\) 的後 \(j\) 位與 \(T\) 的前 \(j\) 位匹配,又有 \(k < j\),因此 \(T_{1 \sim j}\) 的後 \(k\) 位一定與 \(S_{1 \sim i}\) 的後 \(k\) 位即 \(S_{i - k + 1 \sim i}\) 匹配。得出

\[T_{j - k + 1 \sim j} = S_{i - k + 1 \sim i}\]

上面兩個式子等量代換得到

\[T_{1 \sim k} = T_{j - k + 1 \sim j}\]

\(border\) 的定義,我們發現 \(T_{1 \sim k}\) 一定是 \(T_{1 \sim j}\)\(border\)。根據 \(border\) 的性質,我們只需要不斷的跳 \(T_{1 \sim j}\) 的最長 \(border\) 即可找到一個最長的可以與 \(S_{1 \sim i}\) 的後幾位匹配的字串。因此問題轉化爲了如何求一個字符串 \(T\) 的所有前綴的最長 \(border\)

顯然 \(border_1 = 0\)。從第 \(2\) 位開始,我們發現問題等價於用 \(T\)(模式串) 的一個前綴去匹配 \(T_{1 \sim i}\) (文本串)的一個後綴,求這個後綴最長是多少,而這個問題的解決方法與上面那個問題的方法 完 全 一 致,都是不斷跳 \(border\) 即可。在 \(i\)\(j\) 成功匹配時,記錄 \(border_i = j\)。而在這個問題中,由於 \(j\) 恆小於 \(i\),正向掃描 \(i\) 時,所用到的 \(border\) 值都已經被計算出,因此可以得出正確的結果。

考慮時間複雜度:一個顯然的事實是每次跳 \(border\) 模式串指針 \(j\) 都會至少減少 \(1\),而當且僅當第 \(S_{i+1}\) 與第 \(T_{j+1}\) 匹配時,\(j\) 纔會自增,因此 \(j\) 僅增加了 \(O(|S|)\),因此 \(j\) 只可能減少 \(O(|S|)\) 次,所以跳 \(border\) 的總次數不超過 \(O(|S|)\),而掃描整個文本串需要 \(O(|S|)\) 的時間,因此總時間複雜度 \(O(|S|)\)

Example

P3375 【模板】KMP字符串匹配

Description

給定一個文本串 \(S\) 和一個模式串 \(T\),求 \(T\)\(S\) 中出現的所有位置,同時要求輸出 \(T\) 的每個前綴的 \(border\) 長度。

Limitations

字符串長度不超過 \(10^6\)

Solution

闆闆題

Code

#include <cstdio>
#include <cstring>

const int maxn = 1000006;

char S[maxn], T[maxn];
int nxt[maxn];

void KMP(char *A, char *B, int x, int y, const bool pt);

int main() {
  freopen("1.in", "r", stdin);
  scanf("%s\n%s", S + 1, T + 1);
  int x = strlen(S + 1), y = strlen(T + 1);
  KMP(T, T, y, y, false); KMP(S, T, x, y, true);
  for (int i = 1; i <= y; ++i) {
    qw(nxt[i], i == y ? '\n' : ' ', true);
  }
  return 0;
}

void KMP(char *A, char *B, int x, int y, const bool pt) {
  for (int j = 0, i = pt ? 1 : 2; i <= x; ++i) {
    while (j && (B[j+1] != A[i])) j = nxt[j];
    if (B[j+1] == A[i]) ++j;
    if (!pt) nxt[i] = j;
    if (j == y) {
      qw(i - y + 1, '\n', true);
    }
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章