C++中的std::lower_bound()和std::upper_bound()函数

本文深入解析C++ STL中的std::lower_bound与std::upper_bound函数,探讨其非对称设计背后的合理性。通过实际例子对比不同实现方式,展示标准库设计的精妙之处。

前言

问题是躲不掉的,该来的总会来,这不是代码中又遇到了 std::upper_bound() 函数,再来学习一遍好了,在我的印象中每次看到这 lower_boundupper_bound 两个函数都有些别扭,凡是见到他们必须查一遍,因为我记得它们两个函数的作用不对称,这一点记得很清楚,但是它们两个函数查找的细节却记不住,这次总结一下,强化记忆,下次回忆起来应该会快一点。

函数定义

今天看到这两个函数时挠挠头又打开了搜索引擎,看到文章里写到 std::lower_bound() 是返回大于等于 value 值的位置,而 std::upper_bound() 是返回第一个大于 value 值的位置,第一反应真是瞎写,怎么俩都是大于,肯定应该是一个大于一个小于啊,这样才“合理”嘛!

但是当看到多个文章中采用相同的说法时,刚刚还“坚定”的想法开始动摇,然后开始查C++标准文档,一遍遍读着那有些拗口的文字:

std::lower_bound returns an iterator pointing to the first element in the range [first, last) that is not less than (i.e. greater or equal to) value, or last if no such element is found.

std::upper_bound returns an iterator pointing to the first element in the range [first, last) that is greater than value, or last if no such element is found.

这些标准文档上的文字印证了刚刚查询到的结果,两个函数返回的结果都是迭代器,std::lower_bound() 是在区间内找到第一个大于等于 value 的值的位置并返回,如果没找到就返回 end() 位置。而 std::upper_bound() 是找到第一个大于 value 值的位置并返回,如果找不到同样返回 end() 位置。

两个函数都提到了大于操作,而没有涉及到小于操作,这就是我前面提到的不对称,也是我感觉不合理的地方,但是当尝试使用了几次这两个函数之后,我发现这两个函数的设计的恰到好处,这样的设计很方便我们来做一些具体的操作。

实际例子

首先说明这两个函数内部使用了二分查找,所以必须用在有序的区间上,满足有序的结构中有两个常见的面孔:std::mapstd::set,他们本身就是有序的,所以提供了 std::map::lower_bound()std::set::lower_bound() 这种类似的成员函数,但是原理都是一样的,我们可以弄明白一个,另外类似的函数就都清楚了。

自己设计

如果你看了这两个函数的具体含义也和我一样不太理解为什么这样设计,可以思考一下接下来这个需求,找出数组内所有值为2和3的元素,图例如下:

lower_bound()

对于一个有序数组,我们在实现 lower_bound() 函数和 upper_bound() 函数时可以让它返回指定的位置来确定取值区间,第①种情况就是标准函数库的实现方式,而第②种和第③种就是我第一印象中感觉应该对称的样子,这样看起来也没什么问题,下面具体来分析下后两种设计有哪些不好的地方。

具体分析

假如我们采用第②种实现方式,那么实现打印元素2和3的代码要写成下面这样:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    std::vector<int> v{1,1,2,2,3,3,3,5,7,8};
    std::vector<int>::const_iterator itorLower = std::lower_bound(v.begin(), v.end(), 2);
    std::vector<int>::const_iterator itorUpper = std::upper_bound(v.begin(), v.end(), 3);
    while(true)
    {
        std::cout << *itorLower << std::endl;
        if (itorLower == itorUpper)
            break;
        ++itorLower;
    }
    return 0;
}

代码看起来还可以,打印完元素后判断到达了结尾直接跳出循环,但是如果要是数组中不包含元素2和3呢,那么也会打印出一个元素,还有可能导致程序崩溃。

