【C语言习题演练】分支与循环——折半查找

大家好,很高兴又和各位见面了!!!今天我们继续分享分支与循环的相关习题,今天要分享的题目如下所示:

  • 在一个有序数组中查找具体的某个数字n。

今天的题目感觉有点难以理解,没关系,接下来我就来给各位分享这些题目的解析,希望各位看完解析后,能够自己动手实操一遍,加深对这些内容的理解。

习题演练——分支与循环篇

在一个有序数组中查找具体的某个数字n

经过前面两个篇章的习题演练,我相信大家对我的解题流程比较熟悉了,这里我就不多强调了。下面我们开始对这一题进行分析吧;

题目解析

因为阅读这篇文章的朋友有刚接触C语言的,也有基本功已经很扎实了的,所以这里我就简单介绍一下数组。

数组

所谓的数组,我们可以简单的理解为是一个集合,一个数组由数组元素类型+数组名+数组大小组成,如下所示:

//数组
int arr[10];
//int——数组元素类型;
//arr——数组名,这个名称凭自己的喜好随意定义,没有特别的要求;
//[10]——[数组大小],数组的大小是由中括号加具体数值组成;
 数组下标

数组内的元素都是有对应下标的,这里我们先记结论:

  1. 数组元素的下标从0开始依次增加,如例子中的arr数组,它里面有10个元素,那这十个元素的下标就是,0、1、2、3、4、5、6、7、8、9;
  2. 每一个下标对应一个元素,我们可以通过数组名+数组下标来访问数组内的元素,如:arr[0]、arr[1]、arr[2]、arr[3]、……、arr[9];
  3. 数组所占空间大小=数组元素所占空间大小*数组元素数量;
  4. 数组元素所占空间大小=数组元素类型所占空间大小;
  5. 最后一位元素下标比数组大小小1;

这里简单举一个例子:

//数组
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//int——数组元素类型;
	//arr——数组名,这个名称凭自己的喜好随意定义,没有特别的要求;
	//[10]——[数组大小],数组的大小是由中括号加具体数值组成;
	//{ 1,2,3,4,5,6,7,8,9,10 }——数组元素,需要用大括号圈起来;
	int i = 0;//定义下标变量
	for (i = 0; i < 10; i++)
	{
		//通过数组下标访问数组元素
		printf("arr[%d] = %d\n", i, arr[i]);
		//arr[i]——数组元素
	}
	//计算数组所占空间大小
	printf("%d\n", sizeof(arr));
	//计算数组元素所占空间大小(以第一个元素为例)
	printf("%d\n", sizeof(arr[0]));
	//计算元素类型所占空间大小
	printf("%d\n", sizeof(int));
	//定义数组大小变量
	int sz = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz);
	//sizeof——计算操作对象所占计算机内存大小关键字;
	//arr[0]——数组首元素;
	return 0;
}

现在我们看一下这个代码运行的结果,我们通过VS的调试界面来说明:

从运行结果中我们进一步证明了一下结论:

  1. 数组元素可以通过下标进行访问;
  2. 数组元素所占空间大小=元素类型所占空间大小;
  3. 数组所占空间大小=数组元素所占空间大小*数组大小;
  4. 最后一位元素下标比数组大小小1;

想必刚刚接触C语言的朋友们对数组应该有了个初步的认识了,后面我会将数组的内容单独作为一个篇章的内容来给各位介绍的,记得关注哦!

数组的相关内容咱们了解后,现在再回到咱们的题目中来;

题目分析

题目要求是在一个有序数组中查找具体的某个数字n。从这个题目中我们提取出关键信息:

一个有序数组、查找、数字n。

通过这三个关键信息,我们不难联想到这一题我们需要完成三个任务:

  1. 定义一个有序数组;
  2. 代码有在数组中查找的功能;
  3. 数组内的元素是数字;

这三个任务我们再整合一下就会变成:

  1. 定义一个整型/浮点型数组;
  2. 代码有在数组中查找的功能;

这样咱们的思路就很清晰了,这一题我们简单点,直接定义整型数组来进行解题,下面我们来分步完成;

第一步:定义一个整型数组

整型数组还是很好定义的,咱们在介绍数组时的举例就是一个整型数组,但是题目需要的是一个有序数组,那什么又是有序数组呢?有序数组和数组有什么区别?

我对这个问题的理解是,有序数组是数组的一种特殊形式,如下图所示:

从图中我们可以看到,所谓的有序就是数组里的元素按照特定的次序进行排列,如arr2的从小到大排列,arr3中的从大到小排列。这里我们直接记结论:

有序数组就是数组里的元素按照一定次序排列的数组

在这题中我们只需要使用数组arr2或者arr3就可以了,简单点,咱们使用从小到大排列的有序数组arr2。

数组定义好后,接下来就是查找功能的实现了;

第二步:查找数字n

看到这个功能,马上就有朋友想到了,那不就是很简单,我一个一个查找过去就好了,把数组中的每一个元素都与目标数字比较一下,如果相等我们不就找到了吗?

在实现这个功能的编写之前,我们要考虑到查找的过程中会出现两种情况:

  • 数字n在数组内;
  • 数字n不在数组内;

为了分辨这两种情况,我们这个功能中就需要有条件语句来进行判断:

当元素在数组中找到时,元素的下标肯定小于元素个数,或者说小于数组大小;

当元素下标大于数组大小时,说明所有元素都被找过了,数组中并没有该元素。

