[算法笔记]二分查找

1.二分查找

经典问题:如何再一个严格递增的序列A中找出给定的数x

二分查找:二分查找是基于有序序列的查找算法,该算法一开始令[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]查找
时间复杂度O(logn)

int binarySearch(int A[], int left, int right, int x){
    int mid;
    while(left <= right){
        mid = (left + right) / 2;
        if(A[mid] == x) return mid;
        else if(A[mid] > x) right = mid - 1;
        else left = mid + 1;
    }
    return -1;
}

注意:如果二分上届超过int类型的一半,那么当欲查询元素再序列较靠后的位置时,语句mid = (left + right) / 2;可能会导致溢出,此时可以使用mid = left + (right - left) / 2这条等价语句避免溢出。

问题二:如果序列A中的元素可能重复,那么如何对给定的欲查询元素x,求出序列中第一个大于等于x的元素的位置L以及第一个大于x的元素位置R,这样元素x再序列中的存在区间就是[L,R)。
如对下标从0开始,有5个元素的序列{1,3,3,3,6}来说,查询3,则L=1,R=4,查询6,则L=4,R=5。
查询8 则L=R=5。
如果序列中没有x,那么L与R可以理解为假设序列中存在x,那么x应当存在的位置。

1)先考虑第一个大于等于x的元素,做法与之前类似,假设当前的二分区间为[left,right],那么可以根据mid位置处的元素与欲查询元素x的大小来判断应该往哪个子区间继续查找。
如果A[mid]>=x,说明第一个大于等于x的元素的位置一定在mid处或mid的左侧,向左区间[left,mid]继续查找,令right=mid
2)如果A[mid]<x,说明第一个大于等于x的元素一定在mid的右侧,应该往右区间[mid+1,right]继续查找,即令left=mid+1。
代码:

int lower_bound(int A[], int left, int right, int x) {
	int mid;
	while (left < right) {//left==right意味找见唯一位置
		mid = (left + right) / 2;
		if (A[mid] >= x)right = mid;
		else left = mid + 1;
	}
	return left;//执行到最后,x如果存在left一定是第一个x
}

这里的循环条件为left<right,并非left<=right,是因为在上题中,元素不存在返回-1,这样当left>right时[left,right]就不是闭区间,以此作为元素不存在的判定原则,因此left<=right一直执行,如果想要返回第一个大于等于x元素的位置,就不需要判断x的本身是否存在,因为就算不存在,返回的也是如果存在,它应该在的位置,当left=right时,刚好能够找出唯一的位置,就是需要的结果,返回值既可以返回right,也可返回right。

3)再找出序列中第一个大于x的元素的位置,做法类似。

int upper_bond(int A[], int left, int right, int x) {
	int mid;
	while (left < right) {
		mid = (left + right) / 2;
		if (A[mid] > x)right = mid;
		else left = mid + 1;
	}
	return left;
}

lower_bound()与upper_bound()x`都在解决,寻找有序序列中第一个满足某条件的元素位置。lower_bound找的时第一个 值大于等于x的元素的位置,upper_bond找的时第一个 值大于x的元素的位置
解决此类问题的模板:
比如如果要求最后一个满足条件C的元素,可以求第一个不满足条件C的元素,然后令下标减一。

int solve(int left,int right){左闭右闭区间
    int mid;
    while(left < right){//left==right说明找到唯一的位置
        mid = (left + right) / 2;//取中点
        if(条件成立){//条件成立,第一个满足条件的元素的位置<=mid
            right = mid;
        }
        else left = mid + 1;//条件不成立,第一个满足条件的元素>mid
    }
    return left;
}
int solve(int left,int right){左开右闭区间
    int mid;
    while(left + 1 < right){//left==right说明找到唯一的位置
        mid = (left + right) / 2;//取中点
        if(条件成立){//条件成立,第一个满足条件的元素的位置<=mid
            right = mid;
        }
        else left = mid;//条件不成立,第一个满足条件的元素>mid
    }
    return right;
}

2.二分法拓展

给定一个定义在[L,R]上的单调函数f(x),求方程f(x)=0的根。
如果f(mid)>0说明根应该在子区间[left,mid]中
如果f(mid)<0说明根应该在子区间[mid,right]中

例如计算根号2的近似值等价于求f(x)=x^2-2=0在[1,2]范围内的根。
代码如下:

const int eps = 1e-5;//精度10^-5
double f(double x) {
	return x * x - 2;
}
double solve(double L, double R) {
	double left = L, right = R, mid;
	while (right - left > eps) {
		mid = (left + right) / 2;
		if (f(mid) > 0)right = mid;
		else mid = left;
	}
	return mid;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值