二分查找可以说是最常用的有序表查找算法了。Knuth在TAOCP中提到,第一个二分查找程序在1946年已经公布,但是到了1962年才出现第一个没有BUG的二分查找程序,期间经历了16年的时间。我们今天就来深入学习一下这一经典的算法。需要说明的是,我们默认数组都是非递减序列。
先给出二分查找递归版本和非递归版本的基本代码。
#include<iostream> using namespace std; int BinarySearch(int a[],const int &x,int l,int r) { if(l<=r) { int m=(l+r)/2; if(a[m]==x) return m; else if(a[m]>x) return BinarySearch(a,x,l,m-1); else return BinarySearch(a,x,m+1,r); } else return -1; } int main() { int array[10]={10,20,30,40,50,60,70,80,90,100}; cout<<BinarySearch(array,90,0,9)<<endl; }
#include<iostream> using namespace std; int BinarySearch(int a[],const int &x,int l,int r) { while(l<=r) { int m=(l+r)/2; if(x==a[m]) return m; else if(x<a[m]) r=m-1; else l=m+1; } return -1; } int main() { int array[10]={10,20,30,40,50,60,70,80,90,100}; cout<<BinarySearch(array,95,0,9)<<endl; }STL已经实现了二分查找算法。binary_search在查找成功时返回1,查找失败时返回0。
#include<algorithm> #include<iostream> #include<vector> using namespace std; int main() { vector<int> v; for(int i=1;i<=20;i++) { v.push_back(i); } sort(v.begin(),v.end()); cout<<binary_search(v.begin(),v.end(),3)<<endl; cout<<binary_search(v.begin(),v.end(),30)<<endl; }
我们也可以用二分查找找寻边界值,也就是说在有序数组中找到正好大于/小于/大于等于/小于等于目标数的那个数。在STL中:
ForwardIter upper_bound(ForwardIter first,ForwardIter last, const _Tp& val)算法返回一个非递减序列[first, last)中第一个大于val的位置。
ForwardIter lower_bound(ForwardIter first,ForwardIter last,const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
实现这两个算法最重要需要注意的是查找成功以后要继续查找;只需要判断两种情况而不是前面的三种。
#include<iostream> #include<algorithm> using namespace std; int my_lower_bound(int *array,int size,int key) { int first=0,last=size-1; int middle,pos=size; //pos记录结果,没有找到时返回size while(first<last) { middle=(first+last)/2; if(array[middle]<key) { first=middle+1; } //如果array[middle]<key则middle一定不是解,first=middle+1 else { last=middle; pos=last; } //如果array[middle]>=key则middle可能是解,记录middle的值,last=middle } return pos; } int my_upper_bound(int *array,int size,int key) { int first=0,last=size-1; int middle,pos=size; //pos记录结果,没有找到时返回size while(first<last) { middle=(first+last)/2; if(array[middle]<=key) { first=middle+1; } //如果array[middle]<=key则middle一定不是解,first=middle+1 else { last=middle; pos=last; } //如果array[middle]>key则middle可能是解,记录middle的值,last=middle } return pos; } int main() { int array[10]={2,2,3,3,3,4,4,5,88,88}; cout<<lower_bound(array,array+10,4)-array<<endl; cout<<my_lower_bound(array,10,4)<<endl; cout<<upper_bound(array,array+10,0)-array<<endl; cout<<my_upper_bound(array,10,0)<<endl; }在STL中还有一个与二分查找有关的函数叫equal_range,该函数返回的是一对迭代器,第一个迭代器指向所查找元素的第一次出现的位置,第二个迭代器指向所查找元素最后一次出现位置的后一个位置。我们可以大致把equal_range理解成lower_bound+upper_bound。原理差不多,我们只讲解一下基本用法。
int main() { vector<int> vecInt; pair<vector<int>::iterator,vector<int>::iterator> iter; vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(3); vecInt.push_back(3); vecInt.push_back(4); vecInt.push_back(5); iter=equal_range(vecInt.begin(),vecInt.end(),3); cout<<*iter.first<<endl; cout<<*iter.second<<endl; }输出:
#include<vector> #include<iostream> #include<algorithm> using namespace std; int main() { vector<int> vecInt; pair<vector<int>::iterator,vector<int>::iterator> iter; vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(4); vecInt.push_back(5); iter = equal_range(vecInt.begin(),vecInt.end(),3); cout<<*iter.first<<endl; cout<<*iter.second<<endl; }输出:
这是因为迭代器指向了容器的end。所以我们最好在使用之前做一下判断。
#include<vector> #include<iostream> #include<algorithm> using namespace std; int main() { vector<int> vecInt; pair<vector<int>::iterator,vector<int>::iterator> iter; vecInt.push_back(1); vecInt.push_back(2); vecInt.push_back(4); vecInt.push_back(5); iter=equal_range(vecInt.begin(),vecInt.end(),0); if(iter.first!=vecInt.end()) cout<<*iter.first<<endl; if(iter.second!=vecInt.end()) cout<<*iter.second<<endl; }