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函數。