二分查找边界问题分析
概述
二分查找对于有序序列性能非常优越,能够达到O(logn)级别,因此熟练使用二分搜索是非常重要的。但是,正如Knuth评价的那样:
Although the basic idea of binary search is comparatively straightforward, the details can be surprisingly tricky…
意思是说,尽管二分搜索算法的基本思想比较简单,但是细节却非常棘手。我觉得这样的评价真的是非常客观而且到位的,如果不花点心思研究一下二分搜索算法的边界问题,还真的很难做到熟练使用二分搜索算法。这里,我就把自己学习时候的理解和体会作以总结和分析。
二分查找的基本思路
最基本的二分查找算法的实现如下代码所示:对一个有序序列而言(我们此处只讨论非递减序列)对于某个要查找的值,我们每次取搜索区间的中间位置,根据中间位置来决定下一步该如何做,则可以分为以下三种情况:
1.如果中间位置的值是我们要查找的值,则返回对应的下标
2.如果中间位置的值比我们要查找的值小,则可以推出我们要查找的值在搜索区间的后半部分,更新搜索区间即可(因为序列非递减)
3.如果中间位置的值比我们要查找的值大,则可以推出我们要查找的值在搜索区间的前半部分,更新搜索区间即可
4.重复上述步骤,直到找到查找值,或者区间长度小于0(即搜索区间不存在)则未找到。
//a[]为要搜索的数组,n表示数组的长度,x表示要搜索的值
int BinarySearch(int a[],int n,int x){
int left=0,right=n-1; //初始化区间长度
while(left<=right){
//当区间长度大于0时,进行循环,假如循环一直走到尽头,则循环结束时,显然有left=right+1
int mid=left+(right-left)/2; //取中间位置,这里的写法可以避免left+right溢出的情况发生
if(a[mid]==x) //如果找到查找值,返回下标
return mid;
else if(a[mid]>x) //如果中间位置的值大于目标值,则向前半部分查找,此时收缩右边界,因为mid位置已经判定过,故right=mid-1
right=mid-1;
else if(a[mid]<x) //如果中间位置的值小于目标值,则向后半部分查找,此使收缩左边界,因为mid位置已经判定过,故left=mid+1
left=mid+1;
} //如果查找失败,则必有left之前的所有值都<x,即left所在的位置为x需要插入的位置
return left;
}
注意:我这里特别强调了left最后所在位置的特征,这对于理解边界情况非常有帮助,请仔细理解left下标之前的所有值都小于x。
二分查找的边界情况
如果非递减序列中,查找的目标值有多个,则有时候我们需要找到这个重复值的左边界和重复值的右边界(如果此值存在的情况下),如下图所示:
二分查找的左边界
在理解算法的写法之前,我们先来简单分析和理解一下左边界的特征和规律,不难发现左边