C++ STL <algorithm> 二分查找 lower_bound, upper_bound,equal_range和binary_search底层实现分析

函数功能总结

在复习时学到STL的二分查找算法,基本上是四个:lower_bound,upper_bound,equal_range和binary_search,四个函数的基本功能如下所示:

函数名功能
lower_bound( begin,end,num)要求数组从小到大非递减排序,从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
upper_bound( begin,end,num)要求数组从小到大非递减排序,从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val, Compare comp);在 [first, last) 区域内查找第一个大于val的元素,并且比较函数自定义
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last, const T& val, Compare comp);查找[first, last)区域中第一个大于val的元素,并且比较函数自定义
pair<ForwardIterator,ForwardIterator> equal_range (ForwardIterator first, ForwardIterator last, const T& val);找到 [first, last) 范围中所有等于 val 的元素。对于equal_range函数,当查找成功时:第 1 个迭代器指向的是 [first, last) 区域中第一个等于 val 的元素;第 2 个迭代器指向的是 [first, last) 区域中第一个大于 val 的元素。反之如果查找失败,则这 2 个迭代器要么都指向大于 val 的第一个元素(如果有),要么都和 last 迭代器指向相同
pair<ForwardIterator,ForwardIterator> equal_range (ForwardIterator first, ForwardIterator last, const T& val, Compare comp);根据 comp 指定的规则,找到 [first, last) 范围内所有满足比较函数且等于val 的元素
bool binary_search (ForwardIterator first, ForwardIterator last, const T& val);查找 [first, last) 区域内是否包含 val
bool binary_search (ForwardIterator first, ForwardIterator last, const T& val, Compare comp)根据 comp 指定的规则,查找 [first, last) 区域内是否包含 val

为什么写这篇文章

在学习过程中我想要从底层实现看懂每个查找算法的执行过程,看到equal_range其实是调用lower_bound和upper_bound两个模板函数后,对参考材料 1:C++ lower_bound() upper_bound() 函数用法详解(深入了解,一文学会)文中一句话产生了疑惑:
在这里插入图片描述
所以经过代码一步步测试后明确了底层实现,下面来一步步阐述。

先总结答案

  • 在lower_bound的自定义比较函数中,val作为第一个实参传入;而upper_bound将val作为第一个实参传入。
  • equal_range的底层实现基于lower_bound和upper_bound两个函数,并且upper_bound函数的开始迭代器first 来自 lower_bound返回的迭代器。

lower_bound的底层实现

lower_bound() 函数定义在头文件中,其语法格式有 2 种,分别为:

//在 [first, last) 区域内查找不小于 val 的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
                             const T& val);
//在 [first, last) 区域内查找第一个不符合 comp 规则的元素
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last,
                             const T& val, Compare comp);

其中,first 和 last 都为正向迭代器,[first, last) 用于指定函数的查找范围;val 用于指定目标元素;comp 用于自定义比较规则,此参数可以接收一个包含 2 个形参(第二个形参值始终为 val)且返回值为 bool 类型的函数,可以是普通函数,也可以是函数对象。
lower_bound的底层实现代码可以在<stl_algo.h>头文件找到
在这里插入图片描述
易理解的底层实现如下所示。
可以看出默认比较方法为迭代器指向对象小于目标元素*it<val时开始位置后移到比较元素下一位,而不满足该条件时结束位置前移到比较元素;而自定义比较函数则是将判断条件改为comp(*it,val)

template <class ForwardIterator, class T>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
    ForwardIterator it;
    iterator_traits<ForwardIterator>::difference_type count, step;
    count = distance(first,last);
    while (count>0)
    {
        it = first; step=count/2; advance (it,step);
        if (*it<val) {//对应第 1 种语法格式
            first=++it;
            count-=step+1;
        }
        else count=step;
    }
    return first;
}
template <class ForwardIterator, class T, class Compare>
ForwardIterator lower_bound (ForwardIterator first, ForwardIterator last, const T& val, Compare comp)
{
    ForwardIterator it;
    iterator_traits<ForwardIterator>::difference_type count, step;
    count = distance(first,last);
    while (count>0)
    {
        it = first; step=count/2; advance (it,step);
        if (*it<val) {  //对应第 2 种语法格式
            first=++it;
            count-=step+1;
        }
        else count=step;
    }
    return first;
}

