C语言算法之二分查找详解


1. 引入

我们从一个小案例来探讨

在一个指定的有序数组中,查找具体的一个数

int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 0;

在arr数组中,找7。如果找到了,就打印下标,找不到,就打印找不到。
在⼀个升序的数组中查找指定的数字k,很容易想到的⽅法就是遍历数组。
这里用代码实现:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()

{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	//            0 1 2 3 4 5 6 7 8 9
	int k = 0;
	scanf("%d", &k);//7
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int find = 0;//假设找不到
	for (i = 0; i < sz; i++)
	{
		if (k == arr[i])
		{
			printf("找到了,下标是%d\n", i);
			find = 1;
			break;
		}
	}
	if (find == 0)
	{
		printf("找不到\n");
	}

	return 0;
}

但是这种⽅法效率⽐较低。 ⽐如我买了⼀双鞋,你好奇问我多少钱,我说不超过300元。你还是好奇,你想知道到底多少,我就让你猜,你会怎么猜?你会1,2,3,4…这样猜吗?显然很慢;⼀般你都会猜中间数字,⽐如:150,然后看⼤了还是⼩了,这就是⼆分查找,也叫折半查找。

2. 算法思路

首先,假设表中元素是按升序排列,将表中间位置记录的关键字与查找关键字比较,如果两者相等,则查找成功;否则利用中间位置记录将表分成前、后两个子表,如果中间位置记录的关键字大于查找关键字,则进一步查找前一子表,否则进一步查找后一子表。重复以上过程,直到找到满足条件的记录,使查找成功,或直到子表不存在为止,此时查找不成功。

3. 算法步骤

在这里插入图片描述

(1) 如图假设要查找数字7,首先确定最左边和最右边的下标元素,分别命名为left,right
(2) 再确定查找区间的中间下标元素 mid = (left + right ) / 2
(3) 用待查找值与中间下标元素所确认的值进行比较,若相等,就打印下标。
(3) 若大于,则在前子表中继续进行二分查找
(4) 若小于,则在后子表中继续进行二分查找
(5) 查找成功后,返回数值的数组下标
在这里插入图片描述

/*假设查找数组*/int arr[] = {12345678910}//中的数值7

分析:一共进行了四次查找,到left = right时查找结束。第一次mid = 4,下标对应值5小于7,则left = mid + 1 = 5,继续在后子表折半查找,第二次mid = 7,下标对应值8大于7,则right = mid - 1 = 6,继续在前子表折半查找,第三次mid = 5,下标对应值6小于7,则left = mid + 1 = 6,继续在后子表折半查找。第四次mid = 6,下标对应值等于7,查找成功 此时left = right,找到了7,对应下标为6

4. 算法实现

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	scanf("%d", &k);
	//查找-二分查找
	int left = 0;
	int right = sz - 1;
	int find = 0;//假设找不到

	while (left<=right)  
	{
		int  mid = (left + right) / 2;
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			printf("找到了,下标是%d\n", mid);
			find = 1;
			break;
		}
	}
	if (find == 0)
	{
		printf("找不到了\n");
	}
	return 0;
}

5. 问题

求中间元素的下标,使⽤ mid = (left + right) / 2 ,如果left和right⽐较⼤的时候可能存在问题
如下代码:

#include <limits.h>
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
	//INT_MAX;

	int left = 2147483646;
	int right = 2147483646;

	int mid = (left + right)/2;

	printf("%d\n", mid);

	return 0;
}

显然运行结果是错的!
在这里插入图片描述

为什么错了?
原因很简单因为left 和 right 太大了,两者相加已经超出了整形所能表示的最大值了,这时再除于2就有一些溢出了。

这时在求两数平均值时,我们用另一种巧妙的方法。
在这里插入图片描述
假设 有两个这样的台阶 怎么让他们一样高?
很容易想到 用两者之间的差值除2 再分给L 这样就能一样高了。
所以平均值就可以用 mid = left + (right - left) / 2
这样就不会有问题了 因为R不会越界的话,L只是加了一小部分绝对不可能越界,只要给一个正确的R,算法就不会有问题。
原代码就可以改成:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	scanf("%d", &k);
	//查找-二分查找
	int left = 0;
	int right = sz - 1;
	int find = 0;//假设找不到

	while (left<=right)  
	{
		int mid = left + (right-left)/2;
		if (arr[mid] < k)
		{
			left = mid + 1;
		}
		else if (arr[mid] > k)
		{
			right = mid - 1;
		}
		else
		{
			printf("找到了,下标是%d\n", mid);
			find = 1;
			break;
		}
	}
	if (find == 0)
	{
		printf("找不到了\n");
	}
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值