C++ <algorithm> 函数概览

高效处理序列的必备利器

使用 ◀ ▶ 或底部按钮进行导航

1. 排序与查找

sort

对指定范围内的元素进行排序(默认升序)。不保证相等元素的相对顺序。

时间复杂度:平均 O(N log N)

stable_sort

对指定范围内的元素进行稳定排序。保证相等元素的原始相对顺序不变。

时间复杂度:通常 O(N log N),如果内存不足可能 O(N log² N)

binary_search

在 **已排序** 的序列中快速检查某个元素是否存在。

时间复杂度:O(log N)

演示区

当前数组状态:

2. 辅助查找 (基于已排序序列)

lower_bound

查找第一个 **不小于** (大于或等于) 指定值的元素,并返回其迭代器。

时间复杂度:O(log N)

upper_bound

查找第一个 **严格大于** 指定值的元素,并返回其迭代器。

时间复杂度:O(log N)

equal_range

返回一个 `pair`,包含 `lower_bound` 和 `upper_bound` 的结果,即表示序列中所有等于指定值的元素的范围 `[first, last)`。

时间复杂度:O(log N)

演示区

当前数组状态:

3. 排列

next_permutation

将当前序列重排为字典序中的下一个排列。如果当前已是最大排列,则重排为最小排列(升序)并返回 `false`,否则返回 `true`。

时间复杂度:平均 O(N)

prev_permutation

将当前序列重排为字典序中的上一个排列。如果当前已是最小排列,则重排为最大排列(降序)并返回 `false`,否则返回 `true`。

时间复杂度:平均 O(N)

演示区

当前排列:

排列历史:

4. 常用序列操作

reverse

反转指定范围内的元素顺序。

时间复杂度:O(N)

rotate

将序列中的元素进行循环左移,使得指定的 `middle` 元素成为新的第一个元素。

时间复杂度:O(N)

unique

移除 **连续的** 重复元素(只保留第一个)。它并不实际删除元素,而是将不重复的元素移动到序列前端,并返回指向新的逻辑尾部的迭代器。通常需要配合容器的 `erase` 方法使用。

时间复杂度:O(N)

accumulate (在 <numeric> 头文件中)

计算指定范围内元素的总和(或其他二元操作的结果)。需要提供初始值。

时间复杂度:O(N)

演示区

当前序列:

5. 竞赛常用高阶算法

nth_element

重新排列序列,使得索引为 `n` 的位置上的元素是 **如果整个序列排序后** 该位置应有的元素(即第 n 小的元素)。所有在 `n` 之前的元素都不大于它,所有在 `n` 之后的元素都不小于它。注意:除了第 n 个元素位置正确外,其他元素不保证有序。

时间复杂度:平均 O(N),最坏 O(N²)

partial_sort

对序列的一部分进行排序。它将序列中最小的 `k` (由 `middle` 迭代器指定) 个元素按升序排列放在序列的前 `k` 个位置。

时间复杂度:O(N log k)

partition

根据指定的谓词(一个返回布尔值的函数或函数对象),重新排列序列,使得所有满足谓词的元素都排在所有不满足谓词的元素之前。返回指向第一个不满足谓词的元素的迭代器。

时间复杂度:O(N)

nth_element / partial_sort / partition 演示

当前序列:

堆操作: make_heap / push_heap / pop_heap

make_heap: 将序列调整为最大堆(默认)。

push_heap: 假设 `[first, last-1)` 已经是堆,将 `*(last-1)` 加入堆中。

pop_heap: 将堆顶元素(最大值)移动到序列末尾 `*(last-1)`,并将剩余 `[first, last-1)` 调整为堆。需要手动 `pop_back()` (或其他方式) 移除末尾元素。

make_heap: O(N) | push/pop_heap: O(log N)

堆操作演示

当前序列 (堆状态):

其他常用函数

iota (在 <numeric> 中): 用连续递增的值填充序列 (e.g., 0, 1, 2, ...)。

transform: 对序列中的每个元素应用一个函数,并将结果存储到另一个序列(或原地修改)。

count / count_if: 计算序列中等于某个值 / 满足某个谓词的元素数量。

min_element / max_element / minmax_element: 返回指向序列中最小/最大/最小和最大元素的迭代器(或 pair)。

6. 练习题与知识延伸

快速测验

问题加载中...

编程练习

练习 1: Top K (无序)

给定一个整数数组和整数 k,使用 `nth_element` 找到数组中第 k 大的元素,并输出数组中前 k 大的所有元素(这些元素本身不需要排序)。


练习 2: Top K (有序) - 堆 vs partial_sort

给定一个整数数组和整数 k,分别使用堆操作(`make_heap`, `pop_heap`)和 `partial_sort` 找到数组中最小的 k 个元素,并按升序输出。思考两种方法在不同场景下的优劣。

7. 使用注意事项

