KMP字符串知识点、KMP字符串匹配和Radio Transmission 无线传输

知识点

参考资料:
王道考研视频讲解,对KMP介绍的非常好
ACWING 视频讲解,评论的帮助也很大,记得看评论
阮一峰2013年日志/博客上介绍的KMP

KMP算法:快速模式匹配算法,对人检索字符行为的模拟(具体王道视频大约5分钟开始),主要是目标串的指针不发生回溯,只有模式串的指针会回溯【注解:以下介绍中模式串右移和回溯的意思其实相同,移动的位数=已匹配的字符数 - next数组对应值,回溯的话是指针j直接回溯到以next对应数值为下标的位置,但是接下来用来比较的位置是j+1的位置,代码以回溯实现】,大概步骤如下

在这里插入图片描述

在这里插入图片描述

KMP算法实现重点:确定每个位置的next数组,因为每次整个模式串移动位数 = 已匹配的字符数 - 对应位置的部分匹配值

有点抽象,看上面的图,如第二次匹配时,串的地址都从1开始,对于“abca”的前缀:a、ab、abc;后缀:bca、ca、a, 则对应的next数组值/对应位置(也就是最后一个a)的部分匹配值为1;所以对于模式串的移动位数为4-1=3,所以模式串向后移动3位,若回溯的话,则j为1,则下一次参加比较的位置是j+1的位置,即2的位置,即 ‘c’;【好绕啊啊啊!加油!】

next数组和对应位置部分匹配值意思相同,当j为最后一个不匹配字符的下标时,我们考虑的next数组的值是不匹配字符的前一个字符的next值,即下标为j-1的字母的值,即考虑[1, j-1]的前后缀问题
在这里插入图片描述

next数组详细介绍:
在这里插入图片描述

KMP最主要代码
注意事项:ne数组即next数组,字符集合p为模式串,n为其长度,字符集合s为模板串/目标串,m为其长度;两个字符集合都是从1开始。j除了作为模式串指针外,还统计匹配相同的字符串数字。

	cin>>n>>p+1>>m>>s+1; //从1开始储存
	// 第一个for循环为针对模式串p产生next数组,模板!记住!
	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;
	}
	//第二个for循环开始匹配
	//i指向目标串,j指向模板串
	for(int i=1,j=0;i<=m;i++)//所以i从1开始,j从0开始,真正参与与s[i]比较的是p[j+1]
	{
		while(j&&s[i]!=p[j+1])
		{
			j=ne[j];//不能匹配上,则j=ne[j];
		}
		if(s[i]==p[j+1]) j++;//能够匹配上,则向后移动
		if(j==n)
		{
			printf("%d ",i-n);//输出所有出现位置的起始下标,输出的下标从0开始 所以i-n,而不是i-n+1;
			j=ne[j]; 
		}
	}
//输出 -- 借助输出来理解会更好理解!
/*
5
abcac
10
ababcabcac
  1   2
  此时已匹配完的串为“ab” 所以j回到0,下一个p[j+1]为p[1],即b 
      此时已匹配完的串为“abca” 所以j回到1,下一个p[j+1]为p[2],即c 
*/
//若在第二次for输出cout<<s[i]<<" "<<p[j+1]<<" "<<ne[j]<<" "<<j<<endl; 的话
//结果:
/*
a a 0 0
b b 0 1
a c 0 2   不匹配,第一次j变化
b b 0 1   
c c 0 2
a a 0 3
b c 1 4   不匹配,第二次j变化
c c 0 2
a a 0 3
c c 1 4
*/

好的,知识点算是告一段落了,来看题目!

题目

先上基础题

AcWing 831. KMP字符串

