【模板】KMP字符串匹配

模板题:洛谷P3375

暴力的复杂度O(mn)
KMP复杂度O(n)
Part 1: 匹配的思路优化:
txt表示长串,pat表示短串,目标是在长串中找出短串的所在位置
记: i 为txt下标, j 为pat下标, 均表示当前待判断的位置, 字符串下标从0开始。

If ( txt[i]==pat[j] ){//如果匹配
	i++,j++;//就下一位置
}
else{//如果不匹配
	j=k;//令j=k
	//其中, k符合:pat[0,k-1]==txt[i-k,i-1]
	//即pat的长度为k-1的前缀 与 txt从i-1位置再往前推k-1个前缀 一致
	//注:如果没有相等前缀,k=-1;
	i++;
}

#为什么这样的思路匹配,最“聪明”,具体解释和动态效果图点击这里 [这篇知乎解释得很好]

那么,这个方法巧妙处在于令j=k,难点也在于如何快速找到这样的k?
“nex数组” 帮助我们进行这一步的操作👇

Part 2:nex数组 含义与构造
#nex数组处理部分推荐食用这里 [图文结合很不错]
**目标:**令nex[j]表示 j点处失配时应该跳到的k值
根据nex数组需要达成的作用 先思考的两件事:

  1. 如果要符合pat[0,k-1]==txt[i-k,i-1],一定符合pat[k-1]==txt[i-1],
    也就是说,如果适配位置的前一个字母为“A”,那一定至少要跳到pat串中上一个为"A"的位置;如此一直向前跳,直到符合条件。
  2. 由此推出,符合条件的nex[j]==k一定符合:pat[0,k-1]==pat[j-k,j-1]

根据这样的理解
=>nex数组的初始化处理一定有一个状态转移:

if(pat[k]==pat[j]) nex[j+1]=nex[j]+1;

这样转移的原因就是,这样可以保证符合上面推出的第二条
证明:
∵k=nex[j] 即 pat[0,k-1]==pat[j-k,j-1]
∴ if(pat[k]==pat[j]) 则pat[0,k]==pat[j-k,j]
∴nex[j+1]=k+1=nex[j]+1;

⇒ nex数组的完整状态转移方程:

int j=0,nex[0]=k=-1;//j表示从pat的0点开始判断
//根据Part1讲述,如果没有符合条件的k,则令j=-1,即k的初始值是-1
while(j<pat.length()-1){
	if(k==-1||p[j]==p[k]) nex[++j]=++k;//匹配,下一位
	else k=nex[k];//如果不等,k往前跳(符合上面推出的第一条)
}

注:写成nex[++j]=++k 而不能写nex[j+1]=nex[j]+1的原因是,如果k是-1进入这句,那就要从0开始。

Part 3:KMP实现
根据上述思路,KMP分为两个核心部分

  1. nex数组的构造
  2. 与主串的匹配
    PS1. 其实完整地来看,很像pat自己对着自己做了一边KMP,然后再对txt做一遍KMP ⇒ 也许这样想可以方便记忆233
    PS2. 根据前文对于nex性质的推演,归纳一下nex数组的本质: nex[i]表示pat串[0,i-1]的部分,前缀==后缀的最长长度

KMP模板代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N=1e6+5;
char txt[N],pat[N];
int nex[N];

int main(){
	cin>>txt>>pat;
	int len1=strlen(txt),len2=strlen(pat);
	//nex数组	
	int j=0,k;nex[0]=k=-1;
	while(j<len2){
		if(k==-1||pat[j]==pat[k]) nex[++j]=++k;
		else k=nex[k];
	}
	//kmp
	int i=0;j=0;
	while(i<len1){
		if(j==-1||txt[i]==pat[j]) i++,j++;
		else j=nex[j];
		if(j>=len2){cout<<i-len2+1<<endl;j=nex[j];}
	}
	for(int i=1;i<=len2;i++)
		cout<<nex[i]<<" ";
	return 0;
}

//ppss: 考研专业课大纲里竟然看到有KMP,震惊

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

GoesM

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值