KMP算法是一种可以在 O ( n + m ) O(n+m) O(n+m) 的时间复杂度内实现两个字符串匹配的算法。
AcWing 831. KMP字符串
给定一个模式串
S
S
S,以及一个模板串
P
P
P,所有字符串中只包含大小写英文字母以及阿拉伯数字。模板串
P
P
P在模式串
S
S
S中多次作为子串出现。
求出模板串
P
P
P在模式串
S
S
S中所有出现的位置的起始下标。
输入格式
第一行输入整数
N
N
N,表示字符串
P
P
P的长度。
第二行输入字符串
P
P
P。
第三行输入整数
M
M
M,表示字符串
S
S
S的长度。
第四行输入字符串
S
S
S。
输出格式
共一行,输出所有出现位置的起始下标(下标从
0
0
0开始计数),整数之间用空格隔开。
(
1
≤
N
≤
1
e
5
,
1
≤
M
≤
1
e
6
)
(1≤N≤1e5,1≤M≤1e6)
(1≤N≤1e5,1≤M≤1e6)
const int maxn = 1e6+5;
int n,m,ne[maxn];
char s[maxn],p[maxn];
int main()
{
cin>>n; cin>>p+1; //从1开始存储字符串,也可以从0开始,不过会有一些小变化,下面会有从0开始的代码
cin>>m; cin>>s+1;
//求next数组(此处不是很懂 (* ̄︿ ̄) ,upt:2022.0.22 已经懂了)
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;
}
// KMP匹配
for(int i=1,j=0;i<=m;i++)
{
while(j&&s[i]!=p[j+1]) j=ne[j];//如果p[j]后面的字符与s[i]不匹配,字符串p就要向后移动
if(s[i]==p[j+1]) j++;//匹配后,j++,继续下一个
if(j==n)//j == n说明匹配成功
{
cout<<i-n<<" ";
j=ne[j];//因为有可能多次匹配,所以匹配还要继续
}
}
return 0;
}
const int maxn = 1e6+5;
int n,m,ne[maxn];
string s,p;
int main()
{
cin>>n; cin>>p; //下标从0开始
cin>>m; cin>>s;
ne[0]=-1; //0处的next指针指向-1,表示空
//求next数组
for(int i=1,j=-1;i<n;i++)
{
while(~j&&p[i]!=p[j+1]) j=ne[j];//判断条件为 j!=-1 (即~j)
if(p[i]==p[j+1]) j++;
ne[i]=j;
}
// KMP匹配
for(int i=0,j=-1;i<m;i++)
{
while(~j&&s[i]!=p[j+1]) j=ne[j];
if(s[i]==p[j+1]) j++;
if(j==n-1)
{
cout<<i-n+1<<" ";
j=ne[j];
}
}
return 0;
}
upt:2022.9.22 KMP自动机
KMP自动机:构造一个 自动机(一个有限状态机):其状态为当前的前缀函数值,而从一个状态到另一个状态的转移则由下一个字符确定。
(KMP自动机可以理解为是一个单串的AC自动机)
具体定义为 a u t [ i ] [ c ] aut[i][c] aut[i][c]:当前在 K M P KMP KMP中的状态是 i i i,往后多匹配一个字符 c c c后到达的状态。
KMP自动机的预处理时间复杂度为 O ( ∣ ∑ ∣ n ) O(|\sum|n) O(∣∑∣n),其中 ∣ ∑ ∣ |\sum| ∣∑∣为字符集大小。
KMP自动机构造模版:
cin >> s + 1;
n = strlen(s + 1);
aut[0][s[1] - 'a'] = 1;
for (int i = 1; i < n; i ++) {
if (i > 1) ne[i] = aut[ne[i - 1]][s[i] - 'a'];
for (int c = 0; c < 26; c ++) {
if ('a' + c != s[i + 1])
aut[i][c] = aut[ne[i]][c];
else
aut[i][c] = i + 1;
}
}
例题
codeforce Prefix Function Queries
link
题目大意:
对于给定字符串
s
(
∣
s
∣
≤
1
e
6
)
s(|s| \le 1e6)
s(∣s∣≤1e6)。
有
q
q
q个询问,每次询问给定一个字符串
t
(
∣
t
∣
≤
10
)
t(|t|\le10)
t(∣t∣≤10),回答
s
s
s拼接
t
t
t后
t
t
t对应位置的前缀函数值,询问独立。
做法:
如果预处理出
s
s
s的
n
e
ne
ne数组,然后每次询问暴力求拼接上
t
t
t后的
t
t
t对应位置的
n
e
ne
ne数组,这样最坏的情况下,每次询问的时间复杂度都是
O
(
n
)
O(n)
O(n)的。
但是使用
K
M
P
KMP
KMP自动机的话,每次询问,求新增位置的
n
e
ne
ne数组的时间复杂度是稳定在
O
(
26
∣
t
∣
)
O(26|t|)
O(26∣t∣)的。
于是总时间复杂度为
O
(
26
∗
∣
t
∣
∗
q
)
O(26*|t|*q)
O(26∗∣t∣∗q)。
code:
#include <bits/stdc++.h>
#define endl '\n'
#define INF 0x3f3f3f3f
#define all(x) begin(x),end(x)
#define debug(x) cout<<#x<<": "<<x<<endl;
using namespace std;
using ll = long long;
const int N = 1e6 + 30;
int n, m, ne[N], aut[N][26];
char s[N], t[15];
int main() {
cin.tie(0);
ios::sync_with_stdio(false);
cin >> s + 1;
n = strlen(s + 1);
aut[0][s[1] - 'a'] = 1;
for (int i = 1; i < n; i ++) {
if (i > 1) ne[i] = aut[ne[i - 1]][s[i] - 'a'];
for (int c = 0; c < 26; c ++) {
if ('a' + c != s[i + 1])
aut[i][c] = aut[ne[i]][c];
else
aut[i][c] = i + 1;
}
}
int q; cin >> q;
while (q --) {
cin >> s + n + 1;
m = strlen(s + n + 1);
for (int i = n; i <= n + m; i ++) {
if (i > 1) ne[i] = aut[ne[i - 1]][s[i] - 'a'];
for (int c = 0; c < 26; c ++) {
if ('a' + c != s[i + 1])
aut[i][c] = aut[ne[i]][c];
else
aut[i][c] = i + 1;
}
if (i != n) cout << ne[i] << ' ';
}
cout << endl;
}
return 0;
}