字符串匹配(BF算法 +KMP算法)_给定两个字符串 s 和 t,若 t 是 s 的子串,返回 t 在 s 中第一次出现的位置,并返回-程序员宅基地

技术标签: 算法  c语言  数据结构初步  数据结构  

子串的定位操作通常称做申的模式匹配(其中T称为模式串),是各种串处理系统中
最重要的操作之一,字符串匹配的过程就是输入两个字符串,判断第二个字符串T(又称为模式串)是否为第一个字符串S的子串,若是,返回子串在S中的下标

(以下均由C语言实现)

BF算法:

基本思想:

BF算法,又称Brute Force暴力算法,从主串S的第一个字符开始和模式T的第一个字符进行比较,若相等,则继续比较两者的后续宇符;否则,从主串S的第二个宇符开始和模式T的第一个字符进行比较,重复上述过程,直到T中的字符全部比较完毕,则说明本趟匹配成功;或S中字符全部比较完,则说明匹配失败。

可以参考下下图:(参考懒猫老师《数据结构》相关课程笔记)

 即每一次匹配失败以后,指向主串的i向后移动一位,而指向模式串T的j总是回到串首,重新进行判断

代码实现:

(这里我用了两种有些区别的方法实现了BF算法)

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

int isSubstring_BF(char*S,char*T){//这是BF算法的第一种实现
	int i,j,temp;
	if(strlen(S)<strlen(T))//子串长于主串
	return -1;
	for(i=0;i<strlen(S);i++){
		temp=i;
		for(j=0;j<strlen(T);j++){
			if(S[temp]!=T[j]||temp>=strlen(S))//不相等或主串剩余长度小于子串
			break;
			else{
				temp++;
			}
		}
		if(j==strlen(T))
		return temp;
	}
	return -1;
}

int isSubstring_BF1(char*S,char*T){//这是BF算法的第二种实现
	int i=0,j=0,start=0;
	while(S[i]!='\0'&&T[j]!='\0'){
		if(S[i]==T[j]){
			i++;
			j++;
		}else{
			start++;
			i=start;
			j=0;
		}
	}
	if(T[j]=='\0')
	return start;
	else
	return -1;
}


main(){
	char S[50],T[50];
//	while(1){
	printf("请输入第一个字符串S:");
	scanf("%s",S);
	printf("请输入第二个字符串T:");
	scanf("%s",T);
	if(isSubstring_BF1(S,T)!=-1)
	printf("T是S的子串!\n");
	else
	printf("T不是S的子串!\n");
//}
}

KMP算法:

基本思想:

KMP算法为了减少匹配的次数,采用了一种新的思路。先简单的来说,在KMP算法中,当匹配失败时,i不会移动只将j进行移动,j移动的方法在下面会解释;i唯一移动的方法是,发现i指向的元素和j指向的首元素都不相同,则将i向后移动一位。

过程简述:

由下面可以发现,匹配成功时,i,j均移动;匹配失败时,例如主串S和模式串T在下标6号位失配,那下一步要怎么移动呢?

通过观察可以发现模式串T中前缀和后缀存在一个长度为2的相同的部分

对于D主串来说,已经验证过了黄框中的ab和T串中后缀的ab相同,而T前缀中也含有相同部分,那么,下一步,可以直接跳过T中相同的前缀部分,直接向后寻找

 这里补充一下:

前缀:包含首字母,不包含尾字母的所有子串

后缀:包含尾字母,不包含首字母的所有子串

与BF算法的做法不同,KMP算法跳过T中相同的前缀部分,则下一步是:

移动位数 = 模式串T已匹配的字符数 - 失配位置前的最长前缀匹配字符数

 

 当然,如果模式串D中没有相同的前后缀,那寻找的方式就和BF相同了

那么,怎么让模式串T中的j准确的找到该回溯到的位置呢?这里引入一个新的数组,叫前缀表,即next[]:

下标与模式串T下标一致,数组中的元素是该位置之前最长的匹配前缀,(匹配指的是前后缀相同)

计算next数组的方法:

 可以这样理解,因为next[]仅仅是基于模式串T形成的数组,所以并不知道在哪个位置会和S失配,所以要计算出在每一个位置失配时,j要返回的位置

以下图为例: (下划线为相同的对应前后缀)

next数组生成函数:

void getNext(char*T,int*next){//前缀表
	int j=-1;//前缀
	int i=0;//后缀
	next[0]=-1;
	int len=strlen(T);
	while(i<len){
		if(j==-1||T[i]==T[j]){
			i++;
			j++;//都移动
			next[i]=j;//保存当前位置的最长相同序列串长
		}
		else{
			j=next[j];//前缀回溯,原理也是找到相同的前后缀,直接从他们后面开始找
		}
	}
	printf("前缀表为:");
	for(int k=0;k<len;k++)
	printf("%d ",next[k]);
	printf("\n");
}

