一、什么是KMP
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度 O ( ∣ s 1 ∣ + ∣ s 2 ∣ ) O(|s1|+|s2|) O(∣s1∣+∣s2∣)。(摘自百度百科)
二、做法
1.暴力出奇迹!
暴力的方法很简单,对于s1,我们枚举s1的每个位置,从这个位置开始往后查询|s2|个字符并和s2比较,看能不能匹配。时间复杂度 O ( ∣ s 1 ∣ ∣ s 2 ∣ ) O(|s1||s2|) O(∣s1∣∣s2∣)。
/*
*/
#include<bits/stdc++.h>
#define rep(i,s1,s2,s3) for(i = s1;i <= s2;i += s3)
#define r(i,s1,s2,s3) for(i = s1;i >= s2;i -= s3)
#define ull unsigned long long
#define sort stable_sort
#define INF 0x7f7f7f7f
#define ll long long
using namespace std;
string s1,s2;
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>s1>>s2;
int i,j;
bool flag;
rep(i,0,s1.size() - s2.size() - 1,1){
flag = true;
rep(j,0,s2.size() - 1,1) if(s1[i + j] != s2[j]){
flag = false;
break;
}
if(flag) cout<<i + 1<<' ';
}
return 0;
}
很显然,暴力的复杂度太大, 1 0 5 10^5 105就过不了了。
2.滑动窗口+字符串哈希
不难想到,我们可以用滑动窗口,窗口肯定长是|s2|。虽然滑动窗口的复杂度是
O
(
∣
s
1
∣
)
O(|s1|)
O(∣s1∣),但是比较的复杂度是
O
(
∣
s
2
∣
)
O(|s2|)
O(∣s2∣)!
我们可以用字符串哈希,但是有个缺点,字符串哈希特难删除,如果不取余,|s2|连三位数貌似都不能达到!
3.正解——KMP
我们重新回看最暴力的算法。我们前面分析耗时的原因是匹配上时间太久了,单调队列针对的也是这一点。但是现在我们也可以反过来想:枚举每一个起点太过耗时,我们要加快枚举的速度。
例如:
文本串:abacdabaad
模式串:aba
当i=2,4,5,7,10时,我们发现这从开头第一个字母就对比不上,那么如果这个还花
O
(
∣
s
2
∣
)
O(|s2|)
O(∣s2∣)的复杂度来比较,太浪费了。 对于失去匹配的位置,不再直接从后一位开始比较,而是从下一个相等的位置进行比较,这样就优化了比较时间。
现在,如果速度还想更快,就要尽量使移动的位置拥有和模式串匹配的最大前缀。这个就是KMP的思路。简单来说就是下一位失配了迅速跳到和模式串有最大匹配前缀的地方。
三、练习
洛谷P3375
这就是模板题,直接上代码——
/*
*/
#include<bits/stdc++.h>
#define rep(i,s1,s2,s3) for(i = s1;i <= s2;i += s3)
#define r(i,s1,s2,s3) for(i = s1;i >= s2;i -= s3)
#define ull unsigned long long
#define sort stable_sort
#define INF 0x7f7f7f7f
#define ll long long
using namespace std;
string s,str;
int next_[1000010];
int main(){
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin>>str>>s;
str = '#' + str;
s = '#' + s;
int i = 2,j = 0;
while(i < s.size()){
while(j && s[i] != s[j + 1]) j = next_[j];
j += (s[i] == s[j + 1]);
next_[i] = j;
i++;
}
i = 1,j = 0;
while(i < str.size()){
while(j && str[i] != s[j + 1]) j = next_[j];
j += (str[i] == s[j + 1]);
if(j == s.size() - 1){
cout<<i - s.size() + 2<<'\n';
j = next_[j];
}
i++;
}
rep(i,1,s.size() - 1,1) cout<<next_[i]<<' ';
return 0;
}
剩下一些题我以后再补~~(其实我也没做啥KMP的题目)~~