KMP算法

K M P KMP KMP算法


K M P KMP KMP字符串匹配([题目][1])

内容:给出两个字符串 s 1 、 s 2 s_1、s_2 s1s2,求出 s 1 s_1 s1 s 2 s_2 s2中出现的位置

做法:

1.先举个例子,设 s 1 = " A B B A B B A B A B A A " s 2 = " A B B A B B A B A B A A A B A B A " s_1="ABBABBABABAA"s_2="ABBABBABABAAABABA" s1="ABBABBABABAA"s2="ABBABBABABAAABABA"
2. 那么一个最朴素的算法就诞生了在这里插入图片描述
s 2 s_2 s2 s 1 s_1 s1一位一位进行对比,但我们发现这个时间复杂度为 O ( n m ) O(nm) O(nm)
3. 那该如何优化它呢,我们发现对于一次匹配后,下一次可能出现的位置一定是要前几位相同的,那我们假设当前匹配到了第 6 6 6位,也就是说前 6 6 6位是相同的,那下一个会可以匹配的可能会出现在哪里呢?
4.其实就是要寻找最长相同前后缀,对于 A B B A B B ABBABB ABBABB来说(假设匹配成功),那么它的下一个可以成功匹配的可能是在第 4 4 4 A A A开始(如图在这里插入图片描述
),就可以跳过中间无用的匹配
5.如何证明其中没有跳过可能成功匹配的子串呢?在这里插入图片描述
我们假设 A B C ABC ABC为最长相同前后缀,那么如果中间还有一个串 A B AB AB A B C ABC ABC的头能成功匹配,假设 . . . = C + . . . ...=C+... ...=C+...,那很明显最长前后缀为 A B C . . . A B C ABC...ABC ABC...ABC,矛盾,舍,若 . . . ≠ C + . . . ...\not=C+... ...=C+...,明显 A B AB AB处不能匹配,所以下一个可能匹配成功的位置一定在最长相同前后缀的后缀的开始处。(如果没有,那我们就只好往下暴力找了)
6.那该如何寻找最长相同前后缀呢?其实我们发现找最长相同前后缀是不需要 s 2 s_2 s2,因为要跟 s 1 s_1 s1匹配,则是 s 1 s_1 s1在移动,就相当于是要在 s 1 s_1 s1中,找到下一个匹配的位置。我们设 k m p [ i ] kmp[i] kmp[i]表示当前 i i i位匹配成功,第 i + 1 i+1 i+1位匹配失败,下一位从 s 1 [ k m p [ i ] + 1 ] s1[kmp[i]+1] s1[kmp[i]+1]开始找,也可以理解为 k m p [ i ] kmp[i] kmp[i]是以 s 1 [ i ] s1[i] s1[i]开头,以 s 1 [ i ] s1[i] s1[i]结尾的最长公共前后缀的长度!在这里插入图片描述

7.求 k m p kmp kmp的过程,我们设一个 k k k,表示当前匹配到第 k k k位,让后打一个 1 1 1~ s 1. l e n g t h ( ) − 1 s1.length()-1 s1.length()1的循环,对于当前位的下一位,若是可以匹配,那就 k + + , k m p [ i + 1 ] = k k++,kmp[i+1]=k k++,kmp[i+1]=k,否则就根据不匹配的情况回到 k m p [ k ] kmp[k] kmp[k]重新比较,直到 k = 0 k=0 k=0或者 s 1 [ i + 1 ] = = b [ k + 1 ] s1[i+1]==b[k+1] s1[i+1]==b[k+1],若是 k = 0 且 s 1 [ i + 1 ] ! = b [ k + 1 ] k=0且s1[i+1]!=b[k+1] k=0s1[i+1]!=b[k+1] k m p [ i + 1 ] = 0 kmp[i+1]=0 kmp[i+1]=0
8.最后在按照匹配方法把 s 2 s_2 s2跑一遍,若是匹配到了 s 1 s_1 s1的最后一位,那就输出位置,且将 k = k m p [ k ] k=kmp[k] k=kmp[k](回去),时间复杂度为 O ( n + m ) O(n+m) O(n+m)

代码实现

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

void read(int &sum)
{
	sum=0;char last='w',ch=getchar();
	while (ch<'0' || ch>'9') last=ch,ch=getchar();
	while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
	if (last=='-') sum=-sum;
}
char s1[1000001],s2[1000001];
int kmp[1000001],k;
int lena,lenb;
int main()
{
//	freopen("M.in","r",stdin);
//	freopen("M.out","w",stdout);
	scanf("%s",s2+1),lenb=strlen(s2+1);
	scanf("%s",s1+1),lena=strlen(s1+1);
	k=0;
	for (int i=1;i<lena;i++)
	{
		while (k!=0 && s1[i+1]!=s1[k+1]) k=kmp[k];
		if (s1[i+1]==s1[k+1]) kmp[i+1]=k+1,k++;	
	}
	k=0;
	for (int i=0;i<lenb;i++)
	{
		while (k!=0 && s2[i+1]!=s1[k+1]) k=kmp[k];
		if (s2[i+1]==s1[k+1]) k++;
		if (k==lena) printf("%d\n",i-lena+2),k=kmp[k];
	}
	for (int i=1;i<=lena;i++) printf("%d ",kmp[i]);
//	fclose(stdin);fclose(stdout);
	return 0;
}