和上面应用的是一个原理

 完整代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int isSubstring_KMP(char*S,char*T){
	int i=0,j=0,start=0;
    int len1=strlen(S);
    int len2=strlen(T);
	int next[len2];
	getNext(T,next);//给前缀表赋值
//	printf("%d %d\n",strlen(S),strlen(T));
	while(i<len1&&j<len2){//不能用S[i]!='\0'&&T[j]!='\0',因为j==-1时会越界
		if(S[i]==T[j]||j==-1){//if(j==-1)即两字符串开头第一个元素就不一样
			i++;
			j++;
			start=i;
		}else{
			j=next[j];//滑动到子串的下一个和开头相同序列的判准位置,i位置不变
		}
//		printf("现在i和j的值分别是:%d %d\n",i,j);
	}
	if(j==strlen(T))
	return start;
	else
	return -1;
}

void getNext(char*T,int*next){//前缀表
	int j=-1;//前缀
	int i=0;//后缀
	next[0]=-1;
	int len=strlen(T);
	while(i<len){
		if(j==-1||T[i]==T[j]){
			i++;
			j++;
			next[i]=j;//保存当前位置的最长相同序列串长
		}
		else{
			j=next[j];//回溯
		}
	}
	printf("前缀表为:");
	for(int k=0;k<len;k++)
	printf("%d ",next[k]);
	printf("\n");
}

main(){
	char S[50],T[50];
//	while(1){
	printf("请输入第一个字符串S:");
	scanf("%s",S);
	printf("请输入第二个字符串T:");
	scanf("%s",T);
	if(isSubstring_KMP(S,T)!=-1)
	printf("T是S的子串!\n");
	else
	printf("T不是S的子串!\n");
//}
}

补充:这里可以对next[]前缀表进行优化,例如:

S串:aaabaaaab

T串:aaaab

前缀表next:-1 0 1 2 3

优化后nextval:-1 -1 -1 -1 3

按照上面的next函数求得前缀表后,当比较T和S串时,在第四个位置a和b失配,由next[j]的指示还需进行i=4,j=2;i=4,j=1;i=4,j=0这三次比较,实际上,因为模式串T中1,2,3个字符和第4个字符都相等,因此不需要再和主串中第4个字符相比较,可以直接滑动4个字符。

那该如何实现呢?

如果在前缀表函数中加一个判断,如果串中有连续的元素,就把上一个字符位置找到的最长前缀值再赋一遍就可以了

代码实现:

void get_nextval(char*T,int*nextval){//前缀表
	int j=-1;//前缀
	int i=0;//后缀
	nextval[0]=-1;
	int len=strlen(T);
	while(i<len){
		if(j==-1||T[i]==T[j]){
			i++;
			j++;
			if(T[i]!=T[j])
			nextval[i]=j;//保存当前位置的最长相同序列串长
			else{
			nextval[i]=nextval[j];
			}//防止重复再判断一次
		}
		else{
			j=nextval[j];//回溯
		}
	}
	printf("前缀表为:");
	for(int k=0;k<len;k++)
	printf("%d ",nextval[k]);
	printf("\n");
}

 初学小白,有错误欢迎指正喔!~

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/m0_63223213/article/details/125694656

智能推荐

pg数据库连接失败:org.postgresql.util.PSQLException: ��������: û���������� “xx.xx.xxx.xx“,_org.postgresql.util.psqlexception: 尝试连线已失败。-程序员宅基地

文章浏览阅读2.1k次。在本地电脑写好了一个springboot + mybatis + pg的项目,在本地调试运行正常,将项目打成jar包在服务器上运行,当与pg交互时出现上述报错信息。上述表示允许IP地址为10.10.56.17的所有用户可以通过MD5的密码验证方式连接主机上所有的数据库。1)找到pg的安装路径,该路径下有个data文件夹,在data文件夹找到pg_hba.conf配置文件。2)打开pg_hba.conf配置文件,在ipv4下添加服务器ip,例如。3)修改后保存,打开pg终端,执行。_org.postgresql.util.psqlexception: 尝试连线已失败。

VScode下配置Go语言开发环境【2023最新】_vscode go-程序员宅基地

文章浏览阅读2.2w次,点赞49次,收藏130次。Windows 下安装和卸载 Go 及 vscode 环境配置【2023最新】_vscode go

Java毕业设计基于Springboot+vue的插画投稿网站_vue 插画-程序员宅基地

文章浏览阅读207次。插画投稿网站是提供给插画师们展示和分享自己作品的平台。这些网站通常允许插画师上传自己的作品,并与其他用户进行交流和互动。插画师可以在这些网站上展示自己的作品集,参与各种比赛和活动,与其他插画师进行合作,甚至有机会与潜在客户建立联系。是一个面向设计师和创意人才的社区平台,也是插画师展示作品的理想场所。在Dribbble上,插画师可以上传自己的作品,参与各种设计挑战和竞赛,与全球设计师社区互动,展示自己的创意和技能。_vue 插画

订单状态机-程序员宅基地

