KMP算法详解附加例题

一、KMP

下标都从1开始

给定一个副串fu和一个主串zhu,求副串fu在主串zhu中出现的位置
1.取最长的相等前后缀,保证不漏解
2.通过模式串前后缀的自我匹配的长度,计算next函数,给j指针打一张表,
 失配时就跳到next[j]的位置继续匹配

1.创建nex[N]数组(时间复杂度o(lb))

char zhu[N]:代表主串  cin>>(zhu+1)

char fu[N]:代表副串  cin>>(fu+1)

int la:代表主串的长度  la=strlen(zhu+1)

int lb:代表副串的长度  lb=strlen(fu+1)

nex[i]:表示模式串p[1,i]中相等前后缀的最长长度

void getnex()//创建nex[N]数组,用fu串 
{
	int i,j;
	nex[1]=0;
	for(i=2,j=0;i<=lb;i++)
	{
		while(j&&fu[i]!=fu[j+1])
		{
			j=nex[j];
		}
		if(fu[i]==fu[j+1])
		    j++;
		nex[i]=j;//回跳 
	}
}

ps:

无论增加/减少字符有规律(i指针不回退,j指针来回跑) 
1.利用双指针,i扫描副串,j扫描前缀
2.nex[1]=0(因为只有一个字符) ,i=2,j=0(初始化) 
3.如果fu[i]!=fu[j+1],让j回跳(退而求其次)到能匹配的位置,如果找不到能匹配的位置,j回跳0
4. fu[i]==fu[j+1] ,让j++,指向匹配前缀的末尾
5.nex[i]=j; 

2.副串与主串匹配( 时间复杂度o(la))

void kmp()
{
	int i,j;
	for(i=1,j=0;i<=la;i++)
	{
		while(j&&zhu[i]!=fu[j+1])
		{
			j=nex[j];
		}
		if(zhu[i]==fu[j+1])
		    j++;
		if(j==lb)
		{
			cout<<i-lb+1<<endl;
		}
	}
}

ps:

1.利用双指针,i扫描主串,j扫描副串 
2.nex[1]=0(因为只有一个字符) i=1,j=0(初始化) 
3.如果s[i]!=p[j+1],让j回跳(退而求其次)到能匹配的位置,如果找不到能匹配的位置,j回跳0
4. s[i]==p[j+1] ,让j++

3.模拟nex[N]

例如:aabaabaaaa
nex[1]=0;  a
nex[2]=1;  aa   (a)     1
nex[3]=0; aab             2
nex[4]=1  aaba   (a)      3
nex[5]=2  aabaa   (aa)     4
nex[6]=3  aabaab  (aab)     5
nex[7]=4  aabaaba (aaba)    6
nex[8]=5  aabaabaa (aabaa)  7
nex[9]=2  aa|baaba|aa (aa)      8(n-1);

4.回跳

aab|aabaa|aa
aabaa|baaaa
最初:
nex[2]=1;
nex[3]=0;
nex[4]=1;
nex[5]=2;
nex[6]=3;
nex[7]=4;
i=8 j=5 nex[8]=5;
回跳
i=9,j=2
i=9,j=1 
nex[9]=2; 

二、相关例题

P3375 【模板】KMP

一、题目要求

题目描述

给出两个字符串 s1​ 和 s2​,若s1​ 的区间 [l,r] 子串与s2​ 完全相同,则称 s2​ 在 s1​ 中出现了,其出现位置为 l。
现在请你求出 s2​ 在 s1​ 中所有出现的位置。

定义一个字符串 s 的 border 为 s 的一个非 s 本身的子串 t,满足 t 既是 s 的前缀,又是 s 的后缀。
对于 s2​,你还需要求出对于其每个前缀 ′s′ 的最长 border ′t′ 的长度。

输入格式

第一行为一个字符串,即为 s1​。
第二行为一个字符串,即为 s2​。

输出格式

首先输出若干行,每行一个整数,按从小到大的顺序输出 s2​ 在 s1​ 中出现的位置。
最后一行输出 ∣s2​∣ 个整数,第 i 个整数表示s2​ 的长度为 i 的前缀的最长 border 长度。

