二分搜素——(lower_bound and upper_bound)

因为每个人二分的风格不同,所以在学习二分的时候总是被他们的风格搞晕。有的人二分风格是左闭右开也就是[L,R),有的人是左开右闭的(L,R]。

二分的最基本条件是,二分的序列需要有单调性

下面介绍的时候用v来代表我们二分的目标,用第一个大于v,第一个大于等于v【升序】,最后一个小于v,最后一个小于等于v【降序】来描述,这里可以看到我即将要介绍的4种二分搜索。

1.第一个大于等于v

这就是我们常说的lower_bound()了,这是系统里面自带的库函数,下面是这个函数的原型:

ForwardIterator  lower_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp)

其中first代表左边界迭代器,last代表右边界迭代器(注意左闭右开),val代表你要搜索的值,comp代表排序规则(这个参数在你对非结构体数组二分的时候并不需要,有默认规则)。

实例:

int a[100]={1,2,3,3,4,5,5,6,8,9,22},n;
    while(cin>>n){
        int p=lower_bound(a,a+11,n)-a;
        //如果a是vector,那么lower(a.begin(),a.end(),v)-a.begin();
        //你也可以在指定在[L,R)区间内二分lower_bound(a.begin()+L,a.begin()+R,v)-a.begin(),数组也是同理的
        cout<<p<<endl;//这里输出的是第一个大于等于n的数的下标
    }

接下来我们手写一个lower_bound(),风格为[L,R)。

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a[100]={1,2,3,3,4,5,5,6,8,9,22},v;
    while(cin>>v){
        int L=0,R=11;
        while(L<R){
            int M=(L+R)/2;
            if(a[M]>=v) R=M;
            else L=M+1;
        }
        cout<<L<<endl;
    }    
    return 0;
}

注:1.当a[M]>=v时,由于a[R]是第一个大于等于v的数,那么R最大只能是M

  2.当a[M]<v时,说明[L,M)区间内的数都是小于v的,L作为最后的答案最小只能是M+1

2.第一个大于v

这就是我们常说的upper_bound()了,这是系统里面自带的库函数,下面是这个函数的原型:

ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,const T& val, Compare comp);

其中first代表左边界迭代器,last代表右边界迭代器(注意左闭右开),val代表你要搜索的值,comp代表排序规则(这个参数在你对非结构体数组二分的时候并不需要,有默认规则)

实例:

int a[100]={1,2,3,3,4,5,5,6,8,9,22},n;
while(cin>>n){
    int p=upper_bound(a,a+11,n)-a;
    //如果a是vector,那么upper_bound(a.begin(),a.end(),v)-a.begin();
    //你也可以在指定在[L,R)区间内二分upper_bound(a.begin()+L,a.begin()+R,v)-a.begin(),数组也是同理的
    cout<<p<<endl;//这里输出的是第一个大于等于n的数的下标
}

接下来我们手写一个upper_bound()

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a[100]={1,2,3,3,4,5,5,6,8,9,22},v;
    while(cin>>v){
        int L=0,R=11;
        while(L<R){
            int M=(L+R)/2;
            if(a[M]>v) R=M;
            else L=M+1;
        }
        cout<<L<<endl;
    }    
    return 0;
}

注:1.当a[M]>v时,由于a[R]是第一个大于v的数,那么R最大只能是M

  2.当a[M]<=v时,说明[L,M)区间内的下标都是小于等于v的,L作为最后的答案最小只能是M+1

3.最后一个小于等于v

当数组为降序的,使用lower_bound就是返回第一个小于等于下标,若一开始数组是升续的时候,那么应该先reverse一下,再用lower_bound返回下标p,则在原数组中的下标为n-p-1(假设数组有n个元素)。

这里来介绍一下如何在如果手写一个last_less_equal()。和lower_bound二分区间[L,R)左闭右开不同,last_less_equal()的二分区间为(L,R]右闭左开。

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a[100]={1,2,3,3,4,5,5,6,8,9,22},v;
    while(cin>>v){
        int L=-1,R=10;
        while(L<R){
            int M=(L+R+1)/2;
            if(a[M]<=v) L=M;
            else R=M-1;
        }
        cout<<L<<endl;
    }    
    return 0;
}

注:1.当a[M]<=v时,由于a[L]是最后一个小于等于v的数,那么L最小只能是M。

  2.当a[M]>v时,说明(L,M]区间内的下标都是大于v的,R作为最后的答案最大只能是M-1。

4.最后一个小于v

 上面说过了,当数组为降序的,使用upper_bound就是返回第一个大于下标,若一开始数组是升续的时候,那么应该先reverse一下,再用upper_bound返回下标p,则在原数组中的下标为n-p-1(假设数组有n个元素)。

    这里来介绍一下如何在如果手写一个last_less()。和upper_bound二分区间[L,R)左闭右开不同,last_less_equal()的二分区间为(L,R]右闭左开。

#include<bits/stdc++.h>
using namespace std;
int main(){
    int a[100]={1,2,3,3,4,5,5,6,8,9,22},v;
    while(cin>>v){
        int L=-1,R=10;
        while(L<R){
            int M=(L+R+1)/2;
            if(a[M]<v) L=M;
            else R=M-1;
        }
        cout<<L<<endl;
    }    
    return 0;
}

注:1.当a[M]<n时,由于a[L]是最后一个小于v的数,那么L最小只能是M。

  2.当a[M]>=n时,说明(L,M]区间内的下标都是大于等于v的,R作为最后的答案最大只能是M-1。

我们发现lower_bound()和upper_bound()的M=(L+R)/2,而last_less()和last_less_equal()的M=(L+R+1)/2,(L+R)/2和(L+R+1)/2的区别在于前者是向下取整,后者是向上取整,这和我们定义L或者R是实际的答案有关。

猜你喜欢

转载自blog.csdn.net/qie_wei/article/details/81110677