文章浏览阅读8k次,点赞8次,收藏57次。0 前言电商平台所有模块中,订单系统作为比较核心的模块,它决定了整个流程能不能顺畅的执行,起着承上启下的作用(下单、支付、履约、售后、清结算、营销活动)。订单系统的设计主要需要考虑订单字段、业务流程、状态机三大个方面,这些内容决定了订单系统稳定性与扩展性。2 订单流程订单流程指整个订单从产生到完成的整个流转过程,它包括正向流程和逆向的流程。3 订单状态机状态机表示了一笔订单的生命周期,按照一定的方向通过触发不同的事件产生数据流转的过程。状态机v2.0随着业务快速._订单状态机

Linux设备调试-GDB调试器-程序员宅基地

文章浏览阅读491次,点赞5次,收藏11次。工欲善其事,必先利其器”,为了方便Linux驱动设备的开发和调试,建立舒适的开发环境、使用必要的软件工具,以及掌握常用的调试技巧是比较重要的。本篇介绍GDB调试器的主要功能和常见用法,同时在第三部分中,命令顺序按照使用频率由高到低编写,方便阅读和使用。

nyoj-0613-免费馅饼(dp)-程序员宅基地

文章浏览阅读53次。nyoj-0613-免费馅饼 G. 免费馅饼都说天上不会掉馅饼,但有一天gameboy正走在回家的小径上,忽然天上掉下大把大把的馅饼。说来gameboy的人品实在是太好了,这馅饼别处都不掉,就掉落在他身旁的10米范围内。馅饼如果掉在了地上当然就不能吃了,所以gameb...

随便推点

Matlab图像处理系列——图像复原之维纳滤波复原、约束最小二乘复原、L-R复原、盲去卷积图像复原_matlab维纳滤波图像复原-程序员宅基地

文章浏览阅读1.3k次,点赞21次,收藏22次。Lucky-Richardson(L-R)算法是非线性方法中一种典型的算法,在噪声信息未知时仍可得到较好的复原结果。维纳滤波又称为最小均方误差滤波,综合考虑了退化函数和噪声,找出一个原始图像f(x)的估值,使两者的均方误差较小。psf表示退化过程的点扩散函数,用于恢复psf和可能的加性噪声引起的退化;P(u,v)是函数p(x,y)的傅里叶变换,p(x,y)为拉普拉斯算子。Weight表示每个像素的加权值,记录了每个像素反应相机记录的质量。Dampar表示结果图像偏差的阈值,当偏差小于该值,算法停止迭代。_matlab维纳滤波图像复原

柔性数组详解-程序员宅基地

文章浏览阅读51次,点赞5次,收藏3次。这是结构体和动态内存管理的结合,事实上这个概念不常用,因为你会发现和线性表中的顺序表几乎如出一辙,区别只是一个是数组,一个是指针,都是需要动态申请内存。包含柔性数组的结构体的内存,用malloc函数申请内存,而由于结构体的大小不包括柔性数组,因此在开辟空间时要大于结构体的大小,用于预期柔性数组的使用。示例:(struct s*)malloc(sizeof(struct s) + 20);3、未知大小的 数组。

Memcache详解_memcached cachename-程序员宅基地

文章浏览阅读2.3k次。一、MemCached概念Memcached是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信二、MemCac_memcached cachename

python人工智能需要学多久,python人工智能学历要求-程序员宅基地

文章浏览阅读357次,点赞8次,收藏8次。作为一个学习者,什么样的学习方式、学习路径能够帮助我们更高效、便捷的入门人工智能,不至于错过奔驰而过的“AI”号列车?人工智能时代持续发展,成为新一轮产业变革的核心驱动力和引领未来发展的战略技术,不仅受到政策的支持,国内人工智能市场规模也在不断攀升,相应地对各行各业的人员也产生了巨大的影响,人工智能相关专业掀起了热潮,并且非计算机专业也被迫卷入“转型升级”的道路中。作为一项具有一定门槛的学科,如何避免陷入低效率的学习困境和低质量的培训陷阱?

PlugNT CMS v4.6.3 最新功能-程序员宅基地

文章浏览阅读61次。PlugNT CMS v4.6.3 最新功能:弃用标签 selected="commend,stick" 改为andwhere="commend=1 and stick=1"{tmp:baseif test="[tmp:field name="title"]" operate="equals" value="PlugNT"} {tmp:pagelist co..._true and stick

软考考点之软件质量管理及MCCALL_考试中质量属性 iso与mccall-程序员宅基地

文章浏览阅读458次。ISO 9126质量模型:软件质量模型的6大特性和27个子特性ISO9126软件质量模型是评价软件质量的国际标准,由6个特性和27个子特性组成,建议大家深入理解各特性、子特性的含义和区别,在测试工作需要从这6个特性和27个子特性去测试、评价一个软件。这个模型是软件质量标准的核心,对于大部分的软件,都可以考虑从这几个方面着手进行测评。一、功能性:1、适合性:提供了相应的功..._考试中质量属性 iso与mccall