原题链接
题意:求模式串在主串种出现的所有位置的起始下标(从0开始计数)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m;
char s[N],p[N];
int ne[N];
int main()
{
	cin>>n>>p+1>>m>>s+1; 
	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;
	}
	for(int i=1,j=0;i<=m;i++)
	{
		//cout<<s[i]<<" "<<p[j+1]<<" "<<ne[j]<<" "<<j<<endl;
		while(j&&s[i]!=p[j+1])
		{
			//cout<<j<<" "<<p[j+1]<<" ";
			j=ne[j];
			//cout<<j<<" "<<p[j+1]<<endl;
		}
		if(s[i]==p[j+1]) j++;
		if(j==n)
		{
			printf("%d ",i-n);
			j=ne[j];//输出所有出现位置的起始下标 
		}
	}
	//for(int i=2;i<=n;i++) cout<<ne[i]<<" ";
	return 0;
}

P3375 【模板】KMP字符串匹配

洛谷原题链接
题意:输入一个主串和一个模式串,按从小到大输出模式串在主串中出现的位置,并赛最后一样输出模式串的模个整数,第i个整数表示模式串中长度为i的前缀的最长border长度。所谓border,就是定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
题解:对于第一个问题直接用KMP,第二个问题其实是next数组,回忆一下,next数组的定义:模式串中,各个子串的“前缀”和“后缀”所共有元素中最长元素的长度。

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,m;
char s[N],p[N];
int ne[N];
int j;
int main()
{
	cin>>s+1;
	cin>>p+1;
	int ls=strlen(s+1),lp=strlen(p+1);
	for(int i=2;i<=lp;i++)
	{
		while(j&&p[i]!=p[j+1])
		{
			j=ne[j];
		}
		if(p[i]==p[j+1]) j++;
		ne[i]=j;
	}
	j=0;
	for(int i=1;i<=ls;i++)
	{
		while(j>0&&s[i]!=p[j+1])
		{
			j=ne[j];
		}
		if(s[i]==p[j+1]) j++;
		if(j==lp)
		{
			printf("%d\n",i-lp+1);
			//输出所有出现位置的起始下标 ,下标从1开始,其实就相当于上一题的i-n+1
			j=ne[j];
		}
	}
	for(int i=1;i<=lp;i++)
	{
		cout<<ne[i]<<" ";
	}
	return 0;
}

P4391 [BOI2009]Radio Transmission 无线传输

原题链接
在这里插入图片描述
题解:一道神奇的题目,参考这儿,神级题解,因为所给的字符串是S1的一个子串S,S1又是由S2不断拼接而成的,那么对于S1的子串S中必定有不断循环的S2构成的,现在要求S2的最短长度,即子串S-最大公共前后缀长度,即n-next[n];
【注释:以下是完整周期的分析,图中画的第一个线段表示前缀,第二个是原字符串,也是“红色段”+后缀,但是红色段由属于前缀,则由红色段=第一段=第二段…=所有段,则红色串就是最小的循环子串】
在这里插入图片描述
对于不是完整周期的分析如下:
【注解:对于下面这个例子,最大公共前后缀为“cabca”,从对完整周期红色段的分析转化为对周期内任意一个字母的分析,假设一个完整周期有n位,不完整周期有y=n-i位,其中第0个周期被切掉了i位,第0个周期最后一个字母=第一个周期最后一个字母=第二个周期最后一个字母=所有周期最后一个字母,则最大公共前后缀肯定比完整周期的最大公共前后缀少了i位,则n-next[n]=(n-i)-next[y]=y-next[y],此时的next[y]+i=next[n]
在这里插入图片描述
这道题其实感觉是思维题了,代码简单但是要发现规律可能需要点能力?但是这道题真的让人觉得next数组和KMP真的太太太强大了!!

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
char a[N];
int ne[N];
int n;
int ans;
int j;
int main()
{
	cin>>n>>a+1;
	for(int i=2;i<=n;i++)
	{
		while(j&&a[i]!=a[j+1])
		{
			j=ne[j];
		}
		if(a[i]==a[j+1]) j++;
		ne[i]=j;
	}
	ans=n-ne[n];
	//for(int i=1;i<=n;i++) cout<<ne[i]<<" ";
	cout<<ans<<endl;
}

总结

目前做的题目还不是难题,继续加油吧!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值