二分查找又称折半查找,优点是比较次数少,查找速度快,平均性能好,占用系统内存较少。
其缺点是要求待查表为有序表,且插入删除困难。因此,折半查找方法适用于不经常变动而查找频繁的有序列表。
对于二分查找法,首先我们获取需要查找的数组的左边界l和右边界r,然后在通过左右边界计算出数组中中间的位置mid,但是计算mid不能简单的使用mid=(l+r)/2去计算,因为当l和r都接近于int的最大值时,我们求l+r就会发生int变量越界的情况,因此我们这样去求mid的值:mid=l+(l+r)/2(左边界加上一半的偏移量)。
具体如下图所示:
也可以运用递归的思想去实现二叉搜索,先判断我们要查找的target与mid处值的大小,如果运气非常好,target=arr[mid]的话,则直接return target就好了,如果小于mid,则把搜索的边界缩小,使其右边界为mid-1,相反,如果大于mid,则使其左边界变为mid+1,总之,就是在不断的缩小数组的查找范围,每一次查找都能排除上一步一半的数据,因此,二叉搜索是一种非常高效的搜索方法,以下是具体的代码:
#include <iostream>
#include <cassert>
#include <ctime>
using namespace std;
/**二分查找法,在有序数组中查找目标target
* 返回目标target在数组中相对应的索引
* 如果数组中不存在目标target,则返回-1*/
template <typename T>
int binarysearch(T arr[],int n,T target)//n为数组的总数,target为要查询的目标
{
//在[l,r]中搜索target
int l,r,mid;//l为搜索的左边界,r为搜索的右边界,mid为中间的元素
l=0,r=n-1;
while(l<=r){//注意此时l可以等于r
mid=l+(r-l)/2;//不采用(l+r)/2的算法求mid是为了防止Int变量越界
if(arr[mid]==target) return mid;
else if(target<arr[mid]){
r=mid-1;
}
else{
l=mid+1;
}
}
return -1;
}
template <typename T>
int __binarysearch(T arr[],int l,int r,T target)
{//对数组中的[l,r]进行操作,l为起始搜索位置,r为终止搜索位置,mid为中间位置
if(l>r) return -1;
int mid;
mid=l+(r-l)/2;//不采用(l+r)/2的算法求mid是为了防止Int变量越界
if(target==arr[mid]) return mid;
else if(target<arr[mid]) __binarysearch(arr,l,mid-1,target);
else __binarysearch(arr,mid+1,r,target);
}
template <typename T>
int binarysearch2(T arr[],int n,T target)
{
return __binarysearch(arr,0,n-1,target);
}
//生成测试案例为一个大小为1000000的数组,查询超出1000000的则返回-1
int main() {
//生成测试案例为一个大小为1000000的数组,查询超出1000000的则返回-1
int n=1000000;
int *a=new int[n];
for(int i=0;i<n;i++)
{
a[i]=i;
}
//测试非递归算法
clock_t starttime=clock();
for(int i=0;i<2*n;i++)
{
int ret=binarysearch(a,n,i);
if( i < n )
assert( ret== i );
else
assert( ret== -1 );
}
clock_t endtime=clock();
cout<<"Binary Search (Without Recursion):"<<double(endtime-starttime)/CLOCKS_PER_SEC<<'s'<<endl;
//测试递归的二叉搜索法
starttime=clock();
for(int i=0;i<2*n;i++)
{
int ret=binarysearch2(a,n,i);
if(i<n) assert(ret==i);
else assert(ret==-1);
}
endtime=clock();
cout<<"Binary Search (Recursion):"<<double(endtime-starttime)/CLOCKS_PER_SEC<<"s"<<endl;
delete[]a;
return 0;
}
接下来我们看一下测试的结果:
我们发现,使用递归算法较好的描述了二分查找法,但是性能上却会略有下降。