Appearance
简介
KMP 是一种快速的字符串匹配算法,是其发明者 Kruth
,Morris
和 Pratt
的名字缩写。
问题
给定字符串
考虑暴力算法:
- 先将
和 左端对齐; - 如果匹配失败,就将
右移一位,直到匹配成功。
时间复杂度:
cpp
bool match(string a, string b) {
int m = a.size(), n = b.size();
int i = 0, j = 0;
while (i < m) {
while (j < n && a[i] == b[j])
j ++;
if (j == n)
return true;
i ++, j = 0;
}
return false;
}
KMP 算法可以将此复杂度优化到
原理
当匹配失败时,我们忽视了一些重要的信息。当第一次匹配失败时,两个绿串相等。
该绿串还具有相同的前缀[1]和后缀,称作「公共前后缀」。
若
到此为止,KMP 的思路已经很明朗了。
将
现在还有一个问题:在程序中,如何实现字符串的右移?—— 使用指针。
指针
现在假设
- 令
分别指向 和 的开头,即 ; - 如果
, 和 同时右移一位; - 如果
,又分两种情况讨论: 时,绿串的公共前后缀长度为 。那么就令 ; 时, 不能再往左移。此时只能将 右移一位。
- 重复 2、3 两个步骤,直到匹配完成。
令指针
cpp
int pre[];
bool kmp(string A, string B) {
int i = 0, j = 0;
while (i < A.size()) {
if (A[i] == B[j])
i ++, j ++;
else if (j)
j = pre[j - 1];
else i ++;
if (j == B.size()) //匹配成功
return true;
}
return false;
}
预处理
指针
然后,我们直接采用类似 KMP 主程序 的策略:
- 如果
,那么 ,并将 和 同时右移一位; - 如果
,又分两种情况讨论: 时,绿串的公共前后缀长度为 。那么就令 ; 时,绿串长度为 , 不能再往左移了.此时只能将 右移一位。
- 重复前两步,直到
全部求完。
为什么可以这么做呢?因为仅当
预处理
cpp
int pre[];
void getPre(string B) {
int i = 1, j = 0; // 注意 i = 1
while (i < B.size()) {
if (B[i] == B[j])
pre[i] = j + 1, i ++, j ++;
else if (j)
j = pre[j - 1];
else i ++;
}
}
模板
cpp
const int N = 1e6;
int pre[N];
void getPre(string str) {
int i = 1, j = 0;
while (i < str.size()) {
if (str[i] == str[j])
pre[i] = j + 1, i ++, j ++;
else if (j)
j = pre[j - 1];
else i ++;
}
}
bool kmp(string str, string substr) {
getPre(substr);
int i = 0, j = 0;
while (i < str.size()) {
if (str[i] == substr[j])
i ++, j ++;
else if (j)
j = pre[j - 1];
else i ++;
if (j == substr.size())
return true;
}
return false;
}
int main() {
string str, substr;
cin >> str >> substr;
cout << kmp(str, substr);
return 0;
}
字符串
左部的任意子串为 的前缀,且 的前缀不能是 本身。例如 freeze
的前缀有f
,fr
,fre
,free
,freez
。 ↩︎