upper_bound底层实现

upper_bound() 函数定义在头文件中,用于在指定范围内查找大于目标值的第一个元素。该函数的语法格式也有 2 种,分别是:

//查找[first, last)区域中第一个大于 val 的元素。
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
                             const T& val);
//查找[first, last)区域中第一个不符合 comp 规则的元素
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last,
                             const T& val, Compare comp);

upper_bound函数的底层实现也可以在<stl_algo.h>找到
在这里插入图片描述
易理解的底层实现如下所示。
可以看出默认比较方法为迭代器指向对象小于等于目标元素(!(val<*it)等价于*it<=val)时开始位置后移到比较元素下一位,而不满足该条件时结束位置前移到比较元素;而自定义比较函数则是将判断条件改为!comp(val,*it) (和lower_bound传入的实参顺序反了一下,取逆思想:要求it>val时返回0,否则返回1,即只有it<=val时才满足条件),因此val始终作为upper_bound传入自定义比较函数的第一个参数

template <class ForwardIterator, class T>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last, const T& val)
{
    ForwardIterator it;
    iterator_traits<ForwardIterator>::difference_type count, step;
    count = std::distance(first,last);
    while (count>0)
    {
        it = first; step=count/2; std::advance (it,step);
        if (!(val<*it))  //对应第一种语法格式
            { first=++it; count-=step+1;  }
        else count=step;
    }
    return first;
}
template <class ForwardIterator, class T, class Compare>
ForwardIterator upper_bound (ForwardIterator first, ForwardIterator last, const T& val, Compare comp)
{
    ForwardIterator it;
    iterator_traits<ForwardIterator>::difference_type count, step;
    count = std::distance(first,last);
    while (count>0)
    {
        it = first; step=count/2; std::advance (it,step);
        if (!comp(val,*it))  //对应第二种语法格式
            { first=++it; count-=step+1;  }
        else count=step;
    }
    return first;
}

equal_range的底层实现

equel_range() 函数定义在头文件中,用于在指定范围内查找等于目标值的所有元素。它也有两种格式:

//找到 [first, last) 范围中所有等于 val 的元素
pair<ForwardIterator,ForwardIterator> equal_range (ForwardIterator first, ForwardIterator last, const T& val);
pair<ForwardIterator,ForwardIterator> equal_range (ForwardIterator first, ForwardIterator last, const T& val, Compare comp);

对应底层实现如下。

  • 注意到,这里先调用lower_bound函数获取第一个满足条件(即大于等于val)的迭代器it,然后从该迭代器开始向后继续找到第一个不满足条件(大于val)的迭代器,返回这两个迭代器组成的pair结果
  • 并且,对于自定义比较函数,那么lower_bound满足条件为(待比较元素*it<目标元素val),而upper_bound满足条件为
    ( !(val<*it),也就是待比较元素*it<=目标元素val)
  • 另外,由于返回的pair第一项为第一个大于等于目标元素的迭代器,第二项为大于目标元素的迭代器,如果数组中并不存在目标元素,返回结果要么(pair.first==pair.second )!=vec.end(),要么两个都指向vec.end();换句话说,这 2 个迭代器要么都指向大于 val 的第一个元素(如果有),要么都和输入equal_range的last 迭代器指向相同;再换句话说,只要两个迭代器指向相同位置,那么数组中不存在目标元素。