算法前提条件

  • 排序前提: `binary_search`, `lower_bound`, `upper_bound`, `equal_range`, `merge`, `inplace_merge`, `includes`, `set_union`, `set_intersection`, `set_difference`, `set_symmetric_difference` 都要求输入范围(或至少相关部分)是 **已排序** 的。
  • `unique`: 只移除 **相邻** 的重复元素。如果想移除所有重复元素,通常需要先排序。
  • 迭代器有效性: 大部分修改序列的算法(如 `sort`, `partition`, `remove` 等)可能会使传入的迭代器失效(除了 `end()` 返回的迭代器)。`stable_*` 版本的算法通常能更好地保持迭代器(相对于未移动的元素)。

时间复杂度

  • 注意区分 **平均** 时间复杂度和 **最坏** 时间复杂度。例如,`sort` 通常是 O(N log N),但某些旧实现或特定数据下可能退化。`nth_element` 平均是 O(N),但最坏是 O(N²)。
  • 复杂度中的 N 通常指范围内的元素数量。

边界情况处理

  • 处理 **空序列**:确保算法在空范围上行为正确或有定义(通常是无操作)。
  • 迭代器范围: 确保 `[first, last)` 是一个有效范围 (`first` 不在 `last` 之后)。
  • 参数检查: 对于 `nth_element(first, nth, last)`,确保 `nth` 在 `[first, last)` 范围内。对于 `partial_sort(first, middle, last)`,确保 `middle` 在 `[first, last]` 范围内。

返回值

  • 许多算法返回迭代器,如 `find`, `lower_bound`, `partition`, `remove`, `unique` 等。理解这些迭代器的含义非常重要(例如,`remove` 和 `unique` 返回的是 **新的逻辑结尾**)。
  • 一些算法(如 `sort`, `reverse`)返回 `void`。
  • 谓词函数应保持 **纯粹**,不应修改传入的元素或有副作用。

8. 知识延伸

C++20 `<ranges>` 库

C++20 引入了 `<ranges>` 库,提供了对算法的重大改进:

  • 可以直接对 **容器本身** 调用算法,无需 `begin()` 和 `end()`,例如 `ranges::sort(myVector);`
  • 支持 **投影 (Projections)**:允许在比较或操作前对元素应用一个转换函数,例如按对象的某个成员排序。
  • 更好的 **组合性**:通过管道操作符 `|` 连接视图 (views) 和算法,写出更简洁、声明式的代码。
  • **视图 (Views)**:惰性求值的序列适配器,如 `filter`, `transform`, `take` 等,可以高效地处理数据流而无需创建中间容器。

自定义比较器与谓词

许多算法接受可选的最后一个参数,用于自定义比较逻辑 (Comparison function) 或条件判断 (Predicate function):

  • 比较器: 用于 `sort`, `stable_sort`, `lower_bound`, `max_element` 等。必须满足严格弱序 (Strict Weak Ordering) 要求。通常是一个接收两个参数并返回 `bool` 的函数或 Lambda 表达式 (`[](const auto& a, const auto& b) { return a < b; }`)。
  • 谓词: 用于 `find_if`, `count_if`, `partition`, `remove_if` 等。通常是一个接收一个参数并返回 `bool` 的函数或 Lambda 表达式 (`[](const auto& x) { return x > 10; }`)。

并行算法 (`<execution>`)

C++17 引入了执行策略,允许指定算法并行执行:

  • `execution::seq`: 顺序执行(默认)。
  • `execution::par`: 并行执行,可能无序。
  • `execution::par_unseq`: 并行执行,可能无序,且允许向量化(交错执行)。

例如: `sort(execution::par, v.begin(), v.end());`

注意: 并行化并非总能带来性能提升,且需要注意数据竞争问题(自定义操作必须线程安全)。编译器和标准库的支持程度也可能不同。

总结

C++ `<algorithm>` (及 `<numeric>`) 库提供了大量强大、高效且通用的函数,用于处理序列数据。

  • 核心优势: 代码简洁、可读性高、性能通常优于手写循环、经过充分测试。
  • 关键类别:
    • 非修改性操作 (查询): `find`, `count`, `binary_search`, `equal_range`, `min/max_element` ...
    • 修改性操作: `sort`, `stable_sort`, `reverse`, `rotate`, `partition`, `unique`, `remove` ...
    • 排列组合: `next_permutation`, `prev_permutation` ...
    • 数值计算 (`<numeric>`): `accumulate`, `iota`, `inner_product` ...
    • 堆操作: `make_heap`, `push_heap`, `pop_heap`, `sort_heap` ...
    • 集合操作 (需排序): `merge`, `set_union`, `set_intersection` ...
  • 重要概念: 迭代器范围 `[first, last)`、谓词、比较器、时间复杂度、稳定性。
  • 现代 C++: C++11/14/17/20 不断增强算法库,引入 Lambda、执行策略 (`<execution>`) 和范围库 (`<ranges>`),使其更加灵活和强大。

熟练掌握 `<algorithm>` 是 C++ 程序员提升效率和代码质量的关键一步。