首页 > 编程语言 >C语言查找-----------BF算法&&KMP算法

C语言查找-----------BF算法&&KMP算法

时间:2024-03-30 13:29:37浏览次数:28  
标签:主串 BF sub int C语言 算法 回退 next 我们

1.问题引入

有一个主字符串,有一个子字符串,要求我们寻找子字符串在主字符串里面开始出现的位置;

2.BF算法

BF算法就是暴力算法,这个做法虽然效率不高,但是按照我们传统的思路依然能够得到结果,接下来我们使用C语言实现这个查找的过程;

#include<stdio.h>
#include<assert.h>
#include<string.h>
//返回字串在主串里面的位置
//没有找到返回-1;
int BF(char* str, char* sub)
{
	assert(str && sub);
	if (str == NULL || sub == NULL)
	{
		return -1;
	}
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	int i = 0;
	int j = 0;
	while (i < lenstr && j < lensub)
	{
		if (str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			i = i - j + 1;
			j = 0;
		}
	}
	if (j >= lensub)
	{
		return i - j;
	}
	else
	{
		return -1;
	}
}
int main()
{
	printf("%d\n", BF("abcabdefabchd","abch"));
	printf("%d\n", BF("abceabcd", "abcd"));
	printf("%d\n", BF("abcabcabc", "abcdefgh"));
	return 0;
}

这段代码的逻辑是这样的:

(1)我们首先定义一个函数BF,我们这个函数的作用就是寻找子串在主串里面的位置,我们可能会在主串里面找到字串,找到就会返回子串在主串里面的位置下标,如果找不到就会返回-1;

(2)我们以代码主函数里面的第一个作为案例,我们使用*str代表主串,*sub代表子串;

(3)我们首先进行断言,断言的话就要包含对应的头文件,如果子串或者主串是空的,那么我们就直接返回-1,因为这样的话肯定找不到;