//第一种默认实现
template<class ForwardIterator, class T>
std::pair<ForwardIterator ,ForwardIterator> equal_range(ForwardIterator first, ForwardIterator last, const T& val)
{
    ForwardIterator it = std::lower_bound (first,last,val);
    cout<<"lower_bound finish"<<endl;
    return std::make_pair ( it, std::upper_bound(it,last,val) );
}
//第二种自定义比较函数实现
template<class ForwardIterator, class T, class Compare>
std::pair<ForwardIterator ,ForwardIterator> equal_range(ForwardIterator first, ForwardIterator last, const T& val, Compare comp)
{
    ForwardIterator it = std::lower_bound (first,last,val,comp);
    cout<<"lower_bound finish"<<endl;
    return std::make_pair ( it, std::upper_bound(it,last,val,comp) );
}

由此我们可以编写测试用例并自定义比较函数,查看equal_range,lower_bound,upper_bound的执行过程:

equal_range测试

#include <iostream>     // std::cout
#include <algorithm>    // std::equal_range
#include <vector>       // std::vector
#include <iterator>
using namespace std;
//以普通函数的方式定义查找规则
bool mycomp(int i, int j) { return i > j; }

template<class ForwardIterator, class T, class Compare>
std::pair<ForwardIterator ,ForwardIterator> equal_range1(ForwardIterator first, ForwardIterator last, const T& val, Compare comp)
{
    ForwardIterator it = std::lower_bound (first,last,val,comp);
    cout<<"lower_bound finish"<<endl;
    return std::make_pair ( it, std::upper_bound(it,last,val,comp) );
}
//以函数对象的形式定义查找规则(也可以是struct实现,且不用加public)
class mycomp2 {
public://注意一定要是公有类型
    bool operator()(const int& i, const int& j) {
        cout << "=== i: " << i << " === j: " << j << "\n";
        return i > j;
    }//i<=j
};
int main() {
    vector<int>myvector{7,8,5,4,3,3,3,3,2,1};
    pair<vector<int>::iterator, vector<int>::iterator> range2;
    // upper_bound
    //在 myvector 容器中找到所有的元素 3
    // lower_bound()//[first,last,val,[bool comp]) 第一个 满足 >=val的元素,返回指向该元素的迭代器iterator
    // upper_bound()//[first,last,val,[bool comp]) 第一个 满足 >val的元素,返回指向该元素的迭代器iterator
    // iterator it = lower_bound(first, last, val);
    // return pair<>(it, upper_bound(it, last, val, myComp2()));
    range2 = equal_range1(myvector.begin(), myvector.end(), 3, mycomp2());//[first,last,val,[bool comp]) 满足==val的元素的范围,返回pair<iterator begin, iterator end>
    for (auto it = range2.first; it != range2.second; ++it) {
        cout << *it << " ";
    }
    return 0;
}
// Output:
// === i: 3 === j: 3 
// === i: 5 === j: 3 
// === i: 3 === j: 3 
// === i: 4 === j: 3 
// lower_bound finish
// === i: 3 === j: 3 
// === i: 3 === j: 1
// === i: 3 === j: 2
// 3 3 3 3

