入门二分查找——浅谈一些技巧

本文详细介绍了C++STL中的binary_search、lower_bound和upper_bound函数,以及手写二分搜索的方法,包括记录答案法和不记录答案法。同时讨论了处理边界问题,特别是防止死循环的技巧。
摘要由CSDN通过智能技术生成

目录:

一:C++STL中二分的三个函数及其使用方法

二:手写二分搜索的方式以及一些技巧

三:比较常见的边界问题

:C++STL中二分的三个函数及其使用方法:

binary_search(arr.begin(),arr.end(),value)在arr中的指定范围找value,存在则返回1,否则返回0  (bool)。

lower_bound(arr.begin(),arr.end(),value)),在arr中找到一个适合插入value的位置,并且返回值是 第一次满足要求的元素地址,例如在1,3,4,2,5,7,8,6,2,2,1中,若要插入2,则第一次满足条件是在(4,2)之间,因此会返回2的地址。(返回偏后的地址)

upper_bound(arr.begin(),arr.end(),value)),在arr中找到一个适合插入value的位置,并且返回值是 最后一次满足要求的元素地址,例如在1,3,4,2,5,7,8,6,2,2,1中,若要插入2,则最后一次满足条件是在(2,1)之间,因此回返回最后一个元素(1)的地址。(返回偏后的地址)

TIP:

① 如何通过lower_bound或者upper_bound找到符合条件的元素的下标索引

     通过lower_bound()-arr即可得到下标值。

②如何通过lower_bound与upper_bound的组合找到某个元素在原数组中出现的次数

第一步:首先对原数组进行sort排序。

第二步:lower_bound(arr.begin(),arr.end(),value))---upper_bound(arr.begin(),arr.end(),value))即为答案。

:手写二分搜索的方式以及一些技巧:

①记录答案法:

int n, l, r, ans;
bool check(int x)
{
	//判断函数
}
int main()
{
	l = 0, r = 100;
	while (l <= r) //记录答案法
	{
		int mid = (l + r) >> 1;
		if (check(mid))
		{
			ans = mid;
			r = mid;
		}
		else l = mid + 1;
	}
	return 0;
}

注意这里的l<=r中的"=",即while结束后,l会大于r。当我们二分的答案mid正确时,每次都视其为最优解(ans=mid)。这种记录答案的办法是为了避免当r和l相等的时候,相连r(或l)的情况。

我们不断放小lr,此时假设l=2,r=3,并假设mid=4时答案成立。此时来到mid=(2+3)/2,即5/2,经过向下取整后,mid=2,答案不成立,因此l会来到3,再做一次取mid操作,此时mid=3,答案不成立。假设我们没有ans=mid的操作,那么这次循环结束后,r还在3,l走到了4。此时我们无法得知l和r哪一个是答案,这就是答案相连的情况。

除此之外,我们来看为什么l<=r而不是l<r。我们再做一次假设,与上述假设差不多,假设l=2,r=3,并且mid=3时答案成立,此时while的的条件为while(l<r) mid之后,mid=2,答案不成立,l来到3,此时循环结束,可是这时候取mid=3的时候答案是成立的,因此如果l=r时循环已经退出了,有可能会漏掉一些答案。

简单来说,当lr的处理均为mid+(-)1时,循环条件为l<=r

②不记录答案,r就是最优解:

int n, l, r;
bool check(int x)
{
	//判断函数
}
int main()
{
	l = 0, r = 100;
	while (l < r) //不记录答案,r就是最优解
	{
		int mid = (l + r) >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	return 0;
}

 

每次mid为答案时,r=mid,那么mid就是答案。不断放小lr,假设某个时刻l=2r=3,并且mid=2是不是答案。此时r=3是上一个答案,在这次循环中,mid=2,不是答案,l走到3,即l=r,循环退出,当循环结束之后,r就是答案了。(注:此时l=r,因此l也是答案,但是我们按照代码中r=mid作为标准

 

注意:为什么l<r时,不能写成r=mid-1?

原因是当我们没有记录当前答案时,如果mid为答案,我们取r=mid-1会使得原答案消失。

 

 

三:比较常见的边界问题(导致死循环):

有时,我们写出来的二分,往往会不注意l而导致死循环。下面我们来看看这种会死循环的代码:

int n, l, r;
bool check(int x)
{
	//判断函数
}
int main()
{
	l = 0, r = 100;
	while (l < r) //用二分找到最大的解
	{
		int mid = (l + r) >> 1;
		if (check(mid)) l = mid;
		else r = mid - 1;
	}
	return 0;
}

这种求最大的二分过程,死循环的原因出现在l的取值。我们知道,每次二分以后,lr之间必须有一个要发生变化,同样,我们不断放小lr来看看这种极端情况。假设l=2r=3,并且mid=2时,答案成立。

由于这次循环中,(l+r)>>1==5/2,经过向下取整后得到mid=2,而mid=2时答案成立,因此l=mid,注意,看到这里是不是发现问题了,从这个时候开始,l一直卡在2这个值不会变,而r也一直卡在3这里,因此l<r永远成立,死循环了。

那么,我们如何解决这种问题呢? 很简单,我们只要不让计算机向下取整就可以避免了,我们让计算机向上取整,这里有两种方法:

①利用c++的函数ceil(value) ,这里value==(l+r)>>1。如果计算的结果为小数,ceil每次取更大的整数,例如:当value==1.3时,ceil会取2value==1时,ceil1value==-1.3时,ceil-1

因此我们每次循环中的mid的取值部分写成 int mid=ceil((l+r)>>1)即可。

人工向上取整,直接写成int mid=(l+r+1)>>1 这里的证明部分就不多说了。

补充:这种写法最后l就是答案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值