C++二分查找实战指南:lower_bound与upper_bound高效应用解析

1. 从“大海捞针”到“精准定位”:为什么你需要lower_bound和upper_bound?

大家好,我是你们的老朋友,一个在C++和算法领域摸爬滚打了十多年的老码农。今天想和大家聊聊两个在竞赛和工程中出场率极高,但很多朋友又觉得有点“绕”的函数:std::lower_boundstd::upper_bound

先来想象一个场景:你面前有一本厚厚的、按姓氏拼音排序的电话簿。现在让你找出所有姓“张”的人。你会怎么做?最笨的办法是从第一页开始,一页一页翻,直到找到第一个“张”,然后继续翻,直到“张”结束。这个过程,就是顺序查找,对应STL里的 findfind_if,时间复杂度是O(n)。电话簿薄还好,如果是一百万条数据呢?效率就太低了。

但因为我们知道电话簿是有序的,聪明人会直接翻到“Z”开头的部分,然后快速定位到“Zhang”附近开始查找。这个“快速定位”的过程,就是二分查找,时间复杂度是O(log n),效率有质的飞跃。而 lower_boundupper_bound,就是C++ STL为我们封装好的、用于在有序序列中进行二分查找的“神兵利器”。

我见过太多新手朋友,遇到有序数据的查找问题,第一反应还是手写一个二分循环,调试半天边界条件,最后还可能出bug。其实,STL早就把轮子造好了,而且造得极其精良。lower_boundupper_bound 不仅仅是“查找”,它们更是一种“定位”思想,能帮你解决“找第一个”、“找最后一个”、“统计个数”、“确定插入位置”等一系列衍生问题。掌握它们,你就能在有序数据的海洋里,实现从“大海捞针”到“精准定位”的跨越。

2. 核心概念拆解:lower_bound与upper_bound到底在找什么?

这是最核心也最容易混淆的部分。咱们不用死记硬背,我教你一个我用了很多年的“数轴记忆法”。

想象一根水平数轴,上面按顺序排列着许多点(我们的有序数据)。现在,我用手指定一个目标值 val,把它想象成一根垂直的“标尺线”插在数轴上。

std::lower_bound 的行为:它从数轴左侧(first)向右看,寻找第一个位于“标尺线” val 右侧或正好压在线上的点。用数学语言说,就是“第一个不小于 val 的元素”,即 第一个 >= val 的元素

  • 如果存在值等于 val 的点,它返回第一个这样的点。
  • 如果不存在等于 val 的点,它返回第一个大于 val 的点。
  • 如果所有点都在 val 左边(都小于 val),它返回数轴终点(last)。

std::upper_bound 的行为:它也从左侧向右看,但寻找的是第一个严格位于“标尺线” val 右侧的点。即 第一个 > val 的元素

  • 无论是否存在等于 val 的点,它都跳过所有等于 val 的点,直接指向比它大的第一个点。
  • 如果所有点都小于等于 val,它返回数轴终点(last)。

看个具体例子,数组 vec = {1, 2, 4, 4, 4, 6, 8},目标值 val = 4

  • lower_bound(vec.begin(), vec.end(), 4) 会指向第一个4(下标2)。
  • upper_bound(vec.begin(), vec.end(), 4) 会指向6(下标5)。

那如果 val = 5(不存在)呢?

  • lower_bound 会指向6(第一个 >=5 的元素)。
  • upper_bound 也会指向6(第一个 >5 的元素)。

如果 val = 9(比所有元素都大)呢?

  • 两者都会返回 vec.end()

我刚开始也老记混,后来就记住一个口诀:lower_bound“可以相等”,upper_bound“必须大于”。理解了这个核心定位逻辑,所有应用都是在此基础上展开的。

3. 实战为王:五大高频应用场景与代码详解

懂了概念,咱们就来真刀真枪地干。下面这些场景,都是我实际项目和刷题中反复遇到的,掌握了它们,大部分有序查找问题都能迎刃而解。

3.1 场景一:精确判断元素是否存在

你可能会说,判断存在不是有 binary_search 吗?没错,但 binary_search 只返回 true/false。很多时候,我们找到元素后还想用它做点别的事(比如修改、删除),这时 lower_bound 的组合拳就更灵活。

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

int main() {
    vector<int> scores = {650, 680, 710, 710, 730, 780};
    int target = 710;

    // 使用 lower_bound 判断并定位
    auto it = lower_bound(scores.begin(), scores.end(), target);
    
    if (it != scores.end() && *it == target) {
        cout << "找到分数 " << target << ",首次出现位置在索引: " << (it - scores.begin()) << endl;
        // 后续可以直接操作 it,例如 *it = 715;
    } else {
        cout << "未找到分数 " << target << endl;
        // 此时 it 指向第一个 >= target 的位置,可用于插入新成绩
        // sc
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值