字符串匹配(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

智能推荐

874计算机科学基础综合,2018年四川大学874计算机科学专业基础综合之计算机操作系统考研仿真模拟五套题...-程序员宅基地

文章浏览阅读1.1k次。一、选择题1. 串行接口是指( )。A. 接口与系统总线之间串行传送,接口与I/0设备之间串行传送B. 接口与系统总线之间串行传送,接口与1/0设备之间并行传送C. 接口与系统总线之间并行传送,接口与I/0设备之间串行传送D. 接口与系统总线之间并行传送,接口与I/0设备之间并行传送【答案】C2. 最容易造成很多小碎片的可变分区分配算法是( )。A. 首次适应算法B. 最佳适应算法..._874 计算机科学专业基础综合题型

XShell连接失败:Could not connect to '192.168.191.128' (port 22): Connection failed._could not connect to '192.168.17.128' (port 22): c-程序员宅基地

文章浏览阅读9.7k次,点赞5次,收藏15次。连接xshell失败,报错如下图,怎么解决呢。1、通过ps -e|grep ssh命令判断是否安装ssh服务2、如果只有客户端安装了,服务器没有安装,则需要安装ssh服务器,命令:apt-get install openssh-server3、安装成功之后,启动ssh服务,命令:/etc/init.d/ssh start4、通过ps -e|grep ssh命令再次判断是否正确启动..._could not connect to '192.168.17.128' (port 22): connection failed.

杰理之KeyPage【篇】_杰理 空白芯片 烧入key文件-程序员宅基地

文章浏览阅读209次。00000000_杰理 空白芯片 烧入key文件

一文读懂ChatGPT,满足你对chatGPT的好奇心_引发对chatgpt兴趣的表述-程序员宅基地

文章浏览阅读475次。2023年初,“ChatGPT”一词在社交媒体上引起了热议,人们纷纷探讨它的本质和对社会的影响。就连央视新闻也对此进行了报道。作为新传专业的前沿人士,我们当然不能忽视这一热点。本文将全面解析ChatGPT,打开“技术黑箱”,探讨它对新闻与传播领域的影响。_引发对chatgpt兴趣的表述

中文字符频率统计python_用Python数据分析方法进行汉字声调频率统计分析-程序员宅基地

文章浏览阅读259次。用Python数据分析方法进行汉字声调频率统计分析木合塔尔·沙地克;布合力齐姑丽·瓦斯力【期刊名称】《电脑知识与技术》【年(卷),期】2017(013)035【摘要】该文首先用Python程序,自动获取基本汉字字符集中的所有汉字,然后用汉字拼音转换工具pypinyin把所有汉字转换成拼音,最后根据所有汉字的拼音声调,统计并可视化拼音声调的占比.【总页数】2页(13-14)【关键词】数据分析;数据可..._汉字声调频率统计

linux输出信息调试信息重定向-程序员宅基地

文章浏览阅读64次。最近在做一个android系统移植的项目,所使用的开发板com1是调试串口,就是说会有uboot和kernel的调试信息打印在com1上(ttySAC0)。因为后期要使用ttySAC0作为上层应用通信串口,所以要把所有的调试信息都给去掉。参考网上的几篇文章,自己做了如下修改,终于把调试信息重定向到ttySAC1上了,在这做下记录。参考文章有:http://blog.csdn.net/longt..._嵌入式rootfs 输出重定向到/dev/console

随便推点

uniapp 引入iconfont图标库彩色symbol教程_uniapp symbol图标-程序员宅基地

文章浏览阅读1.2k次,点赞4次,收藏12次。1,先去iconfont登录,然后选择图标加入购物车 2,点击又上角车车添加进入项目我的项目中就会出现选择的图标 3,点击下载至本地,然后解压文件夹,然后切换到uniapp打开终端运行注:要保证自己电脑有安装node(没有安装node可以去官网下载Node.js 中文网)npm i -g iconfont-tools(mac用户失败的话在前面加个sudo,password就是自己的开机密码吧)4,终端切换到上面解压的文件夹里面,运行iconfont-tools 这些可以默认也可以自己命名(我是自己命名的_uniapp symbol图标

C、C++ 对于char*和char[]的理解_c++ char*-程序员宅基地

文章浏览阅读1.2w次,点赞25次,收藏192次。char*和char[]都是指针,指向第一个字符所在的地址,但char*是常量的指针,char[]是指针的常量_c++ char*

Sublime Text2 使用教程-程序员宅基地

文章浏览阅读930次。代码编辑器或者文本编辑器,对于程序员来说,就像剑与战士一样,谁都想拥有一把可以随心驾驭且锋利无比的宝剑,而每一位程序员,同样会去追求最适合自己的强大、灵活的编辑器,相信你和我一样,都不会例外。我用过的编辑器不少,真不少~ 但却没有哪款让我特别心仪的,直到我遇到了 Sublime Text 2 !如果说“神器”是我能给予一款软件最高的评价,那么我很乐意为它封上这么一个称号。它小巧绿色且速度非

对10个整数进行按照从小到大的顺序排序用选择法和冒泡排序_对十个数进行大小排序java-程序员宅基地

文章浏览阅读4.1k次。一、选择法这是每一个数出来跟后面所有的进行比较。2.冒泡排序法,是两个相邻的进行对比。_对十个数进行大小排序java

物联网开发笔记——使用网络调试助手连接阿里云物联网平台(基于MQTT协议)_网络调试助手连接阿里云连不上-程序员宅基地

文章浏览阅读2.9k次。物联网开发笔记——使用网络调试助手连接阿里云物联网平台(基于MQTT协议)其实作者本意是使用4G模块来实现与阿里云物联网平台的连接过程,但是由于自己用的4G模块自身的限制,使得阿里云连接总是无法建立,已经联系客服返厂检修了,于是我在此使用网络调试助手来演示如何与阿里云物联网平台建立连接。一.准备工作1.MQTT协议说明文档(3.1.1版本)2.网络调试助手(可使用域名与服务器建立连接)PS:与阿里云建立连解释,最好使用域名来完成连接过程,而不是使用IP号。这里我跟阿里云的售后工程师咨询过,表示对应_网络调试助手连接阿里云连不上

<<<零基础C++速成>>>_无c语言基础c++期末速成-程序员宅基地

文章浏览阅读544次,点赞5次,收藏6次。运算符与表达式任何高级程序设计语言中,表达式都是最基本的组成部分,可以说C++中的大部分语句都是由表达式构成的。_无c语言基础c++期末速成