扩展 K M P KMP KMP([题目][6])

内容:给出字符串 s 1 、 s 2 s_1、s_2 s1s2,求出每个 s 1 [ i . . . s 1 . l e n g t h ] s_1[i...s_1.length] s1[i...s1.length] s 2 s_2 s2的最长公共前缀

做法:

  1. 根据 K M P KMP KMP字符串匹配的思想,我们设 k m p [ i ] kmp[i] kmp[i]表示以 i i i为首和开头以为首的最长公共前缀,我们知道可以先 s 2 s_2 s2自己与自己匹配一遍
  2. 我们先定义一个 k k k表示最近一位匹配到哪了,跟 K M P KMP KMP字符串匹配类似,设 p = k + k m p [ k ] − 1 p=k+kmp[k]-1 p=k+kmp[k]1 L = k m p [ i − k + 1 ] L=kmp[i-k+1] L=kmp[ik+1]于是就有了(动图在这里插入图片描述
    (相同颜色代表相同区间)
  3. 这时候我们要分类讨论一下 i − k + L i-k+L ik+L p − k + 1 p-k+1 pk+1的大小了

1 ) . i − k + L < p − k + 1 1).i-k+L<p-k+1 1).ik+L<pk+1在这里插入图片描述
如图,明显的 ( i . . . p ) = ( i − k + 1... p − k + 1 ) (i...p)=(i-k+1...p-k+1) (i...p=(ik+1...pk+1) 1... L = i − k + 1... i − k + L 1...L=i-k+1...i-k+L 1...L=ik+1...ik+L,因为绿段是最长的了,所以 k m p [ i ] = L = k m p [ i − k + 1 ] kmp[i]=L=kmp[i-k+1] kmp[i]=L=kmp[ik+1]

2 ) . i − k + L > = p − k + 1 2).i-k+L>=p-k+1 2).ik+L>=pk+1在这里插入图片描述
如图,明显的最长为 i p ip ip但我们不是知道 i p ip ip后面是否有更长的,于是就要暴力枚举

  1. 最后处理与 s 2 s_2 s2的关系,与 K M P KMP KMP匹配类似,走一遍即可
  2. 注意开头几项要暴力,因为前面没有,还有 p − i + 1 p-i+1 pi+1有可能小于 0 0 0,要改为 0 0 0,防止下标越界

代码实现

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define int long long
using namespace std;
typedef long long ll;

void read(int &sum)
{
	sum=0;char last='w',ch=getchar();
	while (ch<'0' || ch>'9') last=ch,ch=getchar();
	while (ch>='0' && ch<='9') sum=sum*10+ch-'0',ch=getchar();
	if (last=='-') sum=-sum;
}
char s1[2*10000001],s2[2*10000001];
int kmp[2*10000001],ex[2*10000001],k;
int lena,lenb;
void EX_KMP()
{
	kmp[1]=lena;
	k=1;
	while (s1[k]==s1[k+1]) k++;k--;kmp[2]=k;
	k=2;
	for (int i=3;i<=lena;i++)
	{
		int p=k+kmp[k]-1,L=kmp[i-k+1];
		if (i-k+L<p-k+1) kmp[i]=L;
		else
		{
			int j=p-i+1;
			if (j<0) j=0;
			while (s1[i+j-1+1]==s1[j+1] && i+j-1+1<=lena) j++;
			kmp[i]=j;
			k=i;
		}
	}
	k=1;
	while (s1[k]==s2[k] && k<=lena && k<=lenb) k++;k--,ex[1]=k;
	k=1;
	for (int i=2;i<=lenb;i++)
	{
		int p=k+ex[k]-1,L=kmp[i-k+1];
		if (i-k+L<p-k+1) ex[i]=L;
		else
		{
			int j=p-i+1;
			if (j<0) j=0;
			while (s2[i+j-1+1]==s1[j+1] && i+j-1+1<=lenb && j+1<=lena) j++;
			ex[i]=j;
			k=i;
		}
	}
}
signed main()
{
//	freopen("M.in","r",stdin);
//	freopen("M.out","w",stdout);
	scanf("%s",s2+1),lenb=strlen(s2+1);
	scanf("%s",s1+1),lena=strlen(s1+1);
	EX_KMP();
	int ans=0;
    for (int i=1;i<=lena;i++) ans=ans^(i*(kmp[i]+1));
    printf("%lld\n",ans);
    ans=0;
    for (int i=1;i<=lenb;i++) ans=ans^(i*(ex[i]+1));
    printf("%lld\n",ans);
//	for (int i=1;i<=lenb;i++) printf("%d ",ex[i]);
//	fclose(stdin);fclose(stdout);
	return 0;
}


Tags:KMP 信息学

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值