输入输出样例

输入 

ABABABC
ABA

输出 #

1
3
0 0 1 

说明/提示

样例 1 解释

对于 s2​ 长度为 3 的前缀 ABA,字符串 A 既是其后缀也是其前缀,且是最长的,因此最长 border 长度为 1。

数据规模与约定

本题采用多测试点捆绑测试,共有 3 个子任务

  • Subtask 1(30 points):∣�1∣≤15∣s1​∣≤15,∣�2∣≤5∣s2​∣≤5。
  • Subtask 2(40 points):∣�1∣≤104∣s1​∣≤104,∣�2∣≤102∣s2​∣≤102。
  • Subtask 3(30 points):无特殊约定。

对于全部的测试点,保证 1≤∣s1​∣,∣s2​∣≤10^6,s1​,s2​ 中均只含大写英文字母。

二、思路

可以直接套用模板,最后输出nex[N]数组

三、代码

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e6+10;
const int inf=0x3f3f3f3f;
int la,lb;
char zhu[N],fu[N];
int nex[N];
void getnex()//创建nex[N]数组,用fu串 
{
	int i,j;
	nex[1]=0;
	for(i=2,j=0;i<=lb;i++)
	{
		while(j&&fu[i]!=fu[j+1])
		{
			j=nex[j];
		}
		if(fu[i]==fu[j+1])
		    j++;
		nex[i]=j;//回跳 
	}
}
void kmp()
{
	int i,j;
	for(i=1,j=0;i<=la;i++)
	{
		while(j&&zhu[i]!=fu[j+1])
		{
			j=nex[j];
		}
		if(zhu[i]==fu[j+1])
		    j++;
		if(j==lb)
		{
			cout<<i-lb+1<<endl;
		}
	}
}
void solve()
{
	cin>>(zhu+1);
	cin>>(fu+1);
	la=strlen(zhu+1);
	lb=strlen(fu+1);
	getnex();
	kmp();
	for(int i=1;i<=lb;i++)
	{
		cout<<nex[i]<<' ';
	}
	cout<<endl;
}
signed main()
{
    int t=1;
    while(t--)
    {
       solve();
    }
    return 0;
}

 P4391 [BOI2009] Radio Transmission 无线传输

一、题目要求

题目描述

给你一个字符串 s1​,它是由某个字符串 s2​ 不断自我连接形成的(保证至少重复 2 次)。但是字符串 s2​ 是不确定的,现在只想知道它的最短长度是多少。

输入格式

第一行一个整数 L,表示给出字符串的长度。

第二行给出字符串 s1​ 的一个子串,全由小写字母组成。

输出格式

仅一行,表示 s2​ 的最短长度。

输入输出样例

输入 

8
cabcabca

输出 

3

说明/提示

样例输入输出 1 解释

对于样例,我们可以利用 abcabc 不断自我连接得到abcabcabcabc,读入的 cabcabca,是它的子串。

规模与约定

对于全部的测试点,保证 1<L≤10^6。

二、思路

假设原串是s0,s0经过大于或者等于两次的自我连接得到s,现在需要我们求出原串s0的长度

可以先求出,s串的nex数组,则可以求出[1,strlen(s+1)]中相等前后缀的长度

则原串的长度为n-nex[n](最小周期);

三、代码

#include<bits/stdc++.h>
#define endl '\n'
#define int long long
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
using namespace std;
const int N=1e6+10;
const int inf=0x3f3f3f3f;
int n;
char fu[N];
int nex[N];//表示fu串[1,i]中相等前后缀的最长长度
void solve()
{
	cin>>n;
	int i,j;
	cin>>(fu+1);
	int lb=strlen(fu+1);
	for(i=2,j=0;i<=lb;i++)
	{
		while(j&&fu[i]!=fu[j+1])
		    j=nex[j];
		if(fu[i]==fu[j+1])
		    j++;
		nex[i]=j;
	}
    cout<<n-nex[lb]<<endl;
}
signed main()
{
    int t=1;
    while(t--)
    {
       solve();
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值