(4)我们定义i遍历主串,使用j遍历子串,我们使用strlen求两个字符串的长度(这里也是要包含对应的头文件的,如果i<主串的长度而且j小于子串的长度,说明我们正在进行遍历,我们需要在这样的情况下进行判断;

(5)如果i>=主串的长度,证明主串已经找完了,这个时候就是没有找到返回-1,如果j>=子串的长度,说明我们已经找到了,这个时候就要返回子串在主串的位置下标;

(6)接下来我们需要计算这个下标的变化,以代码主函数里面的第一个作为案例,i指向的是主串的第一个字符,j指向的是字串的第一个字符,第一个都是a,i++,j++,第二个都是b,i++,j++,第三个都是c,i++,j++,第四个就不一样了,这个时候我们需要重新寻找,i和j都要回退,j肯定是回退到0下标,i应该从第二个字符开始,因为我们刚刚是从第一个开始找,找不到,那么这个2下标应该如何表示呢?我们首先要清楚的是i和j的移动的个数是一致的,j已经走了j个,那么i-j就可以表示i开始的位置,但是这个位置找不到,我们就要从他的下一个位置开始找,我们就可以用i-j+1进行表示主串里面下标回退的位置;我们设置一个循环,依次进行,直到主串遍历完成或者子串遍历完成就停止退出循环;

(7)如果是j>=子串长度,说明可以在主串里面找到,我们直接返回i-j就可以找到第一个下标了,这个时候,就不需要加一了,因为i-j+1是找不到的时候j回退的位置;

(8)还有一个我们需要注意的细节就是我们在回退的时候,必须先让i回退,再让j回退,因为j如果回退到0,i-j+1显然就不是我们想逃回退的下标了,我们就是想要利用j的下标计算的,如果先把j置为0,肯定是无法得到我们想要的结果的。

3.KMP算法

我们想要了解KMP算法,就必须知道他和我们普通的暴力算法有什么不同之处,其实KMP算法是三个大佬发现的,KMP分别是这3个大佬名字的第一个字母(我们了解一下就可以了),他和普通算法的不同点就在于,

(1)我们上面介绍的BF算法,如果匹配错误,i返回i-j+1下标,我们的KMP算法是不会回退的;

(2)BF算法的j回退到0下标,这个地方KMP是不会回退到0的,而是回退到一个我们指定的位置,这个回退的指定的位置是KMP算法的亮点,也是难点,只有真正的了解这个回退的特定位置的计算方法,我们才能掌握KMP算法的精髓,再官方的算法里面,每个主串的元素都会对应一个回退的位置,因此我们把每个元素回退的位置放到数组里面,我们称这样的一个数组叫做Next数组

(本人KMP博客是根据下面的这位UP视频所写,除了手动求解我的博客有所涉及解析,其他的代码求解都是简写的(因为我对于其中的诸多细节也不是很通透明了,就不误人子弟了),读者可自行进行系统学习,超详细,链接如下)

【完整版】终于有人讲清楚了KMP算法,Java语言C语言实现_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1UL411E7M8/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=a432cb5e896a2b96961d1f73a6ebe0ca

(3)我们已经知道了Next数组这个概念,我们接下来学习一下这个Next数组里面每个元素的计算方法:

这个就是回退的规则,可能难以理解,我们通过一些具体实例理解一下,

对于初学者我们必须要清楚的是,这个算法是如何用的,通俗的讲,对于一个子串,我们应该看他是否和主串的字符相同,如果相同就进行下一个,如果不相同,我们的子串j就要回退到一个特定的位置,这个位置的求法就是我们要知道的,接下来我们讨论的和练习的都是这个回退下标的计算

可能到这个地方,你大概已经知道了,我们的每一个字符都有一个特定的回退位置,这个组成一个数组,我们称之为Next数组,例如我们的第一个字符回退到2下标的位置,我们就写作Next[0]=2,第二个字符回退到4下标的位置,我们就写作Next[1]=4,以此类推,我们根据规则先手动的求一下这个Next数组,我们现在就要知道怎么手动求解,我们随便给一个字符数组

我们的规定是第一个元素回退到-1下标,第二个回退到0下标(这个记住即可,后面会有用处,可以简单的理解为这个就是规定),从第三个开始,我们就可以根据规则手动求解,第三个c回退到哪个下标,是以a开始,以他前面的b结尾的两个相同的子串,因为只有一个ab,所以我们next[2]=0;第四个字符,我们要找到以a开始,以c结尾的两个字符串,因为这里只有abc,所以next[3]=0;下一个b字符,我们要找到以a开始,以a结尾的两个字符串,他们的长度是1,所以next[4]=1,同理next[5]=2,这样我们就手动求解了这个next数组;

#include<stdio.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
void getnext(char* sub, int* next, int lensub)
{
	next[0] = -1;
	next[1] = 0;
	int i = 2;
	int k = 0;
	while (i < lensub)
	{
		if ((k == -1) || sub[i - 1] == sub[k])
		{
			next[i] = k + 1;
			i++;
			k++;
		}
		else
		{
			k = next[k];
		}
	}
}
int KMP(char* str, char* sub, int pos)
{
	assert(str && sub);
	int lenstr = strlen(str);
	int lensub = strlen(sub);
	if (lenstr == 0 || lensub == 0)
		return -1;
	if (pos < 0 || pos >= lenstr)
		return -1;

	int* next = (int*)malloc(sizeof(int) * lenstr);
	assert(next);
	getnext(sub, next, lensub);
	int i = 0;
	int j = 0;
	while (i < lenstr && j < lensub)
	{
		if (j == -1 || str[i] == sub[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	if (j >= lensub)
		return i - j;
	else
		return -1;
}
int main()
{
	printf("%d", KMP("abdefabc", "abc", 0));
	return 0;
}

这段代码涉及到了代码表示我们手动计算的值,还有数组的越界访问,找不到和自己一样的字符就会不停的回退,直到相同才会停止,详情请根据视频自行学习;

【完整版】终于有人讲清楚了KMP算法,Java语言C语言实现_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1UL411E7M8/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=a432cb5e896a2b96961d1f73a6ebe0canext数组的优化:就是如果不一样,要不停的回退,我们的优化就是一次性回退到位,回退位置的字符和该字符一样,就写回退位置的nextval值,不一样就写自己的next值(视频也有讲解,读者自行学习)。

标签:主串,BF,sub,int,C语言,算法,回退,next,我们
From: https://blog.csdn.net/binhyun/article/details/136771820

相关文章

  • C语言rand、srand库函数生成随机数(附时间戳)
    前言:当我们想要用C语言写程序来获取一个随机数时,该如何获取呢?这里我们上百度搜索一下这里就有提到使用rand、srand、time库函数搭配来获取随机数,也许根据其所说我们已经可以获得随机数解决问题,但想问题不能只浮于表面,下面我们来深入认识一下rand、srand、time库函数。一、ra......
  • 金工实习、C语言课设、数据结构课设-报告
    源代码丢失了只剩下报告,配图流程图齐全,可直接使用C语言课设报告:香水管理系统数据结构课设报告:西邮导航金工实习:车工学习、钳工学习、数控学习 文章头部下载三篇报告压缩包~......
  • 搜索与图论(二)bfs---以题为例
    给定一个 n×m的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。最初,有一个人位于左上角 (1,1)处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。请问,该人从左上角移动至右下角 (n,m)处,至少需要移动多......
  • C语言程序10题
    第71题(10.0分)       难度:易       第1章/*-------------------------------------------------------【程序填空】---------------------------------------------------------功能:求100-999之间的水仙花数说明:水仙花数是指一个三位数的各位数字的立......
  • C语言中的数据文件的操作
    接下来我们开启今天的C语言之旅吧~1. 为什么使用文件?如果没有文件,我们写的程序的数据是储存在电脑中的内存中,如果程序退出,内存回收,数据就会回收,等再次运行程序,是看不到上次程序的数据的,如果将数据进行持久化的保存,我们就可以是文件。2.什么是文件?磁盘(硬盘)上的文件。......
  • 动画图解:九大经典排序算法详解-算法宝App
    重新整理了一遍排序算法,结合自己开发的算法宝App的录屏,转成webp动画一起分享给大家,适合新手。概述时间复杂度(timecomplexity)用来描述算法的运行时间。常用大O符号表述。比如:O(n),O(1),O(logn),O(n2)等。举例:O(n)表示线性级复杂度,表示时间复杂度和元素element数量n成正比。......
  • 【C语言基础】:数据在内存中的存储
    文章目录一、整数在内存中的存储二、大小端字节序和字节序判断1.为什么有大小端?2.练习三、浮点数在内存中的存储1.浮点数的存储1.1浮点数的存储过程1.2浮点数取的过程四、题目解析     书山有路勤为径,学海无涯苦作舟。创作不易,宝子们!如果这篇文......
  • 京东一面挂在了CAS算法的三大问题上,痛定思痛不做同一个知识点的小丑
    写在开头在介绍synchronized关键字时,我们提到了锁升级时所用到的CAS算法,那么今天我们就来好好学一学这个CAS算法。CAS算法对build哥来说,可谓是刻骨铭心,记得是研二去找实习的时候,当时对很多八股文的内容浅尝辄止,很多深奥的知识点只是知道个概念,源码看的也不深,代码量也不够,京东一......
  • 【C语言】运算符优先级全面解析
    目录前言运算符优先级概述运算符分类与优先级列表运算符优先级的实际应用示例1:乘法和加法的优先级示例2:使用括号改变运算顺序示例3:赋值运算符的优先级示例4:逻辑运算符的优先级总结前言    C语言作为编程世界中的一颗常青树,其精确的语法规则和运算符优先级......
  • C语言中的自定义类型
    在C语言中有三种常见的自定义类型:结构体,联合体,枚举。1.1 结构体1.1.1结构体的声明structtag{  member-list;//成员清单}variable-list; //变量清单例如:我们创建一个结构体的变量,来描述一个学生。structStudent{charname[20];intage;cha......