测试用例过程分析

  • 1.创建数组myvector={7,8,5,4,3,3,3,3,2,1}共10个元素
  • 2.调用equal_range模板函数
  • 2.1.先调用lower_bound函数获取[myvector.begin(),myvector.end())范围内第一个满足自定义比较函数条件的迭代器,存入it中
  • 2.1.1 初始化count=distance(first,last) = 10 //[first,last) 之间的元素个数,即last-first(因为左闭右开所以不用+1)
  • 2.1.2 第一遍二分查找:it = first(指向7),step= count/2 = 5, advance(it,step)使it指向下标为5的3(从左往右数第二个3),输入自定义比较函数(此时val=3作为第二个参数输入) ,此时i=3,j=val=3,不满足 i>j条件,count = step = 5
  • 2.1.3 第二遍二分查找:it = first(指向7),step= count/2 = 2, advance(it,step)使it指向下表为2的5,输入自定义比较函数(此时val=3作为第二个参数输入),此时i=5,j=3,满足i>j条件,first = ++it表示先自增it(it指向下标为3的4),然后first也指向下标为3的4,count-=(step+1)也就是剩余比较数量从5减掉(2+1)变为2
  • 2.1.4 第三遍二分查找:it=first(指向下标为3的4),step = count/2 = 1, advance(it,step)使it指向下标为4的3,输入自定义比较函数(此时val=3作为第二个参数输入),此时i=3,j=3,不满足i>j条件,count=step = 1
  • 2.1.5 第三遍二分查找:it= first(指向下标为3的4),step = count/2 = 0, advance(it,step)使it仍指向下标为3的4,输入自定义比较函数(此时val=3作为第二个参数输入),此时i=4,j=3,满足i>j条件,first = ++it使it和first都指向下标为4的第一个3,count 减去 (0+1) 为0,while(count>0)循环条件不成立,循环结束
  • 2.1.6 lower_bound函数执行结束,返回first(指向下标为4的3),即返回第一个大于等于3的元素位置
  • 2.2.再调用upper_bound函数获取[it,myvector.end())范围内第一个不满足自定义比较函数条件的迭代器,和it一起构成pair数据类型返回结果
  • 2.2.1 初始化count=distance(it,myvector.end()) =6
  • 2.2.2 第一遍二分查找: it =first(指向下标为4的3),step =count/2 = 3, advance(it,step)使it指向下标为7的3(最后一个3),输入自定义比较函数(此时val=3作为第一个参数输入) ,此时i=3,j=3满足!comp(i,j) == !comp(val,*it) == !(i>j) == i<=j条件,first ==++it使it和first都指向下标为8的2,step减去(3+1) = 2
  • 2.2.3 第二遍二分查找:it= first(指向下标为8的2),step=count/2 = 1,advance(it,step)使it指向下标为9的1,输入自定义比较函数(此时val=3作为第一个参数输入),此时i=3,j=1,不满足!(i>j)条件,count=step = 1
  • 2.2.4 第三遍二分查找:it= first(指向下标为8的2),step=count/2 = 0, advance(it,step)使it仍指向下标为8的2,输入自定义比较函数(此时val=3作为第一个参数输入),此时i=3,j=2,不满足!(i>j)条件,count = step = 0,while(count>0)循环条件不成立,循环结束
  • 2.2.5 upper_bound函数执行结束,返回first(指向下标为8的2),即返回第一个大于3的元素位置
  • 2.3 equal_range函数将lower_bound返回的结果和upper_bound返回的结果作为一对pair数据返回
  • 2.4 主函数main获得这对pair迭代器数据对,遍历输出数组内满足条件(即等于目标元素)的所有元素。

binary_search底层实现

既然都写到这里了,那么就索性把binary_search二分查找的底层实现也解释一遍,同样存在两种格式:
大致过程就是先找到第一个大于等于val的元素,然后查看返回迭代器位置是否不等于数组末尾并且目标元素是否>=该返回的迭代器指向元素。
这样就保证了:

  1. 返回的迭代器位置不在数组末尾,说明数组中存在大于等于val的元素。
  2. 返回的迭代器只想元素小于等于目标元素,即*iter <= val <= *iter ,因此 *iter == val。
//第一种语法格式的实现
template <class ForwardIterator, class T>
bool binary_search (ForwardIterator first, ForwardIterator last, const T& val)
{
    first = std::lower_bound(first,last,val);
    return (first!=last && !(val<*first));
}
//第二种语法格式的底层实现
template<class ForwardIt, class T, class Compare>
bool binary_search(ForwardIt first, ForwardIt last, const T& val, Compare comp)
{
    first = std::lower_bound(first, last, val, comp);
    return (!(first == last) && !(comp(val, *first)));//注意val又作为第一个形参传入
}

参考材料

1:C++ lower_bound() upper_bound() 函数用法详解(深入了解,一文学会)
2:C++ equel_range()函数详解

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值