二分查找

二分查找

二分查找是基于有序序列的查找算法。令[left,right]为整个序列的下标区间,然后每次测试当前[left,right]的中间位置mid=(left+right)/2,判断A[mid]与欲查询的元素x的大小。

  1. 如果A[mid]==x,说明查找成功,退出查询
    在这里插入图片描述
  2. 如果A[mid]>x,说明元素x在mid位置的左边,因此往左子区间[left,mid-1]继续查找
    在这里插入图片描述
  3. 如果A[mid]<x,说明元素x在mid位置的右边,因此往右子区间[mid+1,right]继续查找
    在这里插入图片描述

例题

从序列A={3,7,8,11,15,21,33,52,66,88}中查询数字11和34的位置,其中序列下标为1到10。
先查询11,令left=1、right=10,表示当前查询的下标范围。

  1. [left,right]=[1,10],因此下标中点mid=(left+right)/2=5。由于A[mid]=A[5]=15,15>11,说明需要在[left,mid-1]范围内继续查找,因此令right=mid-1=4。
    在这里插入图片描述
  2. [left,right]=[1,4],因此下标中点mid=(left+right)/2=2。由于A[mid]=A[2]=7,而7<11,说明需要在[mid+1,right]范围内继续查找,因此令left=mid+1=3。
    在这里插入图片描述
  3. [left,right]=[3,4],因此下标中点mid=(left+right)/2=3。由于A[mid]=A[3]=8,而8<11,说明需要在[mid+1,right]范围内继续查找,因此令left=mid+1=4。
    在这里插入图片描述
  4. [left,right]=[4,4],因此下标中点mid=(left+right)/2=4。由于A[mid]=A[4]=11,而11==11,说明找到了欲查询的数字,因此结束算法,返回下标4。
    在这里插入图片描述
    查询34,令left=1、right=10
  5. [left,right]=[1,10],因此下标中点mid=(left+right)/2=5。由于A[mid]=A[5]=15,而15<34,说明需要在[mid+1,right]范围内继续查找,因此令left=mid+1=6
    在这里插入图片描述
  6. [left,right]=[6,10],因此下标中点mid=(left+right)/2=8。由于A[mid]=A[8]=52,而52>34,说明需要在[left,mid-1]范围内继续查找,因此令right=mid-1=7
    在这里插入图片描述
  7. [left,right]=[6,7],因此下标中点mid=(left+right)/2=6。由于A[mid]=A[6]=21,而21<34,说明需要在[mid+1,right]范围内继续查找,因此令left=mid+1=7
    在这里插入图片描述
  8. [left,right]=[7,7],因此下标中点mid=(left+right)/2=7。由于A[mid]=A[7]=33,而33<34,说明需要在[mid+1,right]范围内继续查找,因此令left=mid+1=8
    在这里插入图片描述
  9. [left,right]=[8,7],由于left>right,因此查找失败,说明序列中不存在34
    在这里插入图片描述
    二分查找的过程与序列的下标从0开始还是从1开始无关。
#include <stdio.h>
//二分区间为左闭右闭的[left,right],传入的初值为[0,n-1]
int binarySearch(int A[], int left, int right, int x)
{
	int mid; //mid为left和right的中点
	while(left <= right) //如果left>right说没办法形成闭区间
	{
		mid = (left + right)/2; //取中点
		if(A[mid] == x)
			return mid; //找到x,返回下标
		else if(A[mid] > x) //中间的数大于x
		{
			right = mid - 1; //往左子区间[left,mid-1]查找
		}
		else //中间的数小于x
		{
			left = mid + 1; //往右子区间[mid+1,right]查找
		}
	}
	return -1; //查找失败,返回-1
}

int main()
{
	const int n = 10;
	int A[n] = {1, 3, 4, 6, 7, 8, 10, 11, 12, 15};
	printf("%d %d\n", binarySearch(A, 0, n-1, 6), binarySearch(A, 0, n-1, 9));
	return 0;
}
3 -1

求序列中第一个大于等于x的元素位置

