39、STL算法(排序与查找):sort、find、binary_search、lower_bound

排序和查找,说白了就是C++里最常用的两个操作。我做了这么多年项目,几乎每个工程都离不开它们。今天咱们就聊聊STL里这几个核心算法——sortfindbinary_searchlower_bound

你想想看,如果让你自己手写一个快速排序或者二分查找,是不是挺麻烦的?STL把这些都封装好了,直接用就行。但问题来了——很多人用错了,或者用得不合适。嗯,这里要注意,选对算法比写对代码更重要。

排序:sort 和 stable_sort

sort 是我用得最多的算法之一。它底层用的是快速排序的改进版——内省排序(IntroSort)。说白了就是:数据量小的时候用插入排序,数据量大且递归深度浅的时候用快速排序,递归太深了就换堆排序。这样既快又稳。

核心要点:sort 默认按升序排列,时间复杂度 O(N log N)。

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3};
    
    // 默认升序
    std::sort(vec.begin(), vec.end());
    
    // 降序:用 lambda 或 greater<>()
    std::sort(vec.begin(), vec.end(), std::greater<int>());
    
    // 自定义排序:按绝对值
    std::sort(vec.begin(), vec.end(), 
              [](int a, int b) { return std::abs(a) < std::abs(b); });
    
    for (int v : vec) std::cout << v << " ";
    return 0;
}

我在项目中遇到过一个问题:需要对一个结构体数组按多个字段排序。比如先按年龄排,年龄相同再按姓名排。这时候用 lambda 就特别方便:

struct Person {
    std::string name;
    int age;
};

std::vector<Person> people = {...};
std::sort(people.begin(), people.end(),
          [](const Person& a, const Person& b) {
              if (a.age != b.age) return a.age < b.age;
              return a.name < b.name;
          });

我的习惯:如果排序后不需要保持相等元素的原始顺序,用 sort 就够了。如果需要保持稳定排序(比如先按时间排,再按优先级排),那就用 stable_sort。不过 stable_sort 会慢一些,因为它底层是归并排序。

查找:find 和 find_if

find 是最朴素的线性查找。它从容器头走到尾,找到第一个匹配的元素就返回迭代器。时间复杂度 O(N)。

std::vector<int> vec = {10, 20, 30, 40, 50};
auto it = std::find(vec.begin(), vec.end(), 30);
if (it != vec.end()) {
    std::cout << "找到了: " << *it << std::endl;
} else {
    std::cout << "没找到" << std::endl;
}

find_if 呢?它允许你传一个条件。比如我想找第一个大于25的元素:

auto it = std::find_if(vec.begin(), vec.end(), 
                       [](int x) { return x > 25; });

我曾经踩过的坑:find 返回的是迭代器,不是 bool。很多人刚学的时候会写成 if (std::find(...)),这不对。一定要判断返回值是否等于 end()。另外,find 只适用于无序数据。如果你已经排好序了,就别用 find 了——用 binary_search 或者 lower_bound 更快。

二分查找:binary_search 和 lower_bound

这两个算法要求数据必须是已排序的。时间复杂度 O(log N),比线性查找快得多。

binary_search 只告诉你「有没有」,返回 bool。而 lower_bound 返回第一个不小于目标值的位置(迭代器)。

std::vector<int> sorted = {1, 3, 5, 7, 9, 11, 13};

// binary_search:判断是否存在
bool found = std::binary_search(sorted.begin(), sorted.end(), 7);
std::cout << (found ? "存在" : "不存在") << std::endl;

// lower_bound:找到第一个 >= 7 的位置
auto it = std::lower_bound(sorted.begin(), sorted.end(), 7);
if (it != sorted.end()) {
    std::cout << "第一个 >= 7 的元素是: " << *it << std::endl;
    std::cout << "下标是: " << (it - sorted.begin()) << std::endl;
}

你可能会问:那 upper_bound 呢?它返回第一个大于目标值的位置。配合 lower_bound 可以找到某个值的区间范围。

实用技巧:lower_bound 和 upper_bound 配合使用,可以快速统计某个值出现的次数:auto range = std::equal_range(vec.begin(), vec.end(), value); 返回一对迭代器,就是所有等于 value 的元素范围。

知识体系总览

下面这张图帮你理清这几个算法的关系和使用场景:

STL 排序与查找算法总览 数据操作 排序算法 查找算法 sort stable_sort partial_sort find / find_if binary_search lower_bound 关键选择原则 数据无序 → 用 find / find_if(O(N)) 数据有序 → 用 binary_search / lower_bound(O(log N))

实战对比:什么时候用哪个?

场景 推荐算法 时间复杂度 前提条件
数据量小,无序 find O(N)
数据量大,无序 先 sort 再 binary_search O(N log N) + O(log N) 排序后
数据量大,已排序 binary_search / lower_bound O(log N) 已排序
需要插入位置 lower_bound / upper_bound O(log N) 已排序
查找所有匹配值 equal_range O(log N) 已排序

我个人习惯:如果数据量小于100,用 find 就够了,没必要排序。如果数据量上千上万,而且查找操作很频繁,那就先排好序,后面都用二分查找。这叫「一次排序,多次受益」。

避坑指南

我曾经犯过一个低级错误:对一个未排序的 vector 用了 binary_search,结果有时候能找到,有时候找不到。调试了半天才发现问题——binary_search 的前提是数据必须有序。这个坑很多人都会踩。

还有一次,我用 lower_bound 找插入位置,然后直接 insert。结果忘了 lower_bound 返回的是迭代器,而 vector 的 insert 在中间位置是 O(N) 的。如果插入操作很多,性能会急剧下降。这时候应该考虑用 set 或 map。

重要提醒:lower_bound 和 upper_bound 要求数据是「按升序排列」的。如果你用了降序排列,那就得传自定义比较器,否则结果会错。我建议统一用升序,省心。

好了,这几个算法其实不难,关键是理解它们的适用场景。排序用 sort,无序查找用 find,有序查找用 binary_search 和 lower_bound。记住这个口诀,基本不会用错。


公众号:蓝海资料掘金营,微信deep3321