1 介绍
本博客用来记录KMP相关概念和题目。
KMP算法已经集成到string类型的find()方法了,
但这里我们不用这个,我们自己来实现这个方法。
KMP算法的关键步骤:
- p[N]表示输入模式串,求取该模式串的ne[]数组。ne[i]表示前缀等于后缀的长度,且它最长。也即p[1,j] = p[i-j+1,i]。
- 循环目标串s[M],利用ne[]数据,得到完全匹配模式串的下标位置,并输出。
//步骤(1)
for (int i = 2, j = 0; i <= n; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
//i表示后缀末尾,j + 1表示前缀末尾。
if (p[i] == p[j + 1]) j ++;
ne[i] = j;
}
//步骤(2)
for (int i = 1, j = 0; i <= m; i ++ )
{
while (j && s[i] != p[j + 1]) j = ne[j]; //发生了冲突之后,看它前一位的next数组值,即ne[j],而不是ne[j + 1]
//i表示文本串"aabaabaaf"的下标,j表示模式串"aabaaf"的下标。
if (s[i] == p[j + 1]) j ++ ;
if (j == n)
{
printf("%d ", i - n);
j = ne[j];
}
}
最长相等前后缀,
以字符串aabaabaaf
、模式串aabaaf
为例,讲解KMP算法。
首先计算模式串aabaaf
的next数组,即最长相等前后缀。计算如下,
a 无前缀与后缀 最长相等前后缀为0
aa 最长相等前后缀为1
aab 最长相等前后缀为0
aaba 最长相等前后缀为1
aabaa 最长相等前后缀为2
aabaaf 最长相等前后缀为0
故next数组如下,
next[1] = 0, next[2] = 1, next[3] = 0, next[4] = 1, next[5] = 2, next[6] = 0.
实现代码如下,
const int N = 1e6 + 10;
char p[N];
int ne[N];
cin >> p + 1; //输入模式串"aabaaf"
for (int i = 2, j = 0; i <= n; i ++ )
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++;
ne[i] = j;
}
计算next数据的主要步骤有,
- 初始化
- 处理前后缀末尾字符不相同的情况,i表示后缀末尾,j + 1表示前缀末尾。
- 处理前后缀末尾字符相同的情况,
- 后缀末尾+1
综合上述,kmp的C++模板如下,
void kmp(string &pattern_str, string &match_str) {
//cout << "pattern_str = " << pattern_str << ", match_str = " << match_str << endl;
int n = pattern_str.size();
int m = match_str.size();
pattern_str = '#' + pattern_str;
match_str = '$' + match_str;
//求取next数组ne
vector<int> ne(n + 10, 0);
for (int i = 2, j = 0; i <= n; ++i) {
while (j && pattern_str[i] != pattern_str[j+1]) j = ne[j];
if (pattern_str[i] == pattern_str[j+1]) j++;
ne[i] = j;
}
//返回匹配的下标位置
vector<int> idxs; //匹配串match_str中的下标位置,表示从改下标起,与模式串pattern_str匹配
for (int i = 1, j = 0; i <= m; ++i) {
while (j && match_str[i] != pattern_str[j+1]) j = ne[j];
if (match_str[i] == pattern_str[j+1]) j++;
if (j == n) {
//cout << i - n << " ";
idxs.emplace_back(i-n);
j = ne[j];
}
}
for (int idx : idxs) {
cout << idx << " ";
}
cout << endl;
pattern_str = pattern_str.substr(1);
match_str = match_str.substr(1);
return;
}
上述pattern_str
表示模式串,match_str
表示匹配串,idxs
存储匹配串match_str
中的下标。例如,调用kmp("aba", "ababa")
,idxs
中的内容为{0, 2}
。
2 训练
题目1:831KMP字符串
C++代码如下,
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
void kmp(string &pattern_str, string &match_str) {
//cout << "pattern_str = " << pattern_str << ", match_str = " << match_str << endl;
int n = pattern_str.size();
int m = match_str.size();
pattern_str = '#' + pattern_str;
match_str = '$' + match_str;
//求取next数组ne
vector<int> ne(n + 10, 0);
for (int i = 2, j = 0; i <= n; ++i) {
while (j && pattern_str[i] != pattern_str[j+1]) j = ne[j];
if (pattern_str[i] == pattern_str[j+1]) j++;
ne[i] = j;
}
//返回匹配的下标位置
vector<int> idxs; //匹配串match_str中的下标位置,表示从改下标起,与模式串pattern_str匹配
for (int i = 1, j = 0; i <= m; ++i) {
while (j && match_str[i] != pattern_str[j+1]) j = ne[j];
if (match_str[i] == pattern_str[j+1]) j++;
if (j == n) {
//cout << i - n << " ";
idxs.emplace_back(i-n);
j = ne[j];
}
}
for (int idx : idxs) {
cout << idx << " ";
}
cout << endl;
return;
}
int main() {
string pattern_str; //模式字符串
string match_str; //匹配字符串
int t1, t2; //没有用上
cin >> t1 >> pattern_str >> t2 >> match_str;
kmp(pattern_str, match_str);
return 0;
}
使用C++库函数实现的版本如下,
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int n, m;
string p, s;
cin >> n >> p >> m >> s;
int pos = s.find(p);
while (pos != string::npos) {
cout << pos << " ";
pos = s.find(p, pos + 1);
}
cout << endl;
return 0;
}