假设当前的二分区间为左闭右闭区间[left,right],那么可以根据mid位置处的元素与欲查询元素x的大小来判断应当往哪个子区间继续查找:

  1. 如果A[mid]>=x,说明第一个大于等于x的元素的位置一定在mid处或者mid左侧,应往左子区间[left,mid]继续查询,即令right=mid
    在这里插入图片描述
  2. 如果A[mid]<x,说明第一个大于等于x的元素的位置一定在mid的右侧,应往右子区间[mid+1,right]继续查询,即令left=mid+1
    在这里插入图片描述
//A[]为递增序列,x为欲查询的数,函数返回第一个大于等于x的元素的位置
//二分上下界为左闭右闭的[left,right],传入的初值为[0,n]
int lower_bound(int A[], int left, int right, int x)
{
	int mid; //mid为left和right的中点
	while(left < right) //对[left,right]来说,left==right意味着找到唯一位置
	{
		mid = (left + right) / 2; //取中点 
		if(A[mid >= x) //中间的数大于等于x
		{
			right = mid; //往左子区间[left, mid]查找
		}
		else //中间的数小于x
		{
			left = mid + 1; //往右子区间[mid+1, right]查找
		}
	}
	return left; //返回夹出来的位置
}

需要注意:

  1. 循环条件为left<right而非之前的left<=right
  2. 由于当left==right时while循环停止,因此最后的返回值既可以时left,也可以是right
  3. 二分的初始区间应当能覆盖到所有可能返回的结果

求序列中第一个大于x的元素的位置

假设当前区间为[left,right],那么可以根据mid位置处的元素与欲查询元素x的大小来判断应当往哪个子区间继续查找:

  1. 如果A[mid]>x,说明第一个大于x的元素的位置一定在mid处或mid的左侧,应往左子区间[left, mid]继续查询
    在这里插入图片描述
  2. 如果A[mid]<=x,说明第一个大于x的元素的位置一定在mid的右侧,应往右子区间[mid+1,right]继续查询
    在这里插入图片描述
//A[]为递增序列,x为欲查询的数,函数返回第一个大于x的元素的位置
//二分上下界为左闭右闭的[left,right],传入的初值为[0,n]
int upper_bound(int A[], int left, int right, int x)
{
	int mid; //mid为left和right的中点
	while(left < right) //对[left,right]来说,left==right意味着找到唯一位置
	{
		mid = (left + right) / 2; //取中点
		if(A[mid] > x) //中间的数大于x
		{
			right = mid; //往左子区间[left,mid]查找
		}
		else //中间的数小于等于x
		{
			left = mid + 1; //往右子区间[mid+1, right]查找
		}
	}
	return left; //返回夹出来的位置
}

模板

寻找有序列中第一个满足某条件的元素的位置

  1. 左闭右闭
//解决“寻找有序序列第一个满足某条件的元素的位置”问题的固定模板
//二分区间为左闭右闭的[left,right],初值必须能覆盖解的所有可能取值
int solve(int left, int right)
{
	int mid; //mid为left和right的中点
	while(left < right) //对[left,right]来说,left==right意味着找到唯一位置
	{
		mid = (left + right) / 2; //取中点
		if(条件成立) //条件成立,第一个满足条件的元素的位置<=mid
		{
			right = mid; //往左子区间[left,mid]查找
		}
		else //条件不成立,则第一个满足该条件的元素的位置>mid
		{
			left = mid + 1; //往右子区间[mid+1,right]查找
		}
	}
	return left; //返回夹出来的位置
}
  1. 左开右闭
//解决“寻找有序序列第一个满足某条件的元素的位置”问题的固定模板
//二分区间为左开右闭的(left,right]
//初值必须能覆盖解的所有可能取值,并且left比最小取值小1
int solve(int left, int right)
{
	int mid; //mid为left和right的中点
	while(left + 1 < right) //对(left,right]来说,left+1==right意味着找到唯一位置
	{
		mid = (left + right) / 2; //取中点
		if(条件成立) //条件成立,第一个满足条件的元素的位置<=mid
		{
			right = mid; //往左子区间[left,mid]查找
		}
		else //条件不成立,则第一个满足该条件的元素的位置>mid
		{
			left = mid; //往右子区间[mid,right]查找
		}
	}
	return right; //返回夹出来的位置
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阳光开朗男孩

你的鼓励是我最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值