一篇文章带你学会二分(二分查找、二分答案)

什么是二分(Bisection Method)?

   二分的含义

众所周知,二分法是一个非常重要的算法,它可以在某些情况中代替遍历,使我们的时间复杂度降低。二分法分为二分查找二分答案,他们分别应用于不同的场景,其基本思想是对于某个已知左端点和右端点的区间进行二分,以此达到最终效果。时间复杂度为O(log n)。

   二分的思路

    二分,顾名思义,是从一个区间[ l , r ] 中,得到一个mid,然后跟具体情况改变 l 和 r 的值。即mid=(l+r)/2。得到中间值后,对相应的判断。

如上图所示,查找8时,mid=(l+r)/2=5(使用整数除法时),然后判断mid<8,L右移到5,然后(l+r)/2=8,找到,退出。

二分法——二分查找(binary search)

    什么是二分查找?

     二分查找,即为用二分法查找一个提出的数。使用的场景和次数也相对较高。

    原理和模版

假设这里给出了一段区间[ l , r ],如何用二分查找找出数w?其实so easy,每次求出区间的中间数mid,然后判断mid与w的大小关系。如果比w大,r=mid-1,。如果比w小,l=mid+1。这是为,如果mid大于了w,那么说明比mid大的数都不可能小于w,反之亦然。

     什么?你说你没听懂?

    没关系,上模板!

#include<bits/stdc++.h>
using namespace std;
int main(){
	int l=1,r=n,best=-1;//best是保存最大值的变量 
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(mid>XXX)
		{
			r=mid-1;
		}
		else if(mid<XXX)
		{
			l=mid+1;
		}
		else
		{
			best=mid;
			break;
		}
	}
	return 0;
}

怎么样?看懂了吗?

来,给题例题试试水!

小明有一个有序数列a,他想要快速找出其中的数x,请你帮帮他。

|a|<=100000,a[i]<=100000,x<=最大的a[i]。

代码如下:

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)
	{
		scanf("%d",&a[i]);
	}
	int l=1,r=n,best=-1; 
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid]>m)
		{
			r=mid-1;
		}
		else if(a[mid]<m)
		{
			l=mid+1;
		}
		else
		{
			best=mid;
			break;
		}
	}
	printf("%d\n",best);
	return 0;
}

lowerbound和upperbound

如果你听懂了最基础的二分查找,那来听听我们还会用到的lowerbound和upperbound,他们是二分查找中的一个很重要的分支,即为第一个和最后一个大于等于x的值。这两个函数和普通二分的区别是:它们在判断时会把“=”分支写进另外两个里,这是因为如果两个值相等,那它前面或后面也可能有相等的值。

什么?你说你还没听懂?

没关系,再上模板!

模板

lowerbound:

#include<bits/stdc++.h>
using namespace std;
int lowerbound(int p)
{
	int l=1,r=n,best=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(mid>=p)//应为要第一个,所以“=”也算进去
		{
			best=mid;//只要时大于等于,best=mid
			r=mid-1;
		}
		else
		{
			l=mid+1;
		}
	}
	return best;
}
int main(){
	printf("%d",lowerbound(x));
	return 0;
}

upperbound:

#include<bits/stdc++.h>
using namespace std;
int upperbound(int p)
{
	int l=1,r=n,best=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(mid<=p)
		{
			best=mid;
			l=mid+1;
		}
		else
		{
			r=mid-1;
		}
	}
	return best;
}
int main(){
	printf("%d",upperbound(x));
	return 0;
}

例题

小明有一个有序序列a,他想知道a中第一个大于等于b和最后一个大于等于b的

保证题目合法

|a|<=100000

代码:

#include<bits/stdc++.h>
using namespace std;
int a[100010];
int n,p;
int lowerbound(int p)
{
	int l=1,r=n,best=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid>=p)
		{
			best=mid;
			r=mid-1;
		}
		else
		{
			l=mid+1;
		}
	}
	return a[best];
}
int upperbound(int p)
{
	int l=1,r=n,best=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(a[mid]<=p)
		{
			best=mid;
			l=mid+1;
		}
		else
		{
			r=mid-1;
		}
	}
	return a[best];
}
int main(){
	scanf("%d%d",&n,&p);
	for(int i=1; i<=n; i++)
	{
		scanf("%d",&a[i]);
	}
	printf("%d %d",lowerbound(p),upperbound(p));
	return 0;
}

二分法——二分答案

    什么是二分答案?

      二分答案与二分查找的区别是,我们并不知道要查找的数,所以我们要对每个mid进行判断并找到最优解。也就是说,我们的判断是要根据题目来写的。

还听不懂?再上!!!

模板与例题

#include<bits/stdc++.h>
using namespace std;
(int/void/bool/...) XXX(...)
{
	...
}
int main(){
	int l=...,r=...,best=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(...)
		{
			l=mid+1;
		}
		else
		{
			r=mid-1;
		}
	}
	return 0;
}

可以看出,和二分查找还是有一点像的。

小明有一个包含n个数的有序数列,他想知道从中删掉m(m<=n)个数(不包含起点和终点),使得这个数列中每两个相邻的数的差的绝对值最小的那个尽量大。

代码:

#include<bits/stdc++.h>
using namespace std;
int a[50010];
int L,N,M;
bool judge(int mid)
{
	int k=0;
	int p=0;
	for(int i=1; i<=N+1; i++)
	{
		if(a[i]-a[p]>=mid)
		{
			p=i;
		}
		else
		{
			k++;
			continue;
		}
	}
	if(k>M)
	{
		return 0;
	}
	return 1;
}
int main(){
	scanf("%d%d%d",&L,&N,&M);
	for(int i=1; i<=N; i++)
	{
		scanf("%d",&a[i]);
	}
	a[0]=0;
	a[N+1]=L;
	int l=1,r=L,best=-1;
	while(l<=r)
	{
		int mid=(l+r)/2;
		if(judge(mid))
		{
			l=mid+1;
			best=mid;
		}
		else
		{
			r=mid-1;
		}
	}
	printf("%d",best);
	return 0;
}

二分法——浮点二分

浮点二分是一个比较特殊的种类,它的二分是可能为小数的,虽然做法没有太大的区别,唯一要注意的就是l和r时等于mid而不是mid+1或mid-1。

总结

二分法是我们在c++中很重要的一个点,在csp中的占比率很高,所以一定要好好学。

以上就是本片的所有内容,感谢观看!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值