C语言 二分查找法<Binary Search>(折半查找)具体实现

二分查找法(Binary Search)(折半查找)

二分查找也称折半查找(Binary Search),它是一种效率较高的查找方法。它充分利用了元素间的次序关系,采用分治策略(分半),可在最坏的情况下用O(log2n)完成搜索任务。但是,折半查找要求线性表必须采用顺序存储结构,而且表中元素按关键字有序排列。

使用条件

折半查找法使用的限制条件为:所要查找的静态范围,如数组,必须是顺序排列的。
例如:无序数组:arr1 = {1,4,3,5,2,6,8,7,9};要想在数组arr1中用折半查找法(二分查找法)找出关键字8,首先第一步要对数组arr1排序(从小到大,或从大到小都可)。对数组从小到大排序,存为arr2 = {1,2,3,4,5,6,7,8,9};,这样才可以实现用折半查找法。
可以运用 C语言 冒泡排序法bubble sort 对数组进行排序。

算法思想

折半查找(Binary Search)法核心思想是:逐步缩小范围;
折半查找的核心查找过程是:先确定需要查找关键值所在范围(区间),然后逐步缩小范围直到找到或找不到该记录为止。

以数组arr1举例。

查找思想

1、找到数组的中间位置
2 、检测中间位置的数据是否与要查找的数据key相等
a: 相等,找到,打印下标,跳出循环
b: key < arr[mid], 则key可能在arr[mid]的左半侧,继续到左半侧进行二分查找
c: key > arr[mid], 则key可能在arr[mid]的右半侧,继续到右半侧进行二分查找
如果找到返回下标,否则继续,直到区间中没有元素时,说明key不在集合中,打印找不到

查找过程

①:确定要查找的关键字key = 8。
②:首先对arr1进行排序。得到数组{1,2,3,4,5,6,7,8,9}。
③:设置数组的下限arr2[0] = 1;为最小值,将它定义为:left = arr[0];上限arr2[8] = 9;为最大值,将它定义为:right = arr[8];中间值为数组中间下标的值,mid = (left+ right)/ 2。
④:将mid位置的值与关键值key比较,
最为理想情况下,若arr2[mid] = key,则一次性找到了关键字key;
若arr2[mid] > key,说明数组中间位置的值比关键字大,则需要在数组的左侧上半段寻找;
若arr2[mid] < key,说明数组中间位置的值比关键字小,则需要在数组的右侧下半段寻找;
以此循环直到找到数组中的key值或者left > right为止。
如果在数组中找到了关键字key,则输出打印数组的下标。
如果left > right时还没有找到,则说明数组中没有要找的关键值。

查找过程图示如下:
在这里插入图片描述

代码实例

折半查找法一共有两种方法的代码:
方法一,采用[left, right] 前闭后闭区间:

// 方法一,采用[left, right] 区间
#include <stdio.h>

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9};
	int key = 8;
	int left = 0;
	int right = sizeof(arr)/sizeof(arr[0])-1; // right位置的数据可以取到

	while(left<=right) // right位置有数据,必须要添加=号
	{
		int mid = left+(right-left)/2;
		if(arr[mid]>key) // key小于中间位置数据,说明key可能在左半侧,需要改变右边界
		{
			right = mid-1; // right位置的数据可以取到,因此right=mid-1
		}
		else if(arr[mid]<key)// key大于中间位置数据,说明key可能在右半侧,需要改变左边界
		{
			left = mid+1; // left位置的数据可以取到,因此left=mid+1
		}
		else
		{
			printf("找到了,下标是:%d\n", mid);
			break;
		}
	}

	if(left>right)
		printf("找不到\n");
	return 0;
}

// 方法二,采用[left, right) 前闭后开区间:

// 方法二,采用[left, right) 区间
#include <stdio.h>
 
int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9};
	int key = 8;
	int left = 0;
	int right = sizeof(arr)/sizeof(arr[0]); // right位置的数据取不到
 
	while(left<right) // right位置没有数据,此处不需要添加=
	{
		int mid = left+(right-left)/2;
		if(arr[mid]>key) // key小于中间位置数据,说明key可能在左半侧,需要改变右边界
		{
			right = mid; // right位置的数据取不到,因此right=mid,不需要减1
		}
		else if(arr[mid]<key)// key大于中间位置数据,说明key可能在右半侧,需要改变左边界
		{
			left = mid+1; // left位置的数据可以取到,因此left=mid+1
		}
		else
		{
			printf("找到了,下标是:%d\n", mid);
            break;
		}
	}
   
	if(left>=right)
		printf("找不到\n");
	return 0;
}

输出:

找到了,下标是:7
请按任意键继续. . .

在这两种方法中有很多易错点和易混淆点

1. right的右半侧区间取值,该值决定了后序的写法
2. while循环的条件是否有等号
3. 求中间位置的方法,直接相加除2容易造成溢出
4. 更改left和right的边界时,不确定是否要+1和-1

过程分析

折半查找的运行过程可以用二叉树来表示,这棵树通常称为“判定树”。例如arr2中的静态查找集合数组中做折半查找的过程,对应的判定树如图所示 :
折半查找对应的判定树
从图中我们可以看出:如果想找到key值8的话,就要在数组中进行3次查找;第一次找到5,经过比较,发现key值应该在5的右半侧值为6~9的范围内;第二次找到7,经过比较,发现key值应该在7的右半侧值为8或9内,第三次找到key值8。

可以发现,对于具有 n 个结点(查找集合中有n个元素)的判定树,它的层次数至多为:log2n + 1(如果结果不是整数,则做向上取整操作,例如:log29 = 3 +1 = 4 )。

算法复杂度

最好情况下,复杂度为1;
最坏情况下,复杂度为O(log2n)(具体计算时向上取整)。

折半查找法的优缺点

优点:比较次数少,查找速度快,平均性能好;
缺点:是要求待查集合为有序集合,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序集合。
当查找表使用链式存储结构表示时,折半查找算法无法有效地进行比较操作(排序和查找操作的实现都异常繁琐)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值