如果我们采用第③种实现方式,那么实现打印元素2和3的代码要写成下面这样:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    std::vector<int> v{1,1,2,2,3,3,3,5,7,8};
    std::vector<int>::const_iterator itorLower = std::lower_bound(v.begin(), v.end(), 2);
    std::vector<int>::const_iterator itorUpper = std::upper_bound(v.begin(), v.end(), 3);
    for(++itorLower; itorLower != itorUpper; ++itorLower)
    {
        std::cout << *itorLower << std::endl;
    }
    return 0;
}

这代码看起来简洁了很多,但是在循环开始前需要先调用 ++itorLower,因为第一个元素并不是需要找到的元素,所以要先跳过它,这样看来确实多做了一步操作,一开始就让 itorLow 指向第一个2就好了呀。

最终版本

当你尝试几种实现方式就会发现,还是标准库提供的这种方式使用起来更加方便,虽然采取的不是对称的方式,但是统一了存在查找元素和不存在查找元素的的情况,写出的代码也比较简洁,没有多余的步骤,代码如下:

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    std::vector<int> v{1,1,2,2,3,3,3,5,7,8};
    auto itorUpper = std::upper_bound(v.begin(), v.end(), 3);
    for(auto itorLower = std::lower_bound(v.begin(), v.end(), 2); itorLower != itorUpper; ++itorLower)
    {
        std::cout << *itorLower << std::endl;
    }
    return 0;
}

总结

  • 有些函数的实现方式和我们想象的并不一样,但是我们可以通过熟练使用来了解它为什么这样设计
  • 对称结构虽然是很美的,但是非对称的结构在编程中常常出现,同样有其美丽所在
  • 遇到类似的问题可以动笔画一画,列举出各种情况会有利于你做出正确的判断

==>> 反爬链接,请勿点击,原地爆炸,概不负责!<<==

有时会很焦虑,看到优秀的人比你还努力时总让人感到急迫,但是一味的忧患是无意义的,脚下迈出的每一步才是真真正正的前进,不要去忧虑可能根本就不会发生的事情,那样你会轻松许多

