KMP字符串匹配

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">KMP字符串匹配算法是经典的字符串匹配算法,具有O(n)的时间复杂度,最早由Knuth,Morris及Pratt三人提出,而KMP算法本身并不是特别容易理解,在浏览了一些介绍KMP的经典文章(</span><a target=_blank href="http://www.matrix67.com/blog/archives/115" style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">http://www.matrix67.com/blog/archives/115</a><span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">)后,搞懂了KMP的工作原理,现总结如下:</span>

对于两个字符串a和b:

a: 12121232 

b: 12123 

可以看出在前4项匹配之后,第5项两个串并不匹配,这是如果是最直接的字符串匹配算法,姑且称它为native算法,就会直接放弃当前的匹配,然后从a[2]开始,重新开始和b[1]进行匹配检测。但其实这样就浪费了我们之前匹配4项后,所蕴含的信息。之前a[4]和b[4]匹配成功,假设我们从a[2]开始可以找到一个匹配,那么现在a[4]和b[3]也应该匹配成功,那么也就说b[3]==b[4],b[3]需要等于2,同理可知,如果从a[3]或a[4]开始找到一个匹配,那么b[2]或b[1]需要等于2。而实际上b串只有b[2]==2,也就是说只有可能从a[3]开始找到一个匹配。所以,native算法对于从a[2]和a[4]开始寻找匹配的尝试,都是徒劳,只是浪费时间而已。

因此我们在第五项匹配失败时,从b[4]开始往前找与b[4]相同的字符,我们找到b[2],然后用b[2]和a[4]匹配

a: 12121232

b:     12123

我们发现b[3]可以与a[5]匹配,我们进一步向后检测,发现当前得到一个匹配。那么是否每次出现不匹配时,都往前找与当前最后匹配字符相同的字符就可以了呢?实际上不然,要找到的这个字符,必须满足以下性质,假设我们找到的字符为b[j],当前最后匹配位置为b[t],那么b[1]b[2]...b[j-1]b[j] == b[t-j+1]b[t-j+2]...b[t-1]b[t]。也就是说:这个j要使得b串的钱j个字符等于b串的后j个字符,这其实不难理解,当不匹配在b[t+1]发生时,我们要将b串的前j位移到之前b串后j位的位置来进行匹配检测。例如:

a: 12121212

b: 121213

当b[5]出现不匹配时,我们找到的新的位置是b[3],而b[1]~b[3]等于b[3]到b[5],我们下一步就要把b[1]~b[3]移动到之前b[3]到b[5]的位置来进行进一步的检测:

a: 12121212

b:     121213

我们注意到其实j==1也是可以的,即b[1]~b[1]等于b[5]~b[5], 但我们的原则是选择符合条件的最长串,例如:当j==3时,有可能出现匹配,如果我们直接选择j==1,那么就把b[1]移动到之前b[5]的位置,如下:

a: 12121212

b:         121213

那么就跳过了j==3的情况,从而可能产生错误的结果。那么如果j==3匹配也不成功,进而我们就继续往前再寻找这样一个j,而对于我们这个例子,j==1。如果连j==1都不行,如下:

j==5

a: 1212132

b: 121212

j==3

a: 1212132

b:     121212

j==1

a: 1212132

b:         121212

那么我们已经无法再往前寻找了,也就说明我们无法从A串的前五位中的某一位开始找到找到一个匹配,这是我们的j取0,表示b串从a[6]开始重新寻找可能的匹配。假设我们通过预处理,将每次b串在j+1位匹配不成功之后,b串需要移动到的位置存储在P[j]中,以我们上面的例子为例:P[5]==3, P[3]==1, P[1]==0, 同理,我们不难求出P[6]==4, P[4]==2, P[2]==0。那我们的算法就描述为:a串和b串同时从头开始检测匹配,如果匹配成功就都往后移一位继续检测,如果不成功,假设在b串的j+1位和a串的i位不成功,那么将P[j]的值赋给j,然后继续检测匹配,如果仍不成功,继续重复这个过程,直到找到匹配,或者完全找不到匹配,那么就放弃a的前i位,从a[i+1]从头开始与b[1]检测匹配。

