2. 二分查找

整数二分

二分的本质不是单调性,有单调性一定可以二分,可以二分不一定有单调性 二分的本质是边界,假设给定一个区间,如果能够根据某个条件,将区间划分为左右两部分,使得左半边满足这个条件,右半边不满足这个条件(或者反之)。就可以用二分来查找左右两部分的边界点

主要思想:假设这组数据关键字已有序

1.寻找红色区间的右边界点 模板一

当l<r时不断循环

先取中间值mid=(l+r)/2

先判断mid是否满足条件(即某种性质),check(mid)

  • 若满足check(mid)为true,区间更新为[l,mid],即r=mid;
  • 若不满足,check(mid)为false,更新区间为[mid+1,r] ,即l=mid+1。(注意这里mid+1,是因为mid本身已不满足条件,只能右移一个位置去区间找答案。)

最后l=r,循环结束,输出分界点位置l(或r)

代码模板:

 
int binarysearch_1(int l,int r){
    while(l<r){
        int mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid+1;
    }
    return l;//return r;也可
}

2.寻找绿色区间的左边界点 模板二

当l<r时不断循环

先取中间值mid=(l+r+1)/2

  • 注意,当采用l = midr = mid - 1这种更新方式时,计算mid时,要加上1(向上取整),即mid = l + r + 1 / 2。否则,在l = r - 1时,计算mid时若不加1,则mid = l + r / 2 = l,这样更新l = mid,就是l = l,会导致死循环。所以要向上取整,采用mid = l + r + 1 / 2

先判断mid是否满足条件(即某种性质),check(mid)

  • 若满足check(mid)为true,区间更新为[mid,r],即l=mid;
  • 若不满足,check(mid)为false,更新区间为[l,mid-1] ,即r=mid-1。(注意这里mid-1,是因为mid本身已不满足条件,只能左移一个位置去区间找答案。)

最后l=r,循环结束,输出分界点位置l(或r)

代码模板:

int binarysearch_2(int l,int r){
    while(l<r){
        int mid=(l+r+1)/2;//需要加1,避免边界问题
        if(check(mid)) l=mid;
        else r=mid-1;
    }
    return l;
}

(AcWing 789.数的范围)

根据上述思想和模板,代码思路为:

对于模板一,check条件为x<=a[mid]mid=(l+r)/2,满足条件时x在mid左侧,更新r=mid,最终输出为起始位置

对于模板二,check条件为x>=a[mid]mid=(l+r+1)/2,满足条件时x在mid右侧,更新l=mid,最终输出为终止位置

最终依次输出各模板的l(或r)

#include <iostream>
#include <algorithm>
using namespace std;
int n;//数组个数
int a[N];
int m,x;//m为询问个数,x为询问数
int main(){
    cin>>n>>m;
    for(int i=0;i<n;i++) cin>>a[i];
    while(m--){
        cin>>x;
        int l=0,r=n-1;
        while(l<r){
            int mid=(l+r)/2;
            if(a[mid]>=x) r=mid;
            else l=mid+1;
        }
        if(a[l]!=x) cout<<"-1 -1"<<endl;//未找到
        else{
            cout<<l<<" ";//起始位置
            int l=0,r=n-1;
            while(l<r){
                int mid=(l+r+1)/2;
                if(a[mid]<=x) l=mid;
                else r=mid-1;
            }
            cout<<l<<endl;//终止位置
        }
    }
    return 0;
}
 
 

浮点数二分

主要思想:相比整数二分,浮点数二分无需考虑边界问题,比较简单。当二分的区间足够小时,可以认为已经找到了答案,如当r - l < 1e-6(实际上就是区间长度比较小就近似认为是一个数) ,停止二分;或者直接迭代一定的次数,比如循环100次后停止二分

如问题:给定一个浮点数n,求他的二次方根(三次方根类似 )

代码思路:使用整数二分中的哪个模板都可。这里使用模板一,check条件为xa[mid],mid=(l+r)/2,满足条件更新r=mid

注意:r不能取小于1的数(小于1的数开方反而更大).若x=0.04,取r=x,实际结果应为0.2,但算法会在0~0.04中找答案,无法找出答案,故应令r=max(1,x);

#include <iostream>
#include <algorithm>
using namespace std;
double x;
int main(){
    cin>>x;
    double l=0,r=max(1,x);
    while(l<r){
        int mid=(l+r)/2;
        if(mid*mid >=x) r=mid;
        else l=mid;
    }
    cout<<l;
    return 0;
}