什么是二分(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中的占比率很高,所以一定要好好学。
以上就是本片的所有内容,感谢观看!