本篇对应源码见仓库
solutions/xcpc/algorithm/string/kmp/。
BF算法
说明
-
BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。
-
时间复杂度:O(mn)
示例
int findPos(string s, string t)
{
int m = s.size(), n = t.size();
int i = 0, j = 0;
while (i < m && j < n)
{
if (s[i] == t[j])
i++, j++;
else
i = i - j + 1, j = 0;
}
if (j == n)
return i - j;
return -1;
}
KMP算法及其优化
原理和说明
- 在匹配过程中,主串的指针不需要回溯,只回溯模式串的指针(回溯到匹配失败位置前的模式串的内容的最长公共前后缀的长度),然后继续比较
- 优化后的
KMP算法的优化点在于aaaaabaaaaac与aaaaac的匹配,b与c失配后与a匹配(失配),然后根据next数组,后面又是与a匹配,这里是在做无用功 - 注意,kmp优化后的匹配过程也适用于优化前的匹配过程
前缀:包含头不包含尾的所有子串
后缀:包含尾不包含头的所有子串
最长相等前后缀:两者的交集的中的最大长度
注意:若字符串长度为1,则没有前后缀
next 数组:
- 给模式串的每个位置
i赋一个值,表示i之前的字符串前后缀最大匹配长度 - 特殊的,
next[0] = -1, next[1] = 0- 如模式串
t = "aabaabsaabaaa的next[] = {-1, 0, 1, 0, 1, 2, 3, 0, 1, 2, 3, 4, 5, 2}
- 如模式串
求next数组(前缀表)
步骤:
- 初始化,
next[0] = -1, next[1] = 0 next[i] 与本身字符 s2[i]没有关系- 那么
next[i]可以根据next[i - 1],和s2[i - 1] =?= s2[next[i - 1]]得出,实际上就是看上一个字符s2[i - 1]与上一个字符最大前缀的下一个字符s2[next[i - 1]]是否相等,否则递归处理
void get_next(string s, vector<int> &ne)
{
ne[0] = -1;
int m = s.size();
if(m == 1) return;
ne[1] = 0;
// i: 当前要求的 next 值的位置
// ind: 当前要和 s[i - 1] 匹配的下标
int i = 2, ind = 0;
while(i < m) {
if(s[i - 1] == s[ind]) {
ne[i++] = ++ind;
} else if(ind > 0) {
ind = ne[ind];
} else {
ne[i++] = 0;
}
}
}
匹配过程
步骤:
-
如果当前
s1[x] == s2[y]说明,这一位匹配上了,那么x++, y++ -
否则,如果当前
y == 0,也就是说,s2不能通过前缀数组进行跳跃了,也就是s2没有一个前缀可以和s1当前后缀进行匹配,那么只能x++ -
否则,是可以进行跳跃,用
s2的前缀和s1当前的某个后缀进行匹配,那么就是y进行跳跃操作,y = next[y] -
那么循环退出条件就是
x < n && y < m -
那么最终退出循环后,判断是否匹配成功的条件就是
y == m ? x - m : -1,匹配成功返回的是首字母匹配的下标
int kmp(string s1, string s2) {
int n = s1.size(), m = s2.size();
int x = 0, y = 0;
while(x < n && y < m) {
if(s1[x] == s2[y]) {
x++, y++;
} else if(y != 0) {
y = next[y];
} else {
x++;
}
}
return y == m ? x - m : -1;
}
求nextval数组
void get_nextval(string s, int m, int *ne, int *neval)
{
neval[0] = -1;
if (m == 1)
return;
int i = 0;
while (i < m)
{
if (s[i] == s[ne[i]])
neval[i] = neval[ne[i]]; // 跳过相同的匹配不上的字符
else
neval[i] = ne[i]; // 否则,与 next 一样
i++;
}
return;
}
优化后的匹配过程
int i = 0, j = 0;
while (i < m && j < n)
{
// 如果 j == -1,说明 i 这个位置不可能匹配,所以 i++, j = 0(即j++)
// 如果 s1[i] == s2[j], 说明 i 这个位置匹配上了,所以 i++, j++
if (j == -1 || s1[i] == s2[j])
i++, j++;
else // 其他情况: j != -1 且匹配不上,则 j = neval[j]
j = neval[j];
}
cout << (j == s2.size() ? "YES" : "NO") << endl;
究极完整代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define pii pair<int, int>
#define all(x) x.begin(), x.end()
const int MOD = 1e9 + 7;
const int N = 2e5 + 2;
void get_next(string s, int m, int *ne)
{
ne[0] = -1;
if (m == 1)
return;
ne[1] = 0;
int i = 2, cn = 0;
while (i < m)
{
while (cn > 0 && s[i - 1] != s[cn])
cn = ne[cn];
if (s[i - 1] == s[cn])
ne[i++] = ++cn;
else
ne[i++] = 0;
}
return;
}
void get_nextval(string s, int m, int *ne, int *neval)
{
neval[0] = -1;
if (m == 1)
return;
int i = 0;
while (i < m)
{
if (s[i] == s[ne[i]])
neval[i] = neval[ne[i]]; // 跳过相同的匹配不上的字符
else
neval[i] = ne[i]; // 否则,与 next 一样
i++;
}
return;
}
void kmp(string s1, string s2)
{
int m = s1.size(), n = s2.size();
int *ne = new int[n];
cout << "kmp匹配:\n";
get_next(s2, n, ne);
cout << "打印next数组:" << endl;
for (int i = 0; i < n; i++)
cout << ne[i] << ' ';
cout << endl;
int i = 0, j = 0;
while (i < m && j < n)
{
while (j > 0 && s1[i] != s2[j])
j = ne[j]; // 得到新的下标(>=0)
if (s1[i] == s2[j])
i++, j++;
else
i++;
}
if (j == n)
cout << i - j << endl;
else
cout << -1 << endl;
}
void kmp_plus(string s1, string s2)
{
int m = s1.size(), n = s2.size();
int *ne = new int[n];
get_next(s2, n, ne);
int *neval = new int[n];
get_nextval(s2, s2.size(), ne, neval);
cout << "kmp_plus匹配\n";
cout << "打印nextval数组:" << endl;
for (int i = 0; i < n; i++)
cout << neval[i] << ' ';
cout << endl;
int i = 0, j = 0;
while (i < m && j < n)
{
// 如果 j == -1,说明 i 这个位置不可能匹配,所以 i++, j = 0(即j++)
// 如果 s1[i] == s2[j], 说明 i 这个位置匹配上了,所以 i++, j++
if (j == -1 || s1[i] == s2[j])
i++, j++;
else // 其他情况: j != -1 且匹配不上,则 j = neval[j]
j = neval[j];
}
if (j == n)
cout << i - j << endl;
else
cout << -1 << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
string s1, s2;
cin >> s1 >> s2;
kmp(s1, s2);
kmp_plus(s1, s2);
return 0;
}
// 输入
ababaaaaababaaab
ababaaab
// 输出
kmp匹配:
打印next数组:
-1 0 0 1 2 3 1 1
YES
kmp_plus匹配
打印nextval数组:
-1 0 -1 0 -1 3 1 0
YES
题目
模板KMP
题意
找出模式串在主串的每次出现的位置,并且求出模式串的next数组
思路
模板题,模拟KMP即可
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define pii pair<int, int>
#define all(x) x.begin(), x.end()
#define endl '\n'
const int MOD = 1e9 + 7;
const int N = 2e5 + 2;
void get_ne(string s, int ne[])
{
int m = s.size();
ne[0] = -1;
if (m == 1)
return;
ne[1] = 0;
int i = 2, cn = 0;
while (i <= m)
{
while (cn > 0 && s[i - 1] != s[cn])
cn = ne[cn];
if (s[i - 1] == s[cn])
ne[i++] = ++cn;
else
ne[i++] = 0;
}
return;
}
void get_neval(string s, int ne[], int neval[])
{
int m = s.size();
neval[0] = -1;
if (m == 1)
return;
int i = 1;
while (i < m)
{
if (s[i] == s[ne[i]])
neval[i] = neval[ne[i]];
else
neval[i] = ne[i];
i++;
}
return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
string s1, s2;
cin >> s1 >> s2;
int m = s1.size(), n = s2.size();
int *ne = new int[n + 2];
int *neval = new int[n + 2];
get_ne(s2, ne);
get_neval(s2, ne, neval);
int i = 0, j = 0;
while (i < m)
{
if (j == -1 || s1[i] == s2[j])
i++, j++;
else
j = neval[j];
if (j == n)
{
cout << i - j + 1 << endl; // 注意纯 endl 会超时
i = i - j + 1;
j = 0;
}
}
for (int i = 1; i <= n; i++)
cout << ne[i] << ' ';
return 0;
}
B. Password
题意
你需要找到既是 S 的前缀又是 S 的后缀同时又在 S中间出现过的最长子串
思路
求出S的next数组,并且用map记下每个next[i]的个数,分不需要跳跃和需要跳跃两种情况模拟
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define pii pair<int, int>
#define all(x) x.begin(), x.end()
#define endl '\n'
const int MOD = 1e9 + 7;
const int N = 2e5 + 2;
map<int, int> mp;
void get_ne(string s, int ne[])
{
int m = s.size();
ne[0] = -1;
mp[-1]++;
if (m == 1)
return;
ne[1] = 0;
mp[0]++;
int i = 2, cn = 0;
while (i <= m)
{
while (cn > 0 && s[i - 1] != s[cn])
cn = ne[cn];
if (s[i - 1] == s[cn])
ne[i] = ++cn;
else
ne[i] = 0;
mp[ne[i++]]++;
}
return;
}
void solve()
{
string s;
cin >> s;
int *ne = new int[s.size() + 2];
get_ne(s, ne);
string ss;
int len = ne[s.size()];
// for (int i = 0; i <= s.size(); i++)
// cout << ne[i] << ' ';
// cout << endl;
bool f = true;
while (len > 0)
{
if (f && mp[len] > 1)
{
ss = s.substr(0, len);
break;
}
else if (!f && mp[len] >= 1)
{
ss = s.substr(0, len);
break;
}
f = false;
len = ne[len];
}
cout << (len > 0 ? ss : "Just a legend");
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--)
{
solve();
}
return 0;
}
Power Strings
题意
求一个字符串最多由多少个重复的子串连接而成
思路
结合题面分析next数组,可以得到结论:循环节的长度 k = s.size() - ne[s.size()]
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define pii pair<int, int>
#define all(x) x.begin(), x.end()
#define endl '\n'
const int MOD = 1e9 + 7;
const int N = 2e5 + 2;
void get_next(string s, int m, int *ne)
{
ne[0] = -1;
if (m == 1)
return;
ne[1] = 0;
int i = 2, cn = 0;
while (i <= m)
{
while (cn > 0 && s[i - 1] != s[cn])
cn = ne[cn];
if (s[i - 1] == s[cn])
ne[i++] = ++cn;
else
ne[i++] = 0;
}
return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
string s;
while (cin >> s, s != ".")
{
int m = s.size();
int *ne = new int[m + 2];
get_next(s, m, ne);
int cnt = m - ne[m];
if (m % cnt)
cout << 1 << endl;
else
cout << m / cnt << endl;
}
return 0;
}
P4391
题意
给你一个字符串,它是由某个字符串不断自我连接形成的。但是这个字符串是不确定的,现在只想知道它的最短长度是多少。
思路
结合题面分析next数组,可以得到结论:循环节的长度 k = s.size() - ne[s.size()]
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int ll
#define pii pair<int, int>
#define all(x) x.begin(), x.end()
#define endl '\n'
const int MOD = 1e9 + 7;
const int N = 2e5 + 2;
void get_next(string s, int m, int *ne)
{
ne[0] = -1;
if (m == 1)
return;
ne[1] = 0;
int i = 2, cn = 0;
while (i <= m)
{
while (cn > 0 && s[i - 1] != s[cn])
cn = ne[cn];
if (s[i - 1] == s[cn])
ne[i++] = ++cn;
else
ne[i++] = 0;
}
return;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n;
string s;
cin >> n >> s;
int *ne = new int[n + 2];
get_next(s, s.size(), ne);
// for (int i = 0; i <= n; i++)
// cout << ne[i] << ' ';
// cout << endl;
int k = n - ne[n]; // 循环节的长度
cout << k << endl;
return 0;
} 