下面是这段算法的具体实现,其中略有不同的是:算法描述过程中,每个串从1开始计数,而程序中从0开始计数。

void KMP(char* A , char* B, int *P)
{
	int j=-1;
	for(int i=0; i<strlen(A);i++)
	{
		while (j>-1&&A[i]!=B[j+1])
		{
			j = P[j];
		}
		if(A[i]==B[j+1])
		{
			j = j+1;
		}
		if(j==strlen(B)-1)
		{
			printf("B is the substring of A\n");
		}
	}
}
要了解KMP算法的时间复杂度,要用到摊还分析。具体的过程如下:strlen(A)是求A串的长度,假设为n,那么显然两个if判断里的语句最多执行n次,现在的关键就是要了解while里的语句执行了多少次,我们注意到j = P[j]实际上是一个j值减少的过程,而j值必须为非负,而这个减少的次数必然小于等于n,这不难理解,因为要j = j + 1这条语句把j加到一个非负数,j值才有可能通过j = P[j]往下减。而j = j + 1总共最多执行n次,那么j = P[j]显然最多执行n次,因为每次j = P[j],j至少减少1。因此KMP算法的每部分都是线性时间,那么这几部分加起来也是线性时间O(n)。

知道了KMP算法,还有一个重要的问题没有解决,就是怎样通过预处理得到这个P数组。其实这个过程与KMP算法本身非常类似,可以看做是b串自己与自己的匹配过程。如下:

b1: abababa

b2: abababa

