C++ STL 二分查找 lower_bound, upper_bound和equal_range底层实现分析
函数功能总结
在复习时学到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的元素,然后查看返回迭代器位置是否不等于数组末尾并且目标元素是否>=该返回的迭代器指向元素。
这样就保证了:
- 返回的迭代器位置不在数组末尾,说明数组中存在大于等于val的元素。
- 返回的迭代器只想元素小于等于目标元素,即*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()函数详解