• 二分查找(Binary Search)


    1.递归实现

    int binarySearchRecursive(int a[],int low,int high,int key){
        if(low>high)
            return -(low+1);
    
        int mid=low+(high-low)/2;
        if(key<a[mid])
            return binarySearchRecursive(a,low,mid-1,key);
        else if(key > a[mid])
            return binarySearchRecursive(a,mid+1,high,key);
        else
            return mid;
    
    }
    
    int binarySearchRecursive(int a[],int n,int key){
        return binarySearchRecursive(a,0,n-1,key);
    }

    2.非递归实现

    int binarySearch(int a[],int n,int key){
        int low=0,high=n-1;
        int mid;
        while(low<=high){
            mid=low+(high-low)/2;
            if(key<a[mid])
                high=mid-1;
            else if(key>a[mid])
                low=mid+1;
            else
                return mid;
        }
        return -(low+1);
    }

    3.二分查找的一种版本,现实需求中可能需要查找“在不破坏有序的状态的原则下,可插入key的第一个位置”。

    /*返回递增数组中第一个不小于key的数的索引,即在不破坏排序状态的原则下,可插入key的第一个位置。
    算法思想:循环不变式为a[low]<key&&a[high]>=key,
    所以当low+1==high && high<n时,high就应该是第一个大于等于key的数的索引;
    但是当high==n,这时就可以判断数组中没有大于等于key的值,则插入位置为high,返回-(high+1);*/
    int lowerBound(int a[],int n,int key){
        int low=-1,high=n;//假设n>=0, a[-1]<key&&a[n]>=key(但是程序并不访问这两个假想的元素)
        int mid;
        while(low+1!=high){
            mid=low+(high-low)/2;
            if(a[mid]<key)
                low=mid;//修正low,确保满足循环不变式中a[low]<key
            else
                high=mid;//修正high,确保满足循环不变式中a[high]>=key
        }
        int index=high;//第一个大于等于key的数的索引
    
        /*判断第一个大于等于key的数a[index]是否存在数组中*/
        if(index>=n||a[index]!=key)//不存在
            index=-(high+1);//修正index为负的插入位置的后一个位置
    
        return index;
    }

    4.二分查找的一种版本,现实需求中可能需要查找“在不破坏有序的状态的原则下,可插入key的最后一个位置”。

    /*upperBound试图在已排序数组中寻找可插入key的最后一个合适的位置。
    算法思想:循环不变式为a[low]<=key&&a[high]>key,
    所以当low+1==high && low>=0时,low就应该是第一个大于key的数的索引;
    但是当low<0,这时就可以判断数组中没有小于等于key的值,则插入位置为low+1,返回-(low+1+1)*/
    int upperBound(int a[],int n,int key){
        int low=-1,high=n;//假设n>=0, a[-1]<=key&&a[n]>key(但是程序并不访问这两个假想的元素)
        int mid;
        while(low+1!=high){
            mid=low+(high-low)/2;
            if(a[mid]<=key)
                low=mid;//修正low,确保满足循环不变式中a[low]<=key
            else
                high=mid;//修正high,确保满足循环不变式中a[high]>key
        }
        int index=low+1;//第一个大于key的数的索引
    
        /*判断最后一个小于等于key的数a[low]是否存在数组中*/
        if(low<=-1||a[low]!=key)//不存在
            index=-(low+1+1);//修正index为负的插入位置的后一个位置
    
        return index;
    }

    幸运的是,STL在<algorithm>中实现了这些算法的泛型版本。对应的函数分别为:binary_search, lower_bound, upper_bound

    其内部实现非常精妙,详见侯捷的《STL 源码剖析》。当然思想与上文实现大同小异,但是速度方面有待验证。

    此外,C库函数也提供了void* bsearch(const void *key, const void *base, size_t n, size_t size, int (*com) (const void *first, const void *second) )。

    下面给出以上函数的例子:

    #include<cstdlib>
    #include<iostream>
    #include<algorithm>
    #include<ctime>
    using namespace std;
    
    //print a array with macro
    #define printArray(arr,n) for(int i=0;i<n;i++){\
            cout<<a[i]<<' ';\
        }\
        cout<<endl<<endl;\
    
    
    //compare function greater
    int greater(const void *first,const void *second){
        int _first=*static_cast<const int*>(first);
        int _second=*static_cast<const int*>(second);
        if(_first>_second)
            return 1;
        else if(_first<_second)
            return -1;
        else
            return 0;
    }
    
    
    int main()
    {
        srand(time(0));
        const int n=10;
        int a[n];
        for(int i=0;i<n;i++){
            a[i]=rand()%20;
        }
        printArray(a,n);
    
        sort(a,a+n);
        printArray(a,n);
    
        int b;
        b=rand()%20;
         //b=a[0];//test 1
        // b=a[n-1];//2
        printf("searching %d ...\n",b);
        bool found=binary_search(a,a+n,b);
        if(found){//found
            cout<<"found"<<endl;
        }
        else{
            cout<<"no found"<<endl;
        }
    
        cout<<"\nbsearch"<<endl;
        void* p=bsearch(&b,a,n,sizeof(int),::greater);
        if(p!=NULL){//found
            cout<<"found "<<*static_cast<int*>(p)<<endl;
        }
        else{
            cout<<"no found"<<endl;
        }
    
        cout<<"\nbinarySearchRecursive"<<endl;
        int index=binarySearchRecursive(a,n,b);
        if(index>=0){
            cout<<"found! And index="<<index<<endl;
        }
        else{
            cout<<"no found! But "<<b<<" should be insert at "<<-index-1<<endl;
        }
    
    
        cout<<"\nbinarySearch"<<endl;
        index=binarySearch(a,n,b);
        if(index>=0){
            cout<<"found! index="<<index<<endl;
        }
        else{
            cout<<"no found! But "<<b<<" should be insert at "<<-index-1<<endl;
        }
    
        int* bound=NULL;
        bound=lower_bound(a,a+n,b);
        cout<<"lower bound: ";
        if(bound!=NULL){//exist
            cout<<*bound;
        }
        else{
            cout<<"no exist";
        }
        cout<<endl;
    
        bound=upper_bound(a,a+n,b);
        cout<<"upper bound: ";
    
        if(bound!=NULL&&bound<a+n){//exist. 
            /*这里需要检查bound是否在数组中,因为最大值的upper_bound返回其下一个位置,即a+n。
            而lower_bound一定指向数组外面。*/
    
            cout<<*bound;
        }
        else{
            cout<<"no exist";
        }
        cout<<endl;
    
        cout<<"\nlowerBound"<<endl;
        int lIndex=lowerBound(a,n,b);
        if(lIndex>=0){
            cout<<"found! The index of lower bound is "<<lIndex<<endl;
        }
        else{
            cout<<"no found! The index of lower bound is "<<-lIndex-1<<endl;
        }
    
    
        cout<<"\nupperBound"<<endl;
        int uIndex=upperBound(a,n,b);
        if(uIndex>=0){
            cout<<"found! The index of upper bound is "<<uIndex<<endl;
        }
        else{
            cout<<"no found! The index of upper bound is "<<-uIndex-1<<endl;
        }
    }

    那么这些算法的执行时间又是咋样的呢?

    我分别针对数组长度和选中概率(即,数组中数据被选中的概率)做了如下实验:

    //生成超过RAND_MAX的大随机数
    long bigRand(){
        return rand()*RAND_MAX+rand();
    }
    
    int main()
    {
        srand(time(0));
        //生成测试数组
        const int n=10000000;
        long* a=new long[n];
        for(int i=0;i<n;i++){
            a[i]=bigRand()%(n*4);//设定选中概率
        }
        
        //生成查找的数值
        const int times=10000;//测试次数
        long b[times];
        for(int i=0;i<times;i++){
            b[i]=bigRand()%(n*4);
        }
    
        clock_t start,end;
    
        //start=clock();
        sort(a,a+n);
        //end=clock();
        //printf("sort eclipse time: %.2f ms\n",double(end-start)*1000/CLOCKS_PER_SEC);
        
    
        start=clock();
        for(int i=0;i<times;i++){
            binarySearchRecursive(a,n,b[i]);
        }
        end=clock();
        printf("%-30s: %.2f ms\n","binarySearchRecursive",double(end-start)*1000/CLOCKS_PER_SEC);
    
    
        start=clock();
        for(int i=0;i<times;i++){
            binarySearch(a,n,b[i]);
        }
        end=clock();
        printf("%-30s: %.2f ms\n","binarySearch",double(end-start)*1000/CLOCKS_PER_SEC);
    
        //vector<int> vec(a,a+n);
    
        start=clock();
        for(int i=0;i<times;i++){
            //binary_search(vec.begin(),vec.end(),b[i]);
            lower_bound(a,a+n,b[i]);
        }
        end=clock();
        printf("%-30s: %.2f ms\n","binary_search",double(end-start)*1000/CLOCKS_PER_SEC);
    
    
        start=clock();
        for(int i=0;i<times;i++){
            bsearch(&b[i],a,n,sizeof(int),::greater);
        }
        end=clock();
        printf("%-30s: %.2f ms\n","bsearch",double(end-start)*1000/CLOCKS_PER_SEC);
    
    
        delete []a;
    }

    Debug模式

     选中概率为0.5

    选中概率0.5
      执行1000次的总时间(ms)
    数组长度 binarySearchRecursive binarySearch lower_bound bsearch
    10000000 13 5 28 12
    1000000 10 3 22 9
    100000 6 2 18 6
    10000 6 2 15 5

      

    选中概率为0.25

    选中概率0.25
      执行1000次的总时间(ms)
    数组长度 binarySearchRecursive binarySearch lower_bound bsearch
    10000000 15 5 28 13
    1000000 10 3 21 8
    100000 7 3 18 7
    10000 5 2 15 5

     选中概率为0.75的执行时间相似,这里就省略了。

    对比以上实验结果,可知Debug模式下binarySearch的执行效率最高,STL中以lower_bound为代表三算法效率最低。可能STL的算法没有被优化调用。

    由于实验的时候忘记调成release模式,差点得出了相反的结果。

    Release模式下binarySearchRecursive,binarySearch,lower_bound全为0,bsearch花费最多时间,可见STL三算法得到了优化调用,而因为正如《编程珠玑》中所述,C库函数的通用接口开销很大。

    综上,没有特殊需求,二分搜索可以直接使用STL中lower_bound,upper_bound,binary_search函数。

  • 相关阅读:
    es6之class继承
    es6-class基本语法
    vue-cli3搭建pwa项目(一)
    vue 组件的通信方式
    react之组件&props
    React之元素渲染
    JSX
    JSX
    在项目中怎么使用react
    认识react
  • 原文地址:https://www.cnblogs.com/freewater/p/2937886.html
Copyright © 2020-2023  润新知