KMP算法

一、什么是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的题目)~~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值