<think>首先,用户的问题是关于C++标准库函数std::lower_bound的作用及使用方法。他们想了解其功能使用示例。 我需要注意系统级指令: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 用户可见层的要求: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考的站内引用: - 引用[1]: 提供了std::lower_bound的示例代码。 - 引用[2]: 解释了std::lower_boundstd::upper_bound的功能效率。 - 引用[3]: 给出了函数模板定义。 我的回答应该基于这些引用,但需要用自己的话组织,并确保真实可靠。 结构清晰的回答: 1. 介绍std::lower_bound的功能。 2. 解释其工作原理。 3. 提供使用示例(基于引用[1]的代码)。 4. 可能添加一些注意事项或常见用法。 5. 最后生成相关问题。 使用中文回答。 在回答中,如果有引用,添加引用标识。例如,在解释功能时,引用[2];在提供示例时,引用[1]。 数学表达式:如果提到时间复杂度,使用行内数学格式,如$O(\log n)$。 独立公式:如果有公式,使用$$...$$,但这里可能不需要,除非有数学公式。 生成相关问题:在回答结束时,添加"相关问题"部分。 现在,规划回答结构: 1. **引言**:简要介绍std::lower_bound是什么。 2. **功能**:详细解释其作用,基于引用[2]。 3. **使用方法**:解释参数返回值。 4. **示例代码**:提供并解释引用[1]中的代码。 5. **注意事项**:如容器必须有序等。 6. **相关问题**:生成2-3个相关问题。 确保LaTeX正确:例如,时间复杂度写成$O(\log n)$。 相关问题示例: 1. std::lower_boundstd::upper_bound有什么区别? 2. 如何自定义比较函数使用std::lower_bound? 3. std::lower_bound在无序容器中能使用吗? 基于引用[2],它们提到了两者的区别,所以第一个问题自然。 最后,添加引用标识。</think>### C++ std::lower_bound 函数的功能及使用方法 std::lower_boundC++ 标准库中的一个高效算法函数,用于在已排序的序列中查找目标值的位置。它在 `<algorithm>` 头文件中定义,通常用于有序容器如 `std::vector`、`std::array` 或普通数组。下面我将逐步解释其功能、原理使用方法,并提供详细示例。 #### 1. **功能与作用** std::lower_bound 的主要功能是**查找第一个不小于目标值(即大于或等于目标值)的元素位置**。这意味着: - 如果目标值存在于序列中,函数返回指向该值的第一个出现位置的迭代器。 - 如果目标值不存在,函数返回指向第一个大于目标值的元素的迭代器(即目标值应插入的位置)。 - 如果序列中所有元素都小于目标值,函数返回序列的尾后迭代器(如 `end()`)。 时间复杂度为 $O(\log n)$,因为它基于二分查找算法实现,要求序列必须已排序[^2]。这使得它非常适合大数据量的高效查询。 #### 2. **函数原型** 根据 C++ 标准,std::lower_bound函数模板定义如下: ```cpp template< class ForwardIt, class T > ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value ); ``` - **参数**: - `first` `last`:表示序列范围的迭代器(左闭右开区间 `[first, last)`)。 - `value`:要查找的目标值。 - **返回值**:指向第一个不小于 `value` 的元素的迭代器。如果未找到,返回 `last`[^3]。 #### 3. **使用示例** 以下是一个完整的代码示例,演示了 std::lower_bound 的实际应用。该示例基于引用[1]提供的代码,并添加了详细注释: ```cpp #include <iostream> #include <algorithm> #include <vector> int main() { // 步骤1: 定义一个已排序的整数序列 std::vector<int> numbers = {1, 3, 5, 7, 9, 11, 13}; // 容器必须有序 // 步骤2: 设置目标值 int target = 7; // 步骤3: 使用 std::lower_bound 查找目标值 // 在 [begin(), end()) 范围内查找第一个不小于 target 的元素 auto it = std::lower_bound(numbers.begin(), numbers.end(), target); // 步骤4: 检查结果 if (it != numbers.end() && *it == target) { // 找到目标值,输出位置(索引从0开始) std::cout << "目标值 " << target << " 在容器中找到,位置为:" << std::distance(numbers.begin(), it) << std::endl; } else { // 未找到目标值,输出插入位置(如果需插入,应在此位置) std::cout << "目标值 " << target << " 不在容器中。" << " 可插入的位置为:" << std::distance(numbers.begin(), it) << std::endl; } return 0; } ``` - **输出解释**: - 当 `target = 7` 时,输出:`目标值 7 在容器中找到,位置为:3`(因为 7 在索引 3 处)。 - 如果 `target = 6`(不存在),输出:`目标值 6 不在容器中。可插入的位置为:3`(因为 6 应插入在 5 7 之间)。 - **关键点**: - 序列必须**有序**,否则行为未定义。 - 使用 `std::distance` 计算索引,便于输出位置信息[^1]。 #### 4. **注意事项** - **排序要求**:序列必须按升序排序。如果使用自定义类型,需提供比较函数(例如,通过重载 `operator<` 或传递谓词参数)。 - **与 std::upper_bound 的区别**:std::lower_bound 返回第一个 **不小于** 目标值的位置,而 std::upper_bound 返回第一个 **大于** 目标值的位置。两者常结合使用来查找目标值的范围[^2]。 - **性能**:在随机访问迭代器(如 `std::vector`)上效率最高,时间复杂度为 $O(\log n)$;在双向迭代器(如 `std::list`)上可能降为 $O(n)$。 #### 5. **扩展应用** std::lower_bound 不仅用于查找,还常用于插入排序或维护有序容器。例如,在实现优先级队列时,可以用它来高效插入元素: ```cpp std::vector<int> sorted_vec = {1, 2, 4, 5}; int new_val = 3; auto pos = std::lower_bound(sorted_vec.begin(), sorted_vec.end(), new_val); sorted_vec.insert(pos, new_val); // 插入后序列保持有序 ``` 通过以上步骤,您应该能掌握 std::lower_bound 的核心用法。如需进一步优化或自定义比较逻辑,可参考 C++ 文档。
评论 16
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlbertS

常来“玩”啊~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值