KMP模板(C++)

例题

字符串匹配
Description
给出两个字符串S和T,请判断T是否为S的子串。

Input
第一行是一个整数N,说明有多少个测试用例。

接下来是N个测试用例,每个测试用例占2行:第一行是字符串S,第二行是字符串T,字符串中不含空格。 1 ≤ strlen(S) , strlen(T) ≤ 10000

Output
对每个测试用例,输出一行结果:是否子串,是则输出"yes" ,否则输出 “no”

Sample Input

2
aabcdd
abc
aaaaaaaaaaaaa
aaaaaab

Sample Output

yes
no

直接朴素暴力查找:O(n^2)
这个代码就不给了

当使用KMP后明显就会快很多~。

KMP算法的详细介绍

对了,关于KMP算法的详细介绍在这里,灰常灰常详细,一定可以看懂的
KMP领取大门在这快去

next数组:

为什么next[0]=-1&next数组含义?

你必须明确next数组的含义:next[j]记录的是第j-1个字符前缀(从首字符开始到当前的上一个字符(j-1)的长度)与后缀 (不包括首字符,第二个字符到当(j-1)字符的长度)的公共元素的最大长度,或者说next[j]是位置j的最长公共前缀后缀的长度。

真前缀当然是不包括本身元素的,当我们只有一个元素是他并没有前缀和后缀,如果你理解为错误理解next数组(是模式串需要移动到的位置,虽然这样做是没问题的,但并不是next的真正意义 ),你在想为什么不能是next[0]=0;这样是错误的,会导致后面的next数值出错,因为长度为0的字符串并没有前缀后缀。
你可以想象成在首字符的前端-1的位置加一个哨兵(就是与所有字符都通配的字符,就是首字符都失配,就是要让模式串往后移一位),故在KMP比较时会有
if(j==-1||s[i]==p[j])
{
j++;
i++;
}

直接理解next[j]的数值表示的就是(j-1)的前后缀的最长公共长度,假如失配与位置j,下次开始匹配的位置,也就是已匹配的字符串(next[j]长度的字符串)下次不用再进行一一匹配了(因为他们是前后缀共有的)。
示意草图:
子串:AB……ABC…… [下标从0开始]
假如子串(匹配串)与主串(模式串)失配于C,next[c的下标号]=2。你看看截止于C的子串的前后缀共有的子串是AB,我们主串失配于C,说明主串有至少是有“
AB……AB[这里不是C]…… ”,KMP匹配是不会回退主串的,即此时主串的匹配索引(就是下面KMP匹配代码的i)不用变,而是移动子串的索引至2的位置,因为前后缀是相同的(AB)就没必要继续比了,这样就实现了主串索引不回退,这也就是KMP时间复杂度为线性的原因

代码模板

void getnext(char *p)
{
	int lenp=strlen(p);
	nextt[0]=-1;
	int k=-1;
	int j=0;
	while(j<lenp-1)
	{
		//p[k]表示前缀,p[j]表示后缀(并没有“真”!)
		if(k==-1||p[j]==p[k])//j在这
		{
			j++;//j+1在这
			k++;//k=k+1
			//---->若p[k]=p[j],则next[j+1]=next[j]+1; 
			//下一个位置的next 
			if(p[j]!=p[k])//当p[k]!=p[j]时,与主串不匹配时可以返回到这
			{
				nextt[j]=k;
			}
			else//当p[j]=p[k]时与主串匹配时你在j位置不匹配匹配串返回这里当前
		    	//还是 不能与主串匹配,然后还是要返回next[]即next[next[k]] 
		    	nextt[j]=nextt[k];//优化了 
		}
		else
		{
			k=nextt[k];
		}
	}
	return;
}

KMP匹配:

int KMP(char *s,char *p)
{
	int i=0;
	int j=0;
	int lens=strlen(s);
	int lenp=strlen(p);
	while(i<lens&&j<lenp)
	{
		//如果j=-1(表示第一次开始或者重新开始匹配),即失配于首字符
		if(j==-1||s[i]==p[j])
		{
			j++;
			i++;
		}
		else
		{
			//如果j!+-1,或者当前匹配失败 ,则 
			j=nextt[j]; // i不变,j=next[j] 
		}
	}
	if(j==lenp)
	return 1;//匹配成功 
	else
	return 0; 
}

以下是问题的输入:

int main(void)
{
	int t;
	char a[10005],b[10005];
	scanf("%d",&t);
	while(t--)
	{
		scanf("%s",a);
		scanf("%s",b);
		if(strlen(a)>=strlen(b))
		{
			getnext(b);
			if(KMP(a,b)==1)
			printf("yes\n");
			else
			printf("no\n");
		}
		else
		{
			getnext(a);
			//getnext(b);
			if(KMP(a,b)==1)
			printf("yes\n");
			else
			printf("no\n");
		}
	}
	return 0;
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值