b串自己和自己当然是完全匹配,但为了求P[i],我们假设在i出现了不匹配,我们以i==6为例,来说明这个求值的过程,此时P[1]~P[5]都已经求出来了,下面要来求P[6]。j==5,因为前5位都匹配,所以j+1==i,这点通过前面的KMP算法就可以理解。那么在第6位,即j+1出现不匹配时,我们同样使用与KMP()相同的方法,将P[j]赋给j,然后再检测b2[j+1和b1[i],如果不匹配,那么继续将P[j]赋给j。这样的好处就是:如果b2[j+1]和b1[i]相同,那么将b2移到新的位置后,至少b1和b2有两位匹配,即b1[i-1]和把b2[j], b1[i]和b2[j+1]。其实,任何移动b2后,b2与b1有大于等于两位匹配的情况,必然通过j == P[j]的方式产生,例如上面的例子:j == P[5]==3, 假设不行,进一步j==P[3]==1, 如下:

j==P[5]==3

b1: abababa

b2:     abababa

j==P[3]==1

b1: abababa

b2:         abababa

如果匹配成功,P[i]==j+1;如果j==P[3]==1仍然不行,那么j==P[1]==0,也就是说在b2中找不到一个与b1前6位任一后缀,有至少两位匹配的前缀。那么,此时j==0,我们下一步检测b2[j+1]和b1[i],如果相等,那么就找到一个与b1中最后1位后缀匹配的b2的前缀,不难理解,如果只有一位匹配,那么必然是b2[1]和b1[i]匹配,P[i]==1,如果仍然不相等,那么就是说找不出一个b2的前缀可以与b1前6位的某一后缀匹配,P[i]==0。从这个过程可以看出,其实预处理检测了所有情况:大于等于两位匹配,1位匹配,及没有匹配,因此能够保证得到的结果符合我们对P[j]的要求,即满足b[1]~b[P[j]] == b[i-P[j]+1]~b[i]情况下的最大值。

下面是具体实现的代码:同样,算法描述过程中,每个串从1开始计数,而程序中从0开始计数。

void PreProcess(char *B, int *P)
{
	P[0]=-1;
	int j=-1;
	
	for(int i=1; i<M; i++)
	{
		while(B[j+1]!=B[i] && j>=0)
		{
			j = P[j];
		}
		if(B[j+1]==B[i])
		{
			j=j+1;
		}
		P[i] = j;
	}
}

PreProcess()的时间复杂度分析方法同KMP(),可以得出结论PreProcess()同样是线性时间,因此整个KMP算法的时间复杂度为O(n)。

以下是完整代码:

#include <stdio.h>
#include <string.h>

#define M 150
#define N 300
int m;
int n;

void PreProcess(char *B, int *P)
{
	P[0]=-1;
	int j=-1;
	
	for(int i=1; i<M; i++)
	{
		while(B[j+1]!=B[i] && j>=0)
		{
			j = P[j];
		}
		if(B[j+1]==B[i])
		{
			j=j+1;
		}
		P[i] = j;
	}
}

void KMP(char* A , char* B, int *P)
{
	int j=-1;
	for(int i=0; i<strlen(A);i++)
	{
		while (j>-1&&A[i]!=B[j+1])
		{
			j = P[j];
		}
		if(A[i]==B[j+1])
		{
			j = j+1;
		}
		if(j==strlen(B)-1)
		{
			printf("B is the substring of A\n");
		}
	}
}

int main()
{
	char A[N],B[M];
	int P[M];
	scanf("%s",A);
	scanf("%s", B);
	n=strlen(A);
	m=strlen(B);

	printf("%s\n",A);
	printf("%s\n",B);

	PreProcess(B,P);

	KMP(A, B, P);
	for(int i=0;i<m;i++)
	{
		printf("%d ",P[i]);
	}
}




内容概要:《2024年中国城市低空经济发展指数报告》由36氪研究院发布,指出低空经济作为新质生产力的代表,已成为中国经济新的增长点。报告从发展环境、资金投入、创新能力、基础支撑和发展成效五个维度构建了综合指数评价体系,评估了全国重点城市的低空经济发展状况。北京和深圳在总指数中名列前茅,分别以91.26和84.53的得分领先,展现出强大的资金投入、创新能力和基础支撑。低空经济主要涉及无人机、eVTOL(电动垂直起降飞行器)和直升机等产品,广泛应用于农业、物流、交通、应急救援等领域。政策支持、市场需求和技术进步共同推动了低空经济的快速发展,预计到2026年市场规模将突破万亿元。 适用人群:对低空经济发展感兴趣的政策制定者、投资者、企业和研究人员。 使用场景及目标:①了解低空经济的定义、分类和发展驱动力;②掌握低空经济的主要应用场景和市场规模预测;③评估各城市在低空经济发展中的表现和潜力;④为政策制定、投资决策和企业发展提供参考依据。 其他说明:报告强调了政策监管、产业生态建设和区域融合错位的重要性,提出了加强法律法规建设、人才储备和基础设施建设等建议。低空经济正加速向网络化、智能化、规模化和集聚化方向发展,各地应找准自身比较优势,实现差异化发展。
数据集一个高质量的医学图像数据集,专门用于脑肿瘤的检测和分类研究以下是关于这个数据集的详细介绍:该数据集包含5249张脑部MRI图像,分为训练集和验证集。每张图像都标注了边界框(Bounding Boxes),并按照脑肿瘤的类型分为四个类别:胶质瘤(Glioma)、脑膜瘤(Meningioma)、无肿瘤(No Tumor)和垂体瘤(Pituitary)。这些图像涵盖了不同的MRI扫描角度,包括矢状面、轴面和冠状面,能够全面覆盖脑部解剖结构,为模型训练提供了丰富多样的数据基础。高质量标注:边界框是通过LabelImg工具手动标注的,标注过程严谨,确保了标注的准确性和可靠性。多角度覆盖:图像从不同的MRI扫描角度拍摄,包括矢状面、轴面和冠状面,能够全面覆盖脑部解剖结构。数据清洗与筛选:数据集在创建过程中经过了彻底的清洗,去除了噪声、错误标注和质量不佳的图像,保证了数据的高质量。该数据集非常适合用于训练和验证深度学习模型,以实现脑肿瘤的检测和分类。它为开发医学图像处理中的计算机视觉应用提供了坚实的基础,能够帮助研究人员和开发人员构建更准确、更可靠的脑肿瘤诊断系统。这个数据集为脑肿瘤检测和分类的研究提供了宝贵的资源,能够帮助研究人员开发出更准确、更高效的诊断工具,从而为脑肿瘤患者的早期诊断和治疗规划提供支持。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值