算法思想来源:Acwing
简介:如果我们有一个性质,能将一个区间划分成两部分,二分能将这个性质的边界求出来。
如图,两个箭头指向的两个点就是二分的两个边界点。
性质: 能二分的区间不一定具有单调性,但是具有单调性的区间一定能二分。
过程:例如我们要从一组从大到小排列的一组数中找出数x,那么我们先让这个数与区间的中间点的值作比较,如果中间值大于x,那么我们就去中间点的值左边的区间找,继续选中间点的值与x比较继续找,反之,如果中间点的值小于x,那么我们就去中间点的值的右边去找,继续选中间点的值与x比较继续找。就这样一直分下去,直到找到想要的值。
举个实例:比如我们要从1-100中找出一个数字,例如80,按照二分查找的方法,先查100/2 = 50,50比80小,那么再查(100+50)/2=75,还是比80小,再查(100+75)/2=87,比80大,接下来再取75-87的中间数与80进行比较,最后查到80。
模板:
二分的话有两个模板,一个是求上图红色区间的边界点的,一个是求上图绿色区间的边界点的,两个模板是不一样的。
int bsearch1(int l,int r)
{
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;//如果满足红色区间的条件,那就去右边找边界点,让l=mid,r依旧是r,因为mid这个点满足条件,所以也算在右边区间范围内
else r=mid-1;//如果不满足红色区间的条件,那就去左边找边界点,让r=mid-1,l依旧是l,因为mid这个点不满足条件,所以不算在右边区间范围内
}
}
int bsearch2(int l,int r)
{
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;//如果满足绿色区间的条件,那就去左边找边界点,让r=mid,l依旧是l,因为mid这个点满足条件,所以也算在左边区间范围内
else l=mid+1;//如果不满足绿色区间的条件,那就去右边找边界点,让l=mid+1,r依旧是r,因为mid这个点不满足条件,所以不算在右边区间范围内
}
}
我们可以发现第一个模板与第二个模板有些不同,主要体现在求中值点的时候,为什么第一个模板求中值点时l+r除以2的时候还要提前加1呢?
因为l+r除以2是向下取整的,如果l正好等于r-1,如果不加1,mid就会等于l,程序就会在if(check(mid)) l=mid;这条语句中死循环下去。
例题来源:Acwing
#include <iostream>
#include <algorithm>
using namespace std;
const int maxn=1e5+7;
int n,q,x;
int a[maxn];
int main()
{
cin>>n>>q;
for(int i=0;i<n;i++) scanf("%d",&a[i]);
while(q--)
{
scanf("%d",&x);
int l=0,r=n-1;
while(l<r)
{
int mid=(l+r)>>1;
if(a[mid]>=x) r=mid;//找到大于等于x和小于x的边界值,即所求元素的起始位置
else l=mid+1;
}
if(a[l]!=x) printf("-1 ");
else printf("%d ",l);
l=0,r=n-1;
while(l<r)
{
int mid=l+r+1>>1;
if(a[mid]<=x) l=mid;//找到小于等于x和大于x的边界值,即所求元素的终止位置
else r=mid-1;
}
if(a[l]!=x) printf("-1 ");
else printf("%d",l);
puts("");
}
return 0;
}