下面我们进行编码,并用数组arr1和arr2分别测试一下:

从测试结果中我们可以看到,如果一个一个去进行查找的话,完全是可以实现的,并且数组也不需要有序;

通过这种方式实现查找,我们可以看到循环执行的次数,在arr1中执行了3次,在arr2中执行了6次。

下面我们来思考一下,如果一个数组里有100个元素、1000个元素、10000个元素甚至更多个元素,这种查找方式会是怎样的一个效果?

此时如果是无序数组的话,我们像这样查找,就凭运气了,运气好,很快就能找到,运气不好就另说了。

此时如果是一个有10000个元素的有序数组并且元素是从小到大排列,元素的值为0~10000,我要在这些元素里面找到值为5000的这个元素,此时程序需要执行5000次才能找到,这样效率是不是太低了。

那对于有序数组来说,有没有什么更高效的方法呢?

当然有,这就是我们今天要介绍的折半查找法,也叫二分法;

折半查找算法(二分法)

什么是二分法?

二分法字面意思理解就是二分,平均分的意思,通过平均分的方式将查找范围缩小,以此来提高查找效率。

下面我还是通过找5000的例子,如果我们通过二分查找法是不是只需要通过找0~10000的中间数,再将这个数与5000进行对比来确定下一次查找的范围,0~10000的平均数刚好就是5000,也就是说,如果用二分查找法,我们只需要一次就能找到。

这个例子只能说明二分法的效率高,但是并不能很好的说明二分法的工作原理,下面我们就来详细介绍一下二分法的查找逻辑:

二分法的查找逻辑

我们介绍查找逻辑还是通过图解的方式来进行介绍:

从图中我们可以看到,二分查找就是通过中间值作为一个向标,来逐步确定目标的具体范围;

每做一次查找,就会有一半的数被筛选掉,这样大大提高了我们的查找效率;

二分法筛选的方式就是通过中间值与查找对象进行对比:

  • 查找对象<中间值时,则将中间值到最右边的所有对象全部排除掉,新的查找范围是从最左边到原中间值左边相邻的值;
  • 查找对象>中间值时,则将最左边的值到中间值的所有对象全部排除掉,新的查找范围是从原中间值右边相邻的值到最右边;
  • 查找对象=中间值时,则我们找到了目标值;
  • 图中未展示的一种情况是,当所有的数都被排查掉时,那说明我们要查找的数并不在我们的范围内。 

我相信看到这里,大家应该都能理解二分法查找了,接下来我们就可以开始通过二分法来实现在有序数组中查找数字了;

代码实现

从二分法的查找逻辑我们知道了在使用二分法时我们需要4个对象——查找目标、最左边的值、中间值、最右边的值,下面我以从小到大的顺序数组为例来编写代码:

第一步:定义对象

我们需要先定义数组、查找目标、最左边的值、中间值、最右边的值、下标这些内容。

第一次二分查找时,数组内的最左边的值就是首元素,最右边的值就是最后一位元素,中间值就是数组最中间的值,对应的元素下标=(左边元素的下标+右边元素的下标)/2;

下面我们开始编写第一步的代码:

第二步:查找目标

 对比的方式我们还是通过条件语句来进行判断,如果中间下标的元素与目标相同,则找到目标,不相同则没有找到:

既然没有找到,我们就需要继续查找,并且改变查找范围内的端点值,这是一个重复的过程,所以我们需要使用循环语句,等到找到目标时,结束循环:

找到目标的情况我们就完美实现了,下面我们来思考一下如果没有找到目标会是什么情况?

第三步:未找到目标 

这里我们还是借助图来理解:

从图中说明我们能的到结论,当在二分查找时出现左坐标大于右坐标时,说明范围内没有符合条件的数,因此判断未找到的条件就是左坐标是否大于右坐标,根据这个条件,我们来进行编码:

//习题演练——分支与循环篇
//1.在一个有序数组中查找具体的某个数字n——二分法
int main()
{
	//定义顺序数组
	int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
	//定义查找对象
	int k = 0;
	scanf("%d", &k);//通过scanf函数输入查找对象
	//定义下标变量
	int i = 0;
	//定义左值下标
	int left = 0;
	//定义中间值元素下标
	int mid = 0;
	//定义右值下标
	int right = sizeof(arr) / sizeof(arr[0]) - 1;
	for (i = 0; i < (sizeof(arr) / sizeof(arr[0]));i++)
	{
		//定义中间值元素下标
		int mid = (left + right) / 2;
		//中间元素与目标对比
		//中间元素大于目标值,舍弃右边
		if (arr[mid] > k)
		{
			//右值变成中间值左边相邻的值
			right = mid - 1;
		}
		//中间元素小于目标值,舍弃左边
		else if (arr[mid] < k)
		{
			//左值变成中间值右边相邻的值
			left = mid + 1;
		}
		//中间元素与目标相等
		else if (arr[mid] == k)
		{
			printf("找到了,目标元素下标为%d\n", mid);
			break;
		}
	}
	if (left > right)
	{
		printf("没找到,数组里没有这个数\n");
	}
	return 0;
 }

 我们输入12来测试一下,看看能不能发现找不到目标:

可以看到能够正常运行。这样我们就通过二分法完成了这一题的解答。

结语 

今天的内容到这里就结束了,希望今天的内容对大家在理解运用二分法上能有帮助,接下来我会继续给大家分享C语言的相关内容,以此来帮助大家更好的学习C语言,感谢大家的翻阅,咱们下一篇见。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值