转载于该大佬的博客:https://blog.youkuaiyun.com/u012878643/article/details/46723609
前言: STL中关于二分查找的函数主要有三个,分别是lower_bound/upper_bound/
binary_search。这三个函数都运用于有序区间,当然这也是二分查找思想运用的前提,下面逐一对这三个函数进行剖析。
在深入剖析之前,首先给出各函数的原型声明,由于这三个函数都包含在算法库中,因此首先应该包含头文件,如下所示:
#include <algorithm>
using namespace std;
lower_bound算法返回一个非递减序列[first, last)中的第一个大于等于val的位置。
upper_bound算法返回一个非递减序列[first, last)中的第一个大于val的位置。
ForwardIter lower_bound(ForwardIter first, ForwardIter last,const _Tp& val)
ForwardIter upper_bound(ForwardIter first, ForwardIter last, const _Tp& val)
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
const T& val, Compare comp);
在没有给出三个函数源码之前,我们首先给出基本的二分查找的代码,作为后续介绍的基础:
template<class ElementType>
int BinarySearch(ElementType *pData,int First,int Last,ElementType Key)
{
while (First<=Last) //最后一次循环是通过First=Last来定位Key的
{
Middle=(First+Last)/2;
if (pData[Middle]==Key)
return Middle;
if (pData[Middle]>Key)
Last=Middle-1;//在左子区间找key
else
First=Middle+1;//在右子区间找key
}
return -1;//没有找到
}
对于递增序列中有重复数字出现的情况,用改进的二分查找可以返回第一次出现的位置或者最后一次出现的位置,如下所示:
template<class ElementType>
int BinarySearch_First(ElementType *pData,int First,int Last,ElementType Key)
{
int iPos=-1;
while (First<=Last) //最后一次循环是通过First=Last来定位Key的
{
int Middle=(First+Last)/2;
if (pData[Middle]==Key)
{
iPos=Middle;
Last=Middle-1;//不要停,向左继续进行二分查找
// First=Middle+1;//如果要找到最后一次出现的位置
}
else if(pData[Middle]>Key)
Last=Middle-1;
else
First=Middle+1;
}
return iPos;
}
对二分查找算法进一步改进可以处理查找不成功的情况。假设要求在查找不成功的情况下输出一个最接近key的区间,使得key属于此区间。试想,如果查找不成功,则最后停下来的元素的左边或者右边一定是最接近key的元素。代码如下:
template<class ElementType>
int *BinarySearch_Secnd(ElementType *pData,int First,int Last,ElementType Key)
{
int iPos=-1;
while (First<=Last) //最后一次循环是通过First=Last来定位Key的
{
int Middle=(First+Last)/2;
if (pData[Middle]==Key)
{
iPos=Middle;
break;
}
if(pData[Middle]>Key)
Last=Middle-1;
else
First=Middle+1;
}
//以下代码处理查找不成功的情况,可以根据题意修改代码,如返回区间或者返回最接近的那个
int *pArray=new int[2];
if (iPos!=-1) //查找成功
{
pArray[0]=iPos;
pArray[1]=iPos;
}
else //查找不成功
{
if (pData[iPos]>Key)
{
pArray[0]=iPos-1;//下界,注意下界可能小于0
pArray[1]=iPos;//上界
}
else
{
pArray[0]=iPos;//下界
pArray[1]=iPos+1;//上界,注意上界可能大于原来的Last位置
}
}
return pArray;
}
1.lower_bound函数
以下代码非STL源码,但完全体现出了STL中lower_bound函数的思想:
int lower_bound(int *array, int size, int key)
{
int first = 0, middle;
int half, len;
len = size;
while(len > 0) {
half = len >> 1;
middle = first + half;//中位元素
if(array[middle] < key) {
first = middle + 1;//因为要查找大于等于key的第一个元素,所以加1
len = len-half-1; //在右边子序列中查找
}
else
len = half; //在左边子序列(包含middle)中查找
}
return first;
}
2.upper_bound函数
int upper_bound(int *array, int size, int key)
{
int first = 0, len = size-1;
int half, middle;
while(len > 0){
half = len >> 1;
middle = first + half;
if(array[middle] > key) //中位数大于key,在包含last的左半边序列中查找。
len = half;
else{
first = middle + 1; //中位数小于等于key,在右半边序列中查找。
len = len - half - 1;
}
}
return first;
}
如果是使用了comp仿函数版本,则将以上代码中if判断语句改成comp函数调用即可;因为原始的序列是有序的,且满足comp(_left,_right)。实际上,upper_bound函数查找到的是第一个不满足comp(_left,key)的元素。下面给出STL中函数的源码:
template <class ForwardIterator, class T>
inline ForwardIterator lower_bound(ForwardIterator first, ForwardIterator last,
const T& value) {
return __lower_bound(first, last, value, distance_type(first),
iterator_category(first));
}
函数通过调用__lower_bound实现,分两个版本,第一种是调用默认less仿函数的,第二种是指定comp函数的,如下所示:
template <class ForwardIterator, class T, class Distance>
ForwardIterator __lower_bound(ForwardIterator first, ForwardIterator last,
const T& value, Distance*,
forward_iterator_tag) {
Distance len = 0;
distance(first, last, len); // 求取整个范围的长度,ForwardIterator没有-n操作
Distance half;
ForwardIterator middle;
while (len > 0)
{
half = len >> 1; // 除以2,注意这种移位写法,不需编译器进行优化
middle = first; // 这两行令middle 指向中间位置
advance(middle, half); //ForwardIterator没有+n的操作
if (*middle < value) // 如果中间位置的元素值 < key的值,在后半区间
{
first = middle; // 这两行令 first 指向 middle 的下一位置
++first;
len = len - half - 1; // 修正 len,回头测试循环条件
}
else // 注意如果是相等的话,那么执行的是else语句,在前半部分找
len = half; // 修正 len,回头测试循环条件
}
return first;
}
下面是带comp函数的版本:
template <class ForwardIterator, class T, class Compare, class Distance>
ForwardIterator __lower_bound(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp, Distance*,
forward_iterator_tag) {
Distance len = 0;
distance(first, last, len);
Distance half;
ForwardIterator middle;
while (len > 0)
{
half = len >> 1;
middle = first;
advance(middle, half); //middle=middle+half;
if (comp(*middle, value)) //此处采用comp函数进行比较
{
first = middle; //若比较结果为TRUE,则在右半子区间进行比较
++first;
len = len - half - 1;
}
else
len = half; //若比较结果为false,则在左半子区间进行比较
}
return first;
}
upper_bound函数两个版本的源码如下所示:
template <class ForwardIterator, class T, class Compare>
inline ForwardIterator upper_bound(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp) {
return __upper_bound(first, last, value, comp, distance_type(first),
iterator_category(first));
}
upper_bound函数通过调用__upper_bound函数实现,两个版本的函数如下:
template <class ForwardIterator, class T, class Distance>
ForwardIterator __upper_bound(ForwardIterator first, ForwardIterator last,
const T& value, Distance*,
forward_iterator_tag) {
Distance len = 0;
distance(first, last, len);
Distance half;
ForwardIterator middle;
while (len > 0) {
half = len >> 1;
middle = first;
advance(middle, half);
if (value < *middle) // 如果中间位置的元素值大于标的值,证明在前半部分
len = half; // 修正len
else { // 注意如果元素值相等的话,那么是在后半部分找
first = middle; // 在后半部分,令first指向middle的下一个位置
++first;
len = len - half - 1;
}
}
return first;
}
第二个是带comp仿函数的版本:
template <class ForwardIterator, class T, class Compare, class Distance>
ForwardIterator __upper_bound(ForwardIterator first, ForwardIterator last,
const T& value, Compare comp, Distance*,
forward_iterator_tag) {
Distance len = 0;
distance(first, last, len);
Distance half;
ForwardIterator middle;
while (len > 0) {
half = len >> 1;
middle = first;
advance(middle, half);
if (comp(value, *middle))
len = half;
else {
first = middle;
++first;
len = len - half - 1;
}
}
return first;
}
由以上可以看出,lower_bound和upper_bound函数不同点在于如果comp(value,*middle)为TRUE,说明value在*middle的左边,则lower_bound函数在前半子区间进行查找,而upper_bound函数在右半子区间进行查找;
3.binary_search函数
默认less函数的版本:
template <class ForwardIterator, class T>
bool binary_search(ForwardIterator first, ForwardIterator last,
const T& value) {
ForwardIterator i = lower_bound(first, last, value);
//这里的实现就是调用的lower_bound ,并且如果元素不存在那么lower_bound指向的元素一定是
//operator < 为ture的地方。
return i != last && !(value < *i);
}
带comp函数的版本:
template <class ForwardIterator, class T, class Compare>
bool binary_search(ForwardIterator first, ForwardIterator last, const T& value,
Compare comp) {
ForwardIterator i = lower_bound(first, last, value, comp);
return i != last && !comp(value, *i);
}
核心思想是Divide and conqure!将原来的数组不断地分成两个子数组,取其一进行解决。