C++ STL 总结:容器、函数、算法与竞赛用法
适用范围:以 C++17 为主,完整列出这一版本的标准 STL 容器类别,按功能汇总成员函数名,以及
<algorithm>、<numeric>的算法函数名;补充 OI 常用的string、bitset、pair、tuple。C++20/23 新增内容单独标注。这是学习与查阅用的总结,同名函数的多种重载合并介绍,不逐条展开模板签名、分配器重载和标准约束。STL 不等于全部 C++ 标准库,本文不展开输入输出、文件系统、线程、正则等模块。默认复杂度按元素比较、移动、哈希等单次操作为常数计算。
参考入口:OI Wiki:C++ 标准库。本文用自己的表格与示例归纳;版本、边界与复杂度同时参照对应 C++ 标准草案。
一、STL 的组成与约定
| 部分 | 作用 | 例子 |
|---|---|---|
| 容器 Container | 保存数据 | vector、set、map |
| 算法 Algorithm | 对区间执行操作 | sort、find、lower_bound |
| 迭代器 Iterator | 连接容器和算法 | begin()、end() 返回的对象 |
| 函数对象 Function Object | 可调用对象,定义比较或运算规则 | less<int>、greater<int>、lambda |
| 适配器 Adapter | 改造容器、迭代器或可调用对象的接口 | stack、reverse_iterator、bind |
| 分配器 Allocator | 管理容器所需的内存 | allocator<T> |
1.1 记号
c:容器;s:字符串;b:bitset。first、last:迭代器;[first, last)表示左闭右开区间。pos:位置,下标还是迭代器由具体函数决定。n:容器或输入区间大小;m:另一输入区间大小;k:匹配或处理的元素数。pred:返回真假值的条件;comp(a, b):判断a是否排在b前。- 除特别说明外,函数位于
std命名空间。
1.2 基本规则
#include <algorithm>
#include <vector>
std::vector<int> a = {4, 1, 3, 2};
std::sort(a.begin(), a.end()); // 整个数组
std::sort(a.begin() + 1, a.begin() + 3); // 只处理下标 1、2
end() 指向最后一个元素之后,不能解引用。空容器的 begin() == end()。<bits/stdc++.h> 是部分 GCC 环境的扩展头文件,不属于标准 C++;跨编译器时应包含各自的标准头文件。
二、容器总览与选型
2.1 序列容器
| 容器 | 头文件 | 特点 | 随机访问 | 单元素插入/删除 |
|---|---|---|---|---|
array<T, N> | <array> | 编译期固定长度、连续存储 | O(1) | 不支持改变长度 |
vector<T> | <vector> | 动态数组,普通元素连续存储 | O(1) | 尾插均摊 O(1),尾删 O(1),中间 O(n) |
deque<T> | <deque> | 分段存储的双端队列 | O(1) | 两端 O(1),中间 O(n) |
list<T> | <list> | 双向链表 | 不支持下标,定位 O(n) | 已知位置时 O(1) |
forward_list<T> | <forward_list> | 单向链表,C++11 | 不支持下标,定位 O(n) | 已知前驱时 O(1) |
2.2 关联容器
| 容器 | 头文件 | 保存内容 | 重复键 | 查找/插入/按键删除 |
|---|---|---|---|---|
set<T> | <set> | 有序集合 | 不允许 | O(log n) |
multiset<T> | <set> | 有序可重集合 | 允许 | 查找/插入 O(log n),按键删除 O(log n + k) |
map<K, V> | <map> | 按键有序的键值对 | 不允许 | O(log n) |
multimap<K, V> | <map> | 按键有序的键值对 | 允许 | 查找/插入 O(log n),按键删除 O(log n + k) |
unordered_set<T> | <unordered_set> | 哈希集合 | 不允许 | 平均 O(1),最坏 O(n) |
unordered_multiset<T> | <unordered_set> | 哈希可重集合 | 允许 | 平均查找/插入 O(1),按键删除 O(k + 1),最坏 O(n) |
unordered_map<K, V> | <unordered_map> | 哈希键值对 | 不允许 | 平均 O(1),最坏 O(n) |
unordered_multimap<K, V> | <unordered_map> | 哈希键值对 | 允许 | 平均查找/插入 O(1),按键删除 O(k + 1),最坏 O(n) |
有序容器通常由平衡搜索树实现,标准规定的是行为与复杂度,不强制使用红黑树。无序容器不保证遍历顺序,也不提供有序边界查询。来源:OI Wiki:关联式容器、无序关联式容器。
2.3 容器适配器与常用辅助类型
| 类型 | 头文件 | 用途 |
|---|---|---|
stack<T> | <stack> | 后进先出栈 |
queue<T> | <queue> | 先进先出队列 |
priority_queue<T> | <queue> | 优先队列,默认取最大元素 |
string | <string> | 拥有字符数据的动态字符串 |
string_view | <string_view> | 不拥有数据的字符串视图,C++17 |
bitset<N> | <bitset> | 固定长度位集合 |
pair<A, B> | <utility> | 两个值组成一组 |
tuple<T...> | <tuple> | 多个值组成一组 |
string、bitset 等经常与 STL 一起学习;其中 bitset 不提供普通 STL 容器的迭代器接口。它们在本文作为相关标准库工具单列。
2.4 怎么选
| 需求 | 优先考虑 |
|---|---|
| 一般动态数组、邻接表、排序、离散化 | vector |
| 固定长度且希望具有容器接口 | array |
| 两端插入删除、单调队列 | deque |
| 已知节点位置,频繁插删或拼接链表 | list / forward_list |
| 有序去重、动态前驱后继 | set |
| 有序且保留重复值 | multiset |
| 键值映射,同时需要按键排序 | map |
| 只要判重、计数或映射,通常无需顺序 | unordered_set / unordered_map |
| BFS | queue |
| 括号匹配、单调栈 | stack 或 vector |
| 贪心、Dijkstra、不断取极值 | priority_queue |
| 大量 0/1 状态,批量位运算 | bitset |
三、通用成员函数
这些是通用接口家族,不代表每个容器都具有全部函数。 后续表格指出重要例外。
| 函数/类型 | 作用 | 适用与说明 |
|---|---|---|
构造函数、析构函数、operator= | 创建、销毁、复制/移动/赋值 | 各类型支持的构造方式不同 |
value_type、size_type、difference_type | 元素、大小、距离类型 | 类型名,不是函数 |
iterator、const_iterator | 可写/只读迭代器类型 | set 的普通迭代器也不能改键 |
begin()、end() | 正向遍历边界 | 容器适配器和 bitset 不提供 |
cbegin()、cend() | 只读正向遍历边界 | C++11 |
rbegin()、rend() | 反向遍历边界 | forward_list、无序容器不提供 |
crbegin()、crend() | 只读反向遍历边界 | 同上,C++11 |
empty() | 是否为空 | bitset 无此函数 |
size() | 元素个数 | forward_list 没有 size() |
max_size() | 实现所允许的理论最大元素数 | 不等于当前可用内存,适配器不提供 |
clear() | 删除全部元素 | array、适配器不提供 |
swap(other) | 交换内容 | 大部分容器通常 O(1),array O(N);有分配器条件 |
get_allocator() | 返回分配器 | 适用于具有分配器的容器 |
insert(...) | 在位置或按键规则插入 | 参数和返回值依容器而变 |
emplace(...) | 在目标位置直接构造元素 | C++11;不等于无条件更快 |
erase(...) | 删除元素 | 序列容器一般按迭代器;关联容器还可按键 |
常用初始化:
std::vector<int> a; // 空数组
std::vector<int> b(5); // 5 个 0
std::vector<int> c(5, 7); // 5 个 7
std::vector<int> d{5, 7}; // 两个元素:5、7
std::vector<int> e(c.begin(), c.end());
std::vector<int> f = c; // 复制
std::vector<int> g = std::move(f); // 移动;不要假定 f 一定为空
() 与 {} 可能选择不同构造函数。容器复制通常随元素数线性增长;移动的复杂度会受构造/赋值形式和分配器条件影响。
四、序列容器
参考:OI Wiki:序列式容器。
4.1 vector:动态数组
std::vector<int> v = {10, 20, 30};
| 函数 | 作用/返回值 | 复杂度与注意点 |
|---|---|---|
v[i] | 第 i 个元素的引用 | O(1),不检查越界 |
v.at(i) | 第 i 个元素的引用 | O(1),越界抛出 out_of_range |
front()、back() | 首/尾元素引用 | O(1),要求非空 |
data() | 指向底层连续元素区的指针 | O(1),vector<bool> 不按普通 vector 使用 |
size()、capacity() | 已有元素数/已分配容量 | O(1) |
reserve(n) | 至少预留 n 的容量 | 不改变 size();重分配时 O(size) |
resize(n)、resize(n, x) | 改变元素数量 | 增加时构造新元素,缩小时销毁尾部 |
shrink_to_fit() | 请求缩减多余容量 | C++11,非强制;可能重分配 |
push_back(x) | 尾部添加元素 | 均摊 O(1),单次扩容可 O(n) |
emplace_back(args...) | 尾部直接构造 | 均摊 O(1);C++17 起返回新元素引用 |
pop_back() | 删除尾部元素 | O(1),返回 void,要求非空 |
insert(pos, x) | 在 pos 前插入,返回新元素迭代器 | 单元素通常 O(n);还有数量/区间重载 |
emplace(pos, args...) | 在 pos 前构造,返回迭代器 | 通常 O(n) |
erase(pos) | 删除一个,返回后继迭代器 | O(n) |
erase(first, last) | 删除区间,返回后继迭代器 | O(n) |
assign(n, x)、assign(first, last)、assign({...}) | 整体替换内容 | 线性 |
clear() | 清空已有元素 | O(n),通常保留容量 |
v.reserve(100); // 只是预留空间
v.push_back(40);
v.insert(v.begin() + 1, 15);
v.erase(v.begin() + 2); // 删除第三个元素
v.erase(v.begin(), v.begin() + 2);
vector<bool> 是位压缩特化,v[i] 可能是代理对象,不能把它当作普通 bool&;它还有 flip(),用于翻转所有位。需要普通布尔数组行为时可考虑 vector<char>。容量与重分配规则参照 C++17 vector 条款。
4.2 array:固定长度数组
std::array<int, 4> a = {1, 2, 3, 4};
| 函数 | 作用 |
|---|---|
operator[]、at() | 下标访问;at 检查越界 |
front()、back()、data() | 首元素、尾元素、底层指针 |
begin/end、cbegin/cend、rbegin/rend、crbegin/crend | 正反向迭代 |
empty()、size()、max_size() | 是否长度为 0、固定长度 N |
fill(x) | 全部赋值为 x,O(N) |
swap(other) | 逐元素交换,O(N) |
std::get<I>(a) | 访问编译期下标 I;非成员函数 |
没有 push_back、insert、erase、resize、clear、reserve。使用 std::array<int, 4> a{}; 可把整数元素零初始化。
4.3 deque:双端队列
| 函数 | 作用/复杂度 |
|---|---|
operator[]、at()、front()、back() | 访问元素,O(1) |
push_front(x)、push_back(x) | 两端插入单个元素,O(1) |
emplace_front(...)、emplace_back(...) | 两端直接构造,O(1);C++17 起返回引用 |
pop_front()、pop_back() | 两端删除,O(1),要求非空 |
insert(pos, ...)、emplace(pos, ...) | 中间插入,O(n) |
erase(pos)、erase(first, last) | 删除并返回后继;中间删除 O(n) |
assign(...)、resize(...)、clear() | 整体赋值、调整长度、清空 |
shrink_to_fit() | C++11,请求缩减内存占用 |
支持通用的大小查询、正反向迭代、分配器与交换接口。没有 data()、capacity()、reserve();能随机访问不代表内存连续。
4.4 list:双向链表
| 函数 | 作用/复杂度 |
|---|---|
front()、back() | 首尾元素引用,O(1) |
push_front/back、emplace_front/back、pop_front/back | 两端操作,O(1) |
insert(pos, ...)、emplace(pos, ...) | 在已知位置前插入单个元素,O(1) |
erase(pos) | 删除单个节点,返回后继,O(1) |
erase(first, last) | 删除 k 个节点,O(k) |
assign(...)、resize(...)、clear() | 替换内容、改变长度、清空 |
remove(x)、remove_if(pred) | 真正删除所有匹配节点,O(n) |
unique()、unique(pred) | 删除相邻重复节点,O(n) |
sort()、sort(comp) | 链表稳定排序,O(n log n) |
merge(other)、merge(other, comp) | 合并已按同一规则排序的链表,O(n + m) |
reverse() | 反转链表,O(n) |
splice(pos, other) | 将 other 整条链表接到 pos 前,O(1) |
splice(pos, other, it) | 转移一个节点,O(1) |
splice(pos, other, first, last) | 转移区间;同一链表 O(1),跨链表通常 O(k) |
支持 size()、正反向迭代。没有下标与 data()。sort、merge、splice 通过链表节点操作完成;跨容器拼接/合并需满足分配器相等等前置条件,且不要给出重叠的非法目标位置。
不能 std::sort(l.begin(), l.end()),应使用 l.sort()。 定位到某个下标本身要 O(n),因此“链表插删 O(1)”的前提是已经知道节点位置。
4.5 forward_list:单向链表
| 函数 | 作用 |
|---|---|
before_begin()、cbefore_begin() | 首元素之前的特殊迭代器,不能解引用 |
begin/end、cbegin/cend | 单向遍历 |
front() | 访问首元素 |
push_front、emplace_front、pop_front | 头部增删 |
insert_after(pos, ...) | 在 pos 后插入 |
emplace_after(pos, ...) | 在 pos 后直接构造 |
erase_after(pos) | 删除 pos 后一个节点,返回删除位置的后继 |
erase_after(first, last) | 删除开区间 (first, last) 中的节点 |
splice_after(...) | 转移节点或区间到指定位置之后 |
remove/remove_if、unique、sort、merge、reverse | 链表删除、相邻去重、排序、合并、反转 |
assign(...)、resize(...)、clear() | 替换、调整长度、清空 |
empty()、max_size()、swap()、get_allocator() | 基本管理接口 |
没有 size()、back()、反向迭代器、push_back()、普通 insert/erase。已知前驱时单节点插删 O(1),需要扫描节点的操作按扫描数量计算;整条单链表拼接可能需要找尾节点,不能直接套用 list::splice 的常数复杂度。
std::forward_list<int> f = {1, 2, 3};
auto before = f.before_begin();
f.insert_after(before, 0); // 0, 1, 2, 3
f.erase_after(before); // 1, 2, 3
前驱及删除区间规则参照 C++17 forward_list 条款。
五、有序关联容器
5.1 set / multiset
std::set<int> s = {3, 1, 2}; // 遍历得到 1, 2, 3
std::multiset<int> ms = {1, 1, 2}; // 保留重复值
| 函数 | 作用/返回值 | 复杂度 |
|---|---|---|
insert(x)、emplace(args...) | set 返回 pair<iterator, bool>;multiset 返回 iterator | O(log n) |
insert(hint, x)、emplace_hint(hint, ...) | 提供插入位置提示,返回 iterator | 一般 O(log n),有效提示可均摊 O(1) |
insert(first, last)、insert({...}) | 批量插入 | 一般 O(k log(n + k)) |
find(x) | 任一匹配元素的迭代器,未找到返回 end() | O(log n) |
count(x) | 等价元素数 | set O(log n),multiset O(log n + k) |
lower_bound(x) | 默认升序下第一个 >= x 的位置 | O(log n) |
upper_bound(x) | 默认升序下第一个 > x 的位置 | O(log n) |
equal_range(x) | 返回上述两个边界组成的 pair | O(log n) |
erase(x) | 删除所有等价元素,返回删除数 | O(log n + k) |
erase(it) | 删除单个元素,返回后继 | 均摊 O(1) |
erase(first, last) | 删除区间 | O(log n + k) |
extract(it) / extract(x) | C++17,移出节点,返回节点句柄 | 按迭代器均摊 O(1),按键 O(log n) |
insert(node) | C++17,插入节点句柄 | O(log n);返回类型与普通插入不同 |
merge(other) | C++17,转移能插入的节点 | 设来源大小 m,一般 O(m log(n + m)) |
key_comp()、value_comp() | 返回排序比较器 | 用于观察排序规则 |
clear()、swap()、size() 等 | 通用管理接口 | 见通用成员函数 |
contains(x) | C++20,是否存在 | O(log n) |
自定义比较器时,“相等”按 !comp(a,b) && !comp(b,a) 判断,不一定是 a == b。lower_bound、upper_bound 也按比较器定义边界,表中的 >=、> 仅适用于默认升序。
auto it = ms.find(1);
if (it != ms.end()) ms.erase(it); // 只删除一个 1
ms.erase(1); // 删除剩余的所有 1
有序集合中的键不能通过迭代器直接修改,否则会破坏排序。extract() 取出的非空节点句柄可以修改值,再插回。
5.2 map / multimap
map<K, V> 的元素类型是 pair<const K, V>:键不可直接改,值可改。
| 函数 | 作用/返回值 |
|---|---|
mp[key] | 仅 map;存在则返回值引用,不存在则插入默认初始化的值 |
mp.at(key) | 仅 map;返回值引用,缺失时抛 out_of_range |
insert({key, value}) | map 返回 pair<iterator, bool>,已有键不会覆盖;multimap 返回 iterator |
emplace(key, value) | 直接构造键值对,已有 map 键不会覆盖 |
try_emplace(key, args...) | 仅 map,C++17;键不存在时才构造映射值 |
insert_or_assign(key, value) | 仅 map,C++17;不存在就插入,存在就更新 |
find/count/lower_bound/upper_bound/equal_range | 查键,规则同 set 家族 |
erase(key) / erase(it) / erase(first, last) | 按键、单个位置、区间删除 |
extract(...)、insert(node)、merge(...) | C++17,节点转移;map 节点有 key()、mapped() |
key_comp()、value_comp() | 键比较器/元素比较器 |
contains(key) | C++20,查键是否存在 |
除了上面差异,还支持有序关联容器的提示插入、迭代器与管理接口。按键访问通常 O(log n);multimap 的计数和按键删除要额外处理 k 个匹配项。
std::map<std::string, int> cnt;
++cnt["apple"]; // 缺失时先创建 0,再加 1
cnt.insert({"apple", 9}); // 已存在,不覆盖
cnt.insert_or_assign("apple", 9); // 更新为 9
auto it = cnt.find("pear"); // 查询不会插入
if (it != cnt.end()) {
int value = it->second;
(void)value;
}
for (const auto& [key, value] : cnt) { // C++17 结构化绑定
// 按键升序遍历
}
multimap 没有 operator[]、at、try_emplace、insert_or_assign。同键的全部元素用 equal_range(key) 遍历;单次 find 不会返回全部重复项。关联容器的精确接口约束见 C++17 关联容器要求。
六、无序关联容器
6.1 四种类型
| 类型 | 内容 | 重复键 |
|---|---|---|
unordered_set<T> | 值 | 不允许 |
unordered_multiset<T> | 值 | 允许 |
unordered_map<K, V> | 键值对 | 不允许 |
unordered_multimap<K, V> | 键值对 | 允许 |
支持 insert、提示插入、emplace、emplace_hint、find、count、equal_range、erase、clear、swap、begin/end、cbegin/cend、大小与分配器接口。
仅 unordered_map 有 operator[]、at、try_emplace、insert_or_assign;四类都有 C++17 的 extract、节点插入、merge,以及 C++20 的 contains。
没有 lower_bound、upper_bound、反向迭代接口和排序比较器。 equal_range 只是返回等价键所在区间,不表示有序边界。
6.2 哈希专属函数
| 函数 | 作用 |
|---|---|
bucket_count() | 当前桶数 |
max_bucket_count() | 实现支持的最大桶数 |
bucket(key) | key 所对应的桶编号 |
bucket_size(i) | 第 i 个桶的元素数 |
begin(i)、end(i)、cbegin(i)、cend(i) | 遍历单个桶 |
load_factor() | 当前负载因子,即 size() / bucket_count() |
max_load_factor() | 获取负载因子上限 |
max_load_factor(x) | 设置负载因子上限 |
rehash(n) | 请求至少 n 个桶,且满足当前大小与负载约束 |
reserve(n) | 按可容纳 n 个元素预留桶容量 |
hash_function() | 返回哈希函数对象 |
key_eq() | 返回键相等判定对象 |
std::unordered_map<int, int> cnt;
cnt.max_load_factor(0.7f);
cnt.reserve(100000);
++cnt[42];
键相等时必须有相同哈希值。重哈希平均 O(n)、最坏 O(n²),可能使迭代器失效,但不会使存活元素的引用或指针失效。预留容量有助于减少重哈希次数,不能保证消除哈希冲突。来源:C++17 无序容器要求。
七、容器适配器
参考:OI Wiki:容器适配器。三个适配器都不公开迭代器,也没有 clear()。
7.1 stack:栈
std::stack<int> st; // 默认底层容器 deque<int>
| 函数 | 作用 |
|---|---|
push(x) | 压栈 |
emplace(args...) | 在栈顶构造;C++17 起返回引用 |
top() | 返回栈顶元素引用 |
pop() | 删除栈顶,返回 void |
size()、empty() | 大小、是否为空 |
swap(other) | 交换两个栈 |
默认底层 deque 时,单元素操作 O(1)。可用 vector 或 list 等满足接口的容器作为底层;若用 vector,尾插为均摊 O(1)。
7.2 queue:队列
std::queue<int> q; // 默认底层容器 deque<int>
| 函数 | 作用 |
|---|---|
push(x) | 队尾入队 |
emplace(args...) | 队尾构造;C++17 起返回引用 |
front()、back() | 队首、队尾引用 |
pop() | 删除队首,返回 void |
size()、empty()、swap(other) | 大小、判空、交换 |
默认单元素操作 O(1)。底层需支持 pop_front,所以不能直接使用 vector。
7.3 priority_queue:优先队列
std::priority_queue<int> big; // 默认大根堆
std::priority_queue<int, std::vector<int>, std::greater<int>> small;
| 函数 | 作用/返回值 | 复杂度 |
|---|---|---|
top() | 堆顶元素的 const 引用 | O(1) |
push(x) | 添加元素 | O(log n) 次比较;底层扩容单次可能 O(n) |
emplace(args...) | 直接构造新元素,返回 void | 同 push |
pop() | 删除堆顶,返回 void | O(log n) |
size()、empty() | 大小、判空 | O(1) |
swap(other) | 交换堆 | 通常 O(1),取决于底层与比较器 |
| 由现有区间批量构造 | 建堆 | O(n) 次比较,另计数据复制/构造 |
不能直接删除任意位置,也没有标准的 decrease_key。Dijkstra 常用“插入新状态,弹出时跳过旧状态”。默认比较器 less<T> 使最大的元素优先,greater<T> 使最小的元素优先。
三个适配器的 pop() 都不返回元素,取值要先访问再删除:
if (!q.empty()) {
int x = q.front();
q.pop();
(void)x;
}
八、string、string_view、bitset
8.1 string:字符串
std::string 是 std::basic_string<char> 的别名。参考:OI Wiki:string。
std::string s = "hello";
std::string t(3, 'a'); // "aaa"
| 函数/操作 | 作用/返回值 | 注意 |
|---|---|---|
size()、length() | 字符单元数,二者等价 | O(1),UTF-8 中文通常占多个 char |
empty()、max_size()、capacity() | 判空、理论最大长度、容量 | 不要把容量当长度 |
operator[]、at() | 访问字符 | O(1);读取实际字符时下标须 < size() |
front()、back() | 首尾字符引用 | 非空 |
data()、c_str() | 底层字符指针 | C++17 非 const string 的 data 可写已有字符;尾部有零字符 |
begin/end、cbegin/cend、rbegin/rend、crbegin/crend | 正反向迭代 | 可交给适用的算法 |
operator+=、append(...) | 在尾部追加 | 不断拼接需考虑分配与复制开销 |
operator+ | 生成拼接后的新字符串 | 非成员操作,通常需要复制 |
push_back(ch)、pop_back() | 添加/删除最后一个字符 | pop 要求非空 |
insert(pos, str) | 在下标 pos 前插入 | 还有迭代器等重载,通常需移动后缀 |
erase(pos, count) | 从 pos 起删 count 个字符 | 参数是起点与数量,不是终点 |
erase(it)、erase(first, last) | 按迭代器删除,返回后继 | 区间是左闭右开 |
replace(pos, count, str) | 替换指定部分 | 还有迭代器等重载 |
assign(...)、operator= | 整体赋值 | 覆盖已有内容 |
substr(pos, count) | 返回子串副本 | O(返回长度),pos 超过 size 抛异常 |
copy(dest, count, pos) | 复制到外部字符数组,返回复制数 | 不会自动追加 '\0' |
find(x, pos) | 从 pos 向后找字符/子串 | 返回下标或 string::npos |
rfind(x, pos) | 反向查找 | 返回下标或 npos |
find_first_of(chars, pos) | 首个属于 chars 中任一字符的位置 | 是字符集合,不是子串匹配 |
find_last_of(chars, pos) | 最后一个属于字符集合的位置 | 同上 |
find_first_not_of(chars, pos) | 首个不属于字符集合的位置 | 可用于跳过分隔符 |
find_last_not_of(chars, pos) | 最后一个不属于字符集合的位置 | 可用于裁去尾部分隔符 |
compare(...) | 字典序比较,返回负/零/正整数 | 不保证恰好是 -1/0/1 |
reserve(n)、resize(n[, ch])、shrink_to_fit() | 预留、调整长度、请求缩容 | 含义与 vector 类似,具体失效规则见标准 |
clear()、swap(other)、get_allocator() | 清空、交换、分配器 | 清空不保证释放容量 |
==、!=、<、>、<=、>= | 字符串比较 | 按字典序,非按长度 |
starts_with(x)、ends_with(x) | C++20,前缀/后缀判断 | 有字符、字符串等重载 |
contains(x) | C++23,是否含字符/子串 | C++17 用 find |
resize_and_overwrite(n, op) | C++23,通过回调写入并确定新长度 | 回调必须遵守缓冲区和返回长度约束 |
字符串查找不要一概当作 O(log n) 或 O(n):朴素子串匹配可能 O(nm),具体标准复杂度保证与函数/重载有关;需要稳定线性上界的长串匹配可使用 KMP 等算法。
auto pos = s.find("ll");
if (pos != std::string::npos) {
std::string part = s.substr(pos, 2); // "ll"
(void)part;
}
s.erase(1, 2); // "hello" -> "hlo"
相关非成员函数:
| 函数 | 作用 |
|---|---|
getline(in, s[, delim]) | 读至分隔符,分隔符被消耗但不放入 s |
stoi/stol/stoll | 转有符号整数,可给解析终点与进制 |
stoul/stoull | 转无符号整数 |
stof/stod/stold | 转浮点数 |
to_string(x) | 数值转 string |
to_wstring(x) | 数值转 wstring |
swap(a, b) | 交换字符串 |
转换失败可抛 invalid_argument,超范围可抛 out_of_range。stoi("123abc") 可只解析前缀,若要确认整串合法需检查解析终点。
8.2 string_view:字符串视图,C++17
string_view 只保存指针与长度,不管理原字符串的生命周期。
| 函数 | 作用 |
|---|---|
size()、length()、max_size()、empty() | 长度与判空 |
operator[]、at()、front()、back()、data() | 只读字符访问;data 不保证以零字符结尾 |
| 正反向迭代接口 | 只读遍历 |
remove_prefix(n)、remove_suffix(n) | 调整视图边界,O(1),n 不得超过当前长度 |
substr(pos, count) | 创建另一视图,O(1) |
copy(dest, count, pos) | 复制字符,不补零字符 |
find/rfind、find_first_of/find_last_of | 查找字符、子串、字符集合 |
find_first_not_of/find_last_not_of、compare | 排除集合查找、比较 |
swap(other) | 交换视图 |
starts_with/ends_with、contains | 分别 C++20、C++23 |
std::string owner = "abcdef";
std::string_view view(owner);
view.remove_prefix(2); // view 是 "cdef",owner 仍为 "abcdef"
不要把临时字符串的结果留给 view,例如 std::string_view v = owner.substr(1); 在该语句结束后就悬空;应先让 string 保存子串,或对 string_view(owner) 调用 substr。
8.3 bitset:固定大小位集合
参考:OI Wiki:bitset。
std::bitset<8> b(std::string("00001101"));
// 下标 0 是最低位,即显示字符串最右边的位
| 函数/运算 | 作用 |
|---|---|
b[i] | 第 i 位,非 const 时是可赋值代理;不检查越界 |
test(i) | 第 i 位是否为 1,检查越界 |
set() / set(i[, value]) | 全部置 1 / 设置某位 |
reset() / reset(i) | 全部置 0 / 某位置 0 |
flip() / flip(i) | 全部取反 / 某位取反 |
count() | 1 的个数 |
any() | 至少一位为 1 |
none() | 全部为 0 |
all() | 全部为 1,C++11 |
size() | 固定长度 N |
to_string() | 转二进制字符串 |
to_ulong()、to_ullong() | 转整数;值过大时抛 overflow_error |
&、|、^、~ | 按位与、或、异或、取反 |
&=、|=、^= | 原地按位运算 |
<<、>>、<<=、>>= | 移位,空出的位补 0,越过 N 的位丢弃 |
==、!= | 比较整个 bitset 是否相同 |
没有 push_back、动态 resize 或普通迭代器。批量位运算在常见实现中约需 O(ceil(N / W)) 个机器字操作,W 常为 32/64;这不是标准对具体存储布局的保证。_Find_first()、_Find_next() 是部分实现的扩展,不属于标准接口。
九、pair、tuple 与实用工具
9.1 pair
参考:OI Wiki:pair。
| 接口 | 作用 |
|---|---|
pair<A, B>{a, b} | 构造二元组 |
first、second | 第一、第二个成员;它们不是函数 |
make_pair(a, b) | 推导类型并构造 pair |
get<0>(p)、get<1>(p) | 按位置取元素 |
get<T>(p) | 按唯一类型取元素,C++14 |
p.swap(q)、swap(p, q) | 交换两个 pair |
operator= | 赋值,受成员可赋值性限制 |
| 比较运算符 | 先比 first,相等再比 second |
tuple_size、tuple_element | 元素数与元素类型;类型特征 |
std::pair<int, int> p = {2, 5};
auto [x, y] = p; // C++17;按值拆开,改变 x 不改变 p
auto& [a, b] = p; // 按引用绑定,可修改 p
9.2 tuple
| 接口 | 作用 |
|---|---|
tuple<T...>{...}、make_tuple(...) | 创建多元组 |
get<I>(t)、get<T>(t) | 按位置/唯一类型取成员 |
tie(a, b, ...) | 创建引用 tuple,可接收解包赋值 |
ignore | tie 解包时忽略某项 |
forward_as_tuple(...) | 创建转发引用 tuple;注意引用对象生命周期 |
tuple_cat(...) | 连接多个 tuple/pair 等 tuple-like 对象 |
apply(f, t) | C++17,将元素展开为 f 的参数 |
make_from_tuple<T>(t) | C++17,将元素展开为 T 的构造参数 |
swap、赋值、比较 | 按成员操作;比较为字典序 |
tuple_size、tuple_element | 查询元素个数与类型 |
int x = 0, y = 0;
std::tie(x, y) = std::make_pair(3, 4);
auto t = std::make_tuple(2, 3);
int sum = std::apply([](int a, int b) { return a + b; }, t);
9.3 <utility> 常用函数
| 函数 | 作用 | 注意 |
|---|---|---|
swap(a, b) | 交换 | 成本取决于类型 |
exchange(obj, new_value) | C++14,更新 obj 并返回旧值 | 与 swap 不同 |
move(x) | 把表达式转为可用于移动的值类别 | 本身不搬数据 |
forward<T>(x) | 模板中的完美转发 | 常与转发引用配合 |
move_if_noexcept(x) | 根据移动异常保证与可复制性选择引用类别 | 泛型设施 |
as_const(x) | C++17,得到 const 左值引用 | 不复制对象 |
declval<T>() | 在不求值上下文中模拟 T 的表达式 | 不能运行时调用 |
十、迭代器
参考:OI Wiki:迭代器。迭代器像指针,但通常不会自动替你检查所有越界或失效情况。
10.1 分类
| 迭代器类别 | 主要能力 | 典型例子 |
|---|---|---|
| 输入 Input | 单趟读取、++ | istream_iterator |
| 输出 Output | 单趟写入、++ | ostream_iterator、插入迭代器 |
| 前向 Forward | 可多趟遍历 | forward_list、unordered 容器 |
| 双向 Bidirectional | 在前向基础上可 -- | list、set、map |
| 随机访问 Random Access | 可 +n、-n、相减、顺序比较 | vector、deque、array |
| 连续 Contiguous | 随机访问且元素地址连续,C++20 概念 | 普通 vector、array、string、指针 |
输入与输出不是简单的同一条继承链;输出迭代器不保证可读取。vector<bool> 不满足普通连续容器的元素引用要求。
10.2 <iterator> 函数与适配器
| 函数/类型 | 用途 |
|---|---|
begin(c)、end(c) | 非成员版本,也适用于内置数组 |
cbegin(c)、cend(c) | C++14,只读版本 |
rbegin(c)、rend(c)、crbegin(c)、crend(c) | C++14,非成员反向版本 |
size(c)、empty(c)、data(c) | C++17,非成员查询,内置数组也适用 |
advance(it, n) | 原地把 it 移动 n 步,返回 void |
next(it[, n])、prev(it[, n]) | 返回移动后的迭代器副本 |
distance(first, last) | 返回区间距离 |
iter_swap(a, b) | 交换两个迭代器指向的元素,声明在 <algorithm> |
back_inserter(c) | 创建尾部插入迭代器,要求 push_back |
front_inserter(c) | 创建头部插入迭代器,要求 push_front |
inserter(c, pos) | 创建指定位置插入迭代器,要求 insert |
reverse_iterator<It>、make_reverse_iterator(it) | 反向迭代;工厂函数 C++14 |
move_iterator<It>、make_move_iterator(it) | 解引用时产生移动用的引用 |
istream_iterator<T>、ostream_iterator<T> | 流输入/输出迭代器 |
istreambuf_iterator、ostreambuf_iterator | 流缓冲区字符迭代器 |
iterator_traits<It> | 查询迭代器关联类型 |
advance、next、prev、distance 对随机访问迭代器通常 O(1),对链表等通常 O(步数)。负向移动需要双向能力且不能越界;distance 用于非随机访问迭代器时,last 必须可由 first 向前到达。
std::vector<int> src = {1, 2, 3}, dst;
std::copy(src.begin(), src.end(), std::back_inserter(dst));
back_inserter 自动调用 push_back,不用事先给 dst 调整长度。反向迭代器 rit 的 base() 指向其正向后继:rbegin().base() == end()。
十一、algorithm 函数分类表
头文件:<algorithm>。以下列出 C++17 的普通算法函数名,默认不展开执行策略重载;swap 的基础声明属于 <utility>,这里随交换算法一起列出。
表中 out 表示可写输出迭代器。普通复制/转换算法不会自动扩容目标容器;目标必须有足够的已有元素,或使用 back_inserter。如果存在允许的输入输出重叠,需要遵守对应算法的方向和重叠约束。
11.1 判断、遍历与计数
| 函数 | 典型形式 | 返回/含义 | 复杂度 |
|---|---|---|---|
all_of | all_of(first, last, pred) | 全部满足,bool | O(n) |
any_of | any_of(first, last, pred) | 至少一个满足,bool | O(n) |
none_of | none_of(first, last, pred) | 全部不满足,bool | O(n) |
for_each | for_each(first, last, f) | 对每项调用 f,普通重载返回函数对象 | O(n) |
for_each_n | for_each_n(first, n, f) | C++17,处理前 n 项,返回后继迭代器 | O(n) |
count | count(first, last, x) | 等于 x 的数量 | O(n) |
count_if | count_if(first, last, pred) | 满足条件的数量 | O(n) |
空区间上 all_of 和 none_of 返回 true,any_of 返回 false。
11.2 查找、匹配与比较
| 函数 | 用途/返回值 | 复杂度或条件 |
|---|---|---|
find(first, last, x) | 第一个等于 x 的位置,或 last | O(n) |
find_if(first, last, pred) | 第一个满足条件的位置 | O(n) |
find_if_not(first, last, pred) | 第一个不满足条件的位置 | O(n) |
adjacent_find(first, last[, pred]) | 首对相邻匹配元素中前一个的位置 | O(n) |
find_first_of(f1, l1, f2, l2[, pred]) | 第一段中首个与第二段任一元素匹配的位置 | O(nm) |
search(f1, l1, f2, l2[, pred]) | 子序列首次完整匹配的起点 | 一般上界 O(nm) |
search(first, last, searcher) | C++17,使用搜索器对象 | 依搜索器而定 |
search_n(first, last, k, x[, pred]) | 首段连续 k 个匹配值的起点 | 线性 |
find_end(f1, l1, f2, l2[, pred]) | 子序列最后一次匹配的起点 | 一般上界 O(nm) |
mismatch(f1, l1, f2, l2[, pred]) | 首个不匹配位置的迭代器 pair | O(min(n,m)) |
equal(f1, l1, f2, l2[, pred]) | 两段是否逐项相等 | O(n + m) 上界 |
is_permutation(f1, l1, f2, l2[, pred]) | 是否相同多重集合,忽略排列 | 一般 O(n²) |
lexicographical_compare(f1, l1, f2, l2[, comp]) | 第一段字典序是否小于第二段 | O(min(n,m)) |
equal、mismatch、is_permutation 也有只给第二段起点的旧重载;使用时必须保证第二段足够长。find_end 的目标是完整子序列最后一次出现,find_first_of 的目标是任意一个候选元素,二者不同。
相关接口依据:C++17 非修改算法。
11.3 复制、移动、交换与变换
| 函数 | 用途/主要返回值 |
|---|---|
copy(first, last, out) | 复制,返回输出尾后位置 |
copy_n(first, n, out) | 复制 n 项,返回输出尾后位置 |
copy_if(first, last, out, pred) | 只复制满足条件的项,返回输出尾后位置 |
copy_backward(first, last, out_last) | 从后向前复制,返回输出起点 |
move(first, last, out) | 对区间逐项执行移动赋值,返回输出尾后位置 |
move_backward(first, last, out_last) | 从后向前移动,返回输出起点 |
swap(a, b) | 交换两个对象 |
iter_swap(it1, it2) | 交换指向的元素 |
swap_ranges(first1, last1, first2) | 交换两段等长内容,返回第二段尾后位置 |
transform(first, last, out, f) | 一元映射,返回输出尾后位置 |
transform(first1, last1, first2, out, f) | 二元映射,第二段须足够长 |
区间算法通常 O(n),swap/iter_swap 的成本由元素类型决定。std::move(x) 是 <utility> 的类型转换函数;std::move(first,last,out) 是 <algorithm> 的区间移动算法。
11.4 填充、生成与替换
| 函数 | 用途 |
|---|---|
fill(first, last, x) | 全区间赋值为 x,返回 void |
fill_n(first, n, x) | 前 n 项赋值为 x,返回输出尾后位置 |
generate(first, last, gen) | 每项赋值为一次 gen() 的结果,返回 void |
generate_n(first, n, gen) | 生成 n 项,返回输出尾后位置 |
replace(first, last, old, value) | 原地替换等于 old 的项 |
replace_if(first, last, pred, value) | 原地替换满足条件的项 |
replace_copy(first, last, out, old, value) | 复制到输出,同时替换 |
replace_copy_if(first, last, out, pred, value) | 复制到输出,同时按条件替换 |
均按处理元素数线性计;带 copy 的版本返回输出尾后位置,普通 replace 返回 void。
11.5 移除与去重
| 函数 | 用途/返回值 |
|---|---|
remove(first, last, x) | 将不等于 x 的项向前压紧,返回逻辑新终点 |
remove_if(first, last, pred) | 保留不满足条件的项,返回逻辑新终点 |
remove_copy(first, last, out, x) | 复制不等于 x 的项,返回输出尾后位置 |
remove_copy_if(first, last, out, pred) | 复制不满足条件的项,返回输出尾后位置 |
unique(first, last[, pred]) | 压缩相邻重复项,返回逻辑新终点 |
unique_copy(first, last, out[, pred]) | 将相邻去重结果写到输出,返回输出尾后位置 |
这些操作通常 O(n)。remove、unique 不会改变容器 size,逻辑新终点之后的元素不要作为有效结果使用。
std::vector<int> v = {3, 1, 1, 2, 3};
v.erase(std::remove(v.begin(), v.end(), 2), v.end());
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end()); // 1, 3
list::remove 是真正删除节点的成员函数;std::remove 是压紧区间的通用算法。相关语义依据:C++17 修改序列算法。
11.6 反转、旋转、随机化
| 函数 | 用途/返回值 | 复杂度 |
|---|---|---|
reverse(first, last) | 原地反转,返回 void | O(n),至少双向迭代器 |
reverse_copy(first, last, out) | 反转后写入输出,返回输出尾后位置 | O(n) |
rotate(first, middle, last) | 把 [middle,last) 移到前面;返回原 first 的新位置 | O(n),至少前向迭代器 |
rotate_copy(first, middle, last, out) | 旋转结果写到输出,返回输出尾后位置 | O(n) |
shuffle(first, last, rng) | 随机重排,返回 void | O(n),随机访问 |
sample(first, last, out, k, rng) | C++17,随机抽至多 k 项,返回输出尾后位置 | O(n) |
std::mt19937 rng(std::random_device{}()); // <random>
std::shuffle(v.begin(), v.end(), rng);
sample 有迭代器类别约束:若输入不是前向迭代器,输出必须支持随机访问。random_shuffle 在 C++14 被弃用、C++17 移除。来源:OI Wiki:STL 算法。
11.7 排序与部分排序
| 函数 | 用途/返回值 | 复杂度/特点 |
|---|---|---|
sort(first, last[, comp]) | 全部排序,返回 void | O(n log n),不保证稳定 |
stable_sort(first, last[, comp]) | 稳定排序,等价项相对顺序不变 | 有足够辅助内存 O(n log n),否则 O(n log² n) 比较 |
partial_sort(first, middle, last[, comp]) | 最小 k 项有序放在前 k 个位置 | O(n log k),其余部分不保证排序 |
partial_sort_copy(f1, l1, f2, l2[, comp]) | 将最小若干项排序写入第二段,返回有效输出尾后位置 | O(n log k),输入不变 |
nth_element(first, nth, last[, comp]) | nth 上的值与完整排序后相同,并分隔前后部分 | 普通重载平均 O(n),两边内部无序 |
is_sorted(first, last[, comp]) | 是否已经有序,bool | O(n) |
is_sorted_until(first, last[, comp]) | 首个使区间不再有序的位置,完全有序返回 last | O(n) |
前三种原地排序与 nth_element 要求随机访问迭代器。partial_sort_copy 的输出要求随机访问,输入可以更弱。nth_element 在有重复值时允许左右出现与 nth 等价的元素,不能理解成“左侧全严格小于、右侧全严格大于”。
int k = 2; // 求第 2 小,1 <= k <= v.size()
std::nth_element(v.begin(), v.begin() + (k - 1), v.end());
int answer = v[k - 1];
11.8 二分查找
| 函数 | 默认升序下的含义 | 返回值 |
|---|---|---|
lower_bound(first, last, x) | 第一个 >= x | iterator |
upper_bound(first, last, x) | 第一个 > x | iterator |
equal_range(first, last, x) | 等于 x 的区间边界 | pair<iterator, iterator> |
binary_search(first, last, x) | 是否存在与 x 等价的元素 | bool |
都可给比较器。对完整升序数组直接使用最容易保证正确;标准的精确前提是按相关比较表达式分区。比较次数 O(log n),但对非随机访问迭代器可能发生 O(n) 次迭代器移动。
set/map 使用 s.lower_bound(x) 这类成员函数,可保持 O(log n);不要为了查树容器写通用 std::lower_bound(s.begin(), s.end(), x)。
std::vector<int> a = {1, 2, 2, 4, 7};
auto l = std::lower_bound(a.begin(), a.end(), 2);
auto r = std::upper_bound(a.begin(), a.end(), 2);
auto number = r - l; // 2
11.9 分区
| 函数 | 用途/返回值 |
|---|---|
partition(first, last, pred) | 满足条件的项移到前面,返回分界;不保证稳定 |
stable_partition(first, last, pred) | 同上,并保持两组内部相对顺序 |
partition_copy(first, last, out_true, out_false, pred) | 分别写入两个输出,返回两个输出尾后位置的 pair |
is_partitioned(first, last, pred) | 是否满足“所有 true 在 false 之前”,bool |
partition_point(first, last, pred) | 找首个 false 的位置,输入必须已分区 |
普通分区类操作一般线性。stable_partition 谓词调用 O(n),无足够辅助空间时交换可达 O(n log n)。partition_point 判断次数 O(log n),非随机访问迭代器仍可能线性移动。
11.10 合并与有序集合运算
以下输入区间需按同一比较规则排序;集合运算也允许重复元素,按重复次数处理。
| 函数 | 用途/返回值 |
|---|---|
merge(f1, l1, f2, l2, out[, comp]) | 两有序区间稳定合并,返回输出尾后位置 |
inplace_merge(first, middle, last[, comp]) | 稳定合并相邻的两个有序段,返回 void |
includes(f1, l1, f2, l2[, comp]) | 第一段是否包含第二段的全部元素及重数 |
set_union(f1, l1, f2, l2, out[, comp]) | 并集,每值出现次数取 max |
set_intersection(...) | 交集,每值出现次数取 min |
set_difference(...) | A 减 B,每值出现次数取 max(A 次数 - B 次数, 0) |
set_symmetric_difference(...) | 对称差,每值出现次数取绝对差 |
除 inplace_merge 外一般 O(n + m);inplace_merge 有足够辅助空间时线性,否则 O(n log n) 比较。四种 set_ 算法返回输出尾后位置;名称中的 set 不要求输入来自 std::set。
11.11 堆操作
这些算法把随机访问区间当作堆。默认大根堆,可用 greater<T> 构造小根堆。
| 函数 | 用途 | 复杂度 |
|---|---|---|
make_heap(first, last[, comp]) | 将整个区间建堆 | O(n) |
push_heap(first, last[, comp]) | 将末尾新元素纳入已有堆,前 n-1 项原本是堆 | O(log n) |
pop_heap(first, last[, comp]) | 把堆顶移到末尾,并使前 n-1 项成堆 | O(log n),区间非空 |
sort_heap(first, last[, comp]) | 将堆变成有序区间 | O(n log n) |
is_heap(first, last[, comp]) | 是否为堆 | O(n) |
is_heap_until(first, last[, comp]) | 首个违反堆性质的位置 | O(n) |
前四个返回 void。pop_heap 不删除元素,vector 上还要 pop_back();push_heap 也不添加元素,要先 push_back()。
11.12 最值与区间限制
| 函数 | 作用/返回值 |
|---|---|
min(a,b)、max(a,b) | 返回较小/较大参数的 const 引用 |
min({values...})、max({values...}) | 初始化列表中的最值,返回值 |
minmax(a,b) | 返回最小/最大参数引用组成的 pair |
minmax({values...}) | 初始化列表最值 pair,按值返回 |
min_element(first,last[,comp]) | 最小元素迭代器,空区间返回 last |
max_element(first,last[,comp]) | 最大元素迭代器,空区间返回 last |
minmax_element(first,last[,comp]) | 最小/最大元素迭代器 pair |
clamp(x,lo,hi[,comp]) | C++17,将 x 限制到边界,返回 const 引用;lo 不得大于 hi |
区间版本 O(n),两参数版本 O(1)。min_element/max_element 返回首次最值;minmax_element 返回首次最小值和最后一次最大值。初始化列表最值要求非空;不要把临时对象参与的引用返回值保存成长期引用。
11.13 排列
| 函数 | 作用 | 返回 |
|---|---|---|
next_permutation(first,last[,comp]) | 变为字典序下一个排列;已经最大时变回最小 | 存在下一个时 true,否则 false |
prev_permutation(first,last[,comp]) | 变为字典序上一个排列;已经最小时变回最大 | 存在上一个时 true,否则 false |
单次 O(n),至少双向迭代器。从升序排列开始循环 next_permutation,可枚举全部不同排列;空序列在 do-while 写法中也会执行一次循环体。
std::sort(v.begin(), v.end());
do {
// 处理当前排列
} while (std::next_permutation(v.begin(), v.end()));
排序、二分、分区、集合、堆、最值与排列的精确保证参照 C++17 排序及相关算法。
十二、numeric 数值算法
头文件:<numeric>。
| 函数 | 典型用法/作用 | 说明 |
|---|---|---|
accumulate | accumulate(first,last,init[,op]),依次累积 | 顺序明确,累加类型由 init 决定 |
inner_product | inner_product(f1,l1,f2,init[,op1,op2]) | 默认点积;第二段须足够长 |
partial_sum | partial_sum(first,last,out[,op]) | 默认前缀和,无初始值参数 |
adjacent_difference | adjacent_difference(first,last,out[,op]) | 首项复制,之后默认当前项减前项 |
iota | iota(first,last,start) | C++11,依次填入 start、start+1… |
reduce | reduce(first,last[,init[,op]]) | C++17,允许重组运算顺序 |
transform_reduce | 变换后归约;单区间或双区间 | C++17,双区间默认计算内积式归约 |
inclusive_scan | 包含当前元素的扫描 | C++17,例如 1,2,3 -> 1,3,6 |
exclusive_scan | 不包含当前元素的扫描,需 init | C++17,例如 init=0 时 1,2,3 -> 0,1,3 |
transform_inclusive_scan | 先一元变换,再包含式扫描 | C++17 |
transform_exclusive_scan | 先一元变换,再排除式扫描 | C++17 |
gcd(a,b) | 最大公约数 | C++17,整数类型;结果所需绝对值必须可表示 |
lcm(a,b) | 最小公倍数 | C++17,结果必须可表示;任一参数为零时结果为零 |
midpoint(a,b) | C++20,安全计算中点 | 有算术及同一数组内指针版本 |
前十一种区间算法通常线性;写输出的扫描、差分、前缀和函数返回输出尾后位置,iota 返回 void,归约类返回计算值。gcd/lcm 的复杂度标准未规定,常见实现基于欧几里得算法。
std::vector<int> a = {1, 2, 3};
long long sum = std::accumulate(a.begin(), a.end(), 0LL);
std::vector<long long> prefix(a.size());
std::partial_sum(a.begin(), a.end(), prefix.begin()); // 1, 3, 6
partial_sum 的内部累积类型取自输入元素类型,仅把输出改成 long long 不足以防止 int 累加溢出。需要大数前缀和时,用 long long 输入,或自己用 long long 累加器循环。
accumulate(..., 0) 可能在 int 中溢出,应按需求使用 0LL 或 0.0。reduce 不是简单改名的 accumulate:非结合或非交换运算,以及浮点运算,可能得到不同结果;并行时还要注意函数对象的线程安全。来源:C++17 数值算法。
十三、函数对象与分配器
13.1 <functional> 函数对象
| 类模板 | 调用时的效果 |
|---|---|
less<T>、greater<T> | <、> |
less_equal<T>、greater_equal<T> | <=、>= |
equal_to<T>、not_equal_to<T> | ==、!= |
plus<T>、minus<T>、multiplies<T> | +、-、* |
divides<T>、modulus<T>、negate<T> | /、%、一元负号 |
logical_and<T>、logical_or<T>、logical_not<T> | &&、||、! |
bit_and<T>、bit_or<T>、bit_xor<T>、bit_not<T> | &、|、^、~;bit_not 为 C++14 |
hash<T> | 产生哈希值,支持的 T 由标准或合法特化决定 |
这些对象通常通过 operator() 调用。C++14 的 less<> 等透明版本可以比较不同但可比较的类型。less_equal 不能直接用作 sort/set 的排序比较器,因为 comp(x,x) 必须为 false。
| 工具 | 接口/用途 |
|---|---|
function<R(Args...)> | 保存可调用对象;调用 operator()、判空 operator bool、swap、target_type、target<T> |
reference_wrapper<T> | 可复制引用包装;get() 取引用,包装可调用对象时也可调用 |
ref(x)、cref(x) | 创建可写/只读引用包装 |
bind(f, args...) | 绑定参数,配合 placeholders::_1 等;一般 lambda 更易读 |
mem_fn(ptr) | 将成员指针包装为可调用对象 |
invoke(f, args...) | C++17,统一调用普通函数、函数对象、成员指针等 |
not_fn(f) | C++17,包装后把返回值逻辑取反 |
default_searcher | C++17,普通搜索器 |
boyer_moore_searcher、boyer_moore_horspool_searcher | C++17,字符串/序列搜索器,要求相应迭代器与匹配约束 |
空的 std::function 被调用会抛 bad_function_call。旧 unary_function、binary_function、bind1st、bind2nd 等已在 C++17 移除;旧 not1/not2、ptr_fun 等也不适合作为新代码模板。工具接口来源:C++17 函数对象。
13.2 lambda 与排序比较器
std::sort(v.begin(), v.end(), [](int a, int b) {
return a > b; // 降序
});
排序比较器必须形成严格弱序:自身不小于自身,比较规则一致且具有必要的传递性。例如按分数降序、同分编号升序:
struct Student { int id, score; };
std::vector<Student> students = {{2, 90}, {1, 90}, {3, 95}};
std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
if (a.score != b.score) return a.score > b.score;
return a.id < b.id;
});
13.3 <memory> 分配器与底层算法
| 类型/函数 | 用途 |
|---|---|
allocator<T>::allocate(n) | 申请容纳 n 个 T 的未初始化存储 |
allocator<T>::deallocate(p,n) | 释放对应存储;须正确匹配之前的分配 |
allocator_traits<A> | 统一调用分配器接口,包括 allocate/deallocate/construct/destroy 等 |
allocator_traits<A>::max_size(a) | 查询理论最大分配数 |
allocator_traits<A>::select_on_container_copy_construction(a) | 按分配器规则选择复制构造使用的分配器 |
addressof(x) | 获得真实地址,绕过重载 operator& |
uninitialized_copy、uninitialized_copy_n | 在未初始化存储上复制构造 |
uninitialized_fill、uninitialized_fill_n | 在未初始化存储上按给定值构造 |
uninitialized_move、uninitialized_move_n | C++17,在未初始化存储上移动构造 |
uninitialized_default_construct、uninitialized_default_construct_n | C++17,默认初始化构造 |
uninitialized_value_construct、uninitialized_value_construct_n | C++17,值初始化构造 |
destroy_at、destroy、destroy_n | C++17,销毁单对象/区间/n 个对象,不释放存储 |
construct_at | C++20,在指定地址构造对象 |
allocator::construct/destroy/max_size/address 等旧成员在 C++17 弃用、C++20 移除;泛型代码通过 allocator_traits 操作更合适。std::pmr 是 C++17 的多态内存资源设施,见 <memory_resource>。
这些是对象生命周期管理接口,不能与对已构造元素赋值的 copy/fill 混用。常规竞赛代码使用容器自动管理内存即可。参照 C++17 allocator、标准草案:特殊内存算法。
13.4 <execution> 执行策略
| 对象 | 版本 | 含义 |
|---|---|---|
std::execution::seq | C++17 | 顺序执行策略 |
std::execution::par | C++17 | 允许并行 |
std::execution::par_unseq | C++17 | 允许并行且无序执行 |
std::execution::unseq | C++20 | 允许无序执行 |
部分算法提供策略重载,例如 sort(std::execution::par, first, last)。实现是否实际并行取决于标准库支持;竞赛中普通重载通常足够。并行/无序策略下,回调不可随意修改共享状态或使用不允许的同步操作;标准执行策略下回调异常可能导致 terminate。
十四、C++20/23 补充
本章为新标准常用接口补充,不声称覆盖 C++20/23 的整个标准库。 使用前确认评测环境的编译选项和标准库支持。
14.1 C++20 常用新增
| 接口 | 作用 |
|---|---|
关联容器 contains(key) | 判定键是否存在 |
std::erase(c, value) | 对支持的序列容器/string 按值删除,返回删除数 |
std::erase_if(c, pred) | 对支持的序列、关联、无序容器/string 条件删除,返回删除数 |
list/forward_list::remove/remove_if/unique | 返回删除数;C++17 返回 void |
string/string_view::starts_with/ends_with | 前缀/后缀检测 |
shift_left/shift_right(first,last,n) | 移动区间内容,返回有效内容边界,不改变容器大小 |
lexicographical_compare_three_way | 字典序三路比较 |
midpoint | <numeric>,计算中点,规避直接相加的溢出 |
std::to_array(...) | <array>,由内置数组/数组字面量创建 array |
std::span<T> | <span>,非拥有的连续区间视图 |
std::identity | <functional>,原样返回传入参数 |
std::bind_front | <functional>,绑定前部参数 |
span 常用接口:begin/end/rbegin/rend、front/back、operator[]、data、size、size_bytes、empty、first、last、subspan;另有非成员 as_bytes、as_writable_bytes。C++20/23 的 span 没有 at(),也不拥有底层数据。
14.2 ranges 与 views,C++20
std::ranges 提供带范围概念约束的算法,许多函数可直接接收容器:
// C++20,需要 <algorithm> 与 <ranges>
std::ranges::sort(v);
auto it = std::ranges::find(v, 3);
auto even = v | std::views::filter([](int x) { return x % 2 == 0; });
| 接口类别 | 常用名字 |
|---|---|
| 范围访问 | ranges::begin/end/cbegin/cend/rbegin/rend/crbegin/crend |
| 范围查询 | ranges::size/ssize/empty/data/cdata |
| 迭代器操作 | ranges::advance/distance/next/prev/iter_move/iter_swap |
| 范围算法 | ranges::sort/find/count/copy/transform/remove/unique/lower_bound 等 |
| 基础视图 | views::all、views::empty<T>、views::single、views::iota |
| 过滤/映射 | views::filter、views::transform |
| 截取 | views::take/drop/take_while/drop_while |
| 重排/拼接 | views::reverse/join/split/lazy_split |
| 结构转换 | views::common/counted、views::elements<N>/keys/values |
| 输入视图 | views::istream<T>(stream) |
不是所有旧算法都有同名 ranges 版本,例如 C++20 没有 ranges::accumulate。range 算法可能返回 subrange 或带多个字段的结果结构,而不是旧版相同返回类型;ranges::remove/unique 同样不真正删除容器元素。
views 通常惰性处理,也不一定拥有底层数据;底层对象失效后不能继续访问视图。来源:C++23 草案中的范围适配器。
14.3 C++23 常用新增
| 接口 | 作用 |
|---|---|
string/string_view::contains | 字符/子串检测 |
string::resize_and_overwrite | 回调直接写入字符缓冲区 |
| 容器范围接口 | 各容器按类型提供 insert_range、append_range、prepend_range、assign_range、insert_range_after 等 |
适配器 push_range | 对 stack/queue/priority_queue 批量加入范围 |
ranges::contains/contains_subrange | 是否包含元素/连续子区间 |
ranges::starts_with/ends_with | 范围前后缀检测 |
ranges::find_last/find_last_if/find_last_if_not | 最后匹配的位置至末尾组成的 subrange |
ranges::fold_left/fold_right | 左折叠/右折叠 |
ranges::fold_left_first/fold_right_last | 从首项/末项作为初始值折叠,可返回 optional |
ranges::fold_left_with_iter/fold_left_first_with_iter | 同时返回最终输入位置与折叠结果 |
ranges::iota | <numeric>,范围填充递增值 |
ranges::to<Container> | 将范围物化为容器 |
views::zip/zip_transform | 多范围并行组合 |
views::adjacent<N>/adjacent_transform<N> | 相邻 N 项组成窗口或变换;pairwise 系列对应 N=2 |
views::chunk/slide/stride | 分块、滑动窗口、按步长取元素 |
views::chunk_by/join_with | 按相邻条件分组、以分隔范围连接 |
views::cartesian_product/repeat/as_rvalue | 笛卡尔积、重复值、转换成移动用视图 |
std::bind_back、std::invoke_r<R> | 绑定尾部参数、指定调用结果类型 |
std::move_only_function | 仅移动的可调用对象包装 |
flat_set/flat_multiset/flat_map/flat_multimap | 平坦的有序容器适配器,见下表 |
本章的新算法和视图属于 std::ranges 或 std::views,不要擅自去掉命名空间改写成同名 std 算法。
14.4 C++23 平坦关联容器
头文件:<flat_set>、<flat_map>。默认用 vector 家族存储有序数据,查找 O(log n),单次插删通常 O(n),迭代器失效规则与树容器不同。
| 类型 | 通用函数家族 | 特有/差异 |
|---|---|---|
flat_set / flat_multiset | begin/end、只读/反向迭代、empty/size/max_size、insert/emplace/emplace_hint/insert_range、erase/clear/swap、find/count/contains/lower_bound/upper_bound/equal_range、key_comp/value_comp | extract() 移出整个底层容器,replace(...) 替换底层有序容器;重复规则分别同 set/multiset |
flat_map / flat_multimap | 上述查找、迭代、插入、删除、比较家族 | keys()、values() 观察底层键/值序列;extract() 移出两序列,replace(keys,values) 替换;仅 flat_map 有 []/at/try_emplace/insert_or_assign |
这些 extract() 是取出整个底层存储,并非 C++17 树容器的 extract(key) 节点句柄操作;它们有右值限定,通常写作 std::move(c).extract()。不要假设 replace 会自动修复未排序、键值长度不一致或非法重复的数据;需满足对应前置条件。来源:C++23 flat_map、flat_set。
十五、迭代器失效与常见坑
15.1 迭代器失效速查
下表聚焦增删操作;clear、赋值、swap、移动及分配器有关操作需再查对应规则。
| 容器 | 插入 | 删除 |
|---|---|---|
vector | 重分配:全部迭代器/引用/指针失效;未重分配:插入位置及之后的迭代器/引用失效,旧 end 失效 | 被删位置及之后的迭代器/引用失效,旧 end 失效 |
deque | 两端插入使迭代器失效,但已有元素引用通常有效;中间插入还会使引用失效 | 中间删除影响全部迭代器/引用;只删头部且未删尾部通常只失效被删项;删尾部还使旧 end 失效 |
list / forward_list | 一般不影响已有节点的迭代器/引用 | 仅被删节点失效 |
| 有序 set/map 家族 | 不影响已有元素的迭代器/引用 | 仅被删元素失效 |
| 无序容器 | 若重哈希,所有迭代器失效;存活元素引用/指针仍有效 | 仅被删元素失效 |
array | 不支持改变长度 | 不支持删除 |
string | 不应直接套 vector 的全部保证;修改及重分配可能使迭代器/指针/视图失效 | 修改后需按具体操作判断 |
extract、节点转移和拼接还有各自规则,不属于普通“删除后对象销毁”的情况。保留某个旧迭代器后再修改容器时,要先确认规则。
15.2 容易写错的代码
| 错误/误解 | 正确用法 |
|---|---|
*c.end() | end 不是元素,查找后先比较 end |
| 空容器调用 front/back/top/pop | 先判断 empty() |
v.reserve(n); v[0] = x;,原 v 为空 | reserve 不创建元素;用 resize 或 push_back |
| 只调用 remove/unique 就认为删掉元素 | 再 erase 返回边界之后的尾部 |
| 认为 unique 能去掉全部重复项 | 它只处理相邻重复;集合式去重通常先 sort |
set 使用下标或 begin()+k | 不支持;若用 next 走到第 k 项,需 O(k) |
| 在未分区、未排序输入上随意二分 | 先满足相应分区前提,通常先 sort |
用 <= 做 sort/set 比较器 | 使用形成严格弱序的规则 |
map[key] 只是查询 | 缺失时会插入;不希望插入用 find/at |
map::insert 会覆盖旧值 | 用 insert_or_assign 或显式赋值 |
multiset.erase(x) 只删一个 | 用 find 获得 iterator 后 erase(it) |
priority_queue.pop() 返回最大值 | 先 top,再 pop;默认不是小根堆 |
copy(..., empty_vector.begin()) | 先 resize 足够长度,或使用 back_inserter |
| 在范围 for 中随意 erase 当前元素 | 使用下述显式迭代器循环 |
| 认为所有容器 size() 都存在 | forward_list 没有 size |
a.size()-1 在空数组上为 -1 | size_type 通常无符号,先判空或采用安全循环 |
| accumulate 初始值 0 一定不溢出 | 累加 long long 时用 0LL |
| 输出 prefix 是 long long,partial_sum 就不会 int 溢出 | 内部按输入类型累加,调整输入或改循环 |
_Find_first、pb_ds、bits/stdc++.h 都是标准 STL | 属于实现扩展,依评测环境使用 |
15.3 边遍历边删除
std::vector<int> a = {1, 2, 3, 4};
for (auto it = a.begin(); it != a.end(); ) {
if (*it % 2 == 0) it = a.erase(it);
else ++it;
}
对 vector 大量逐个删除可能 O(n²),可优先使用 erase-remove 或 C++20 erase_if。树/哈希容器也可使用返回后继迭代器的 erase 循环;forward_list 需要记录前驱,并调用 erase_after。
十六、竞赛常用模板
下面前三个例子是完整 C++17 程序,可以独立编译运行。
16.1 排序、去重与离散化
离散化保留大小关系,不保留原值差。
#include <algorithm>
#include <cassert>
#include <iostream>
#include <vector>
int main() {
std::vector<int> a = {100, 20, 100, 50};
std::vector<int> xs = a;
std::sort(xs.begin(), xs.end());
xs.erase(std::unique(xs.begin(), xs.end()), xs.end());
std::vector<int> ranks;
for (int x : a) {
int id = static_cast<int>(
std::lower_bound(xs.begin(), xs.end(), x) - xs.begin());
ranks.push_back(id); // 从 0 开始
}
assert((ranks == std::vector<int>{2, 0, 2, 1}));
for (int id : ranks) std::cout << id << ' ';
std::cout << '\n';
}
复杂度 O(n log n),xs[id] 是对应原值;如果需要从 1 开始的编号,在使用 id 时加 1。
16.2 deque:滑动窗口最大值
队列存下标,队首始终对应当前窗口最大值。
#include <cassert>
#include <deque>
#include <iostream>
#include <vector>
int main() {
std::vector<int> a = {1, 3, -1, -3, 5, 3, 6, 7};
int k = 3;
assert(k >= 1 && k <= static_cast<int>(a.size()));
std::deque<int> q;
std::vector<int> result;
for (int i = 0; i < static_cast<int>(a.size()); ++i) {
while (!q.empty() && q.front() <= i - k) q.pop_front();
while (!q.empty() && a[q.back()] <= a[i]) q.pop_back();
q.push_back(i);
if (i >= k - 1) result.push_back(a[q.front()]);
}
assert((result == std::vector<int>{3, 3, 5, 5, 6, 7}));
for (int x : result) std::cout << x << ' ';
std::cout << '\n';
}
每个下标最多入队、出队一次,总时间 O(n),额外空间 O(k)。
16.3 priority_queue + pair:Dijkstra
仅用于非负边权,小根堆保存 (当前距离, 节点),跳过过期状态。
#include <cassert>
#include <functional>
#include <iostream>
#include <limits>
#include <queue>
#include <utility>
#include <vector>
int main() {
using ll = long long;
using State = std::pair<ll, int>;
int n = 4, source = 0;
std::vector<std::vector<std::pair<int, ll>>> graph(n);
graph[0] = {{1, 5}, {2, 1}};
graph[2] = {{1, 1}, {3, 10}};
graph[1] = {{3, 2}};
const ll INF = std::numeric_limits<ll>::max() / 4;
std::vector<ll> dist(n, INF);
std::priority_queue<State, std::vector<State>, std::greater<State>> pq;
dist[source] = 0;
pq.emplace(0, source);
while (!pq.empty()) {
auto [d, u] = pq.top();
pq.pop();
if (d != dist[u]) continue;
for (const auto& [v, w] : graph[u]) {
// 输入须保证 0 <= w,且最短路小于 INF
if (w >= INF || d > INF - w) continue;
ll nd = d + w;
if (nd < dist[v]) {
dist[v] = nd;
pq.emplace(nd, v);
}
}
}
assert((dist == std::vector<ll>{0, 2, 1, 4}));
for (ll d : dist) std::cout << d << ' ';
std::cout << '\n';
}
懒删除堆实现的保守时间上界 O(n + m log(m + 1)),其中 m 为边数;简单图中常写为 O((n + m) log n)。若存在负边权,不能直接使用此模板。
16.4 动态前驱与后继
std::set<int> s = {2, 5, 8};
int x = 5;
auto successor = s.upper_bound(x); // 严格大于 x 的最小值
if (successor != s.end()) {
int value = *successor;
(void)value;
}
auto it = s.lower_bound(x);
if (it != s.begin()) {
auto predecessor = std::prev(it); // 严格小于 x 的最大值
int value = *predecessor;
(void)value;
}
查询 O(log n)。prev(begin()) 非法;若 lower_bound 返回 end,非空集合上仍可 prev(end) 取得最大元素。
16.5 计数与最大/最小堆
std::unordered_map<int, int> cnt;
for (int x : v) ++cnt[x];
std::priority_queue<int> max_heap;
std::priority_queue<int, std::vector<int>, std::greater<int>> min_heap;
16.6 bitset:0/1 子集和
// 要统计的和必须落在 [0, MAX_SUM],物品值 x 为非负整数
constexpr int MAX_SUM = 100000;
std::bitset<MAX_SUM + 1> possible;
possible.set(0);
for (int x : v) possible |= (possible << x);
移位结果先形成,再并入旧状态,所以每个物品只使用一次。超出 bitset 上限的状态被丢弃;不能据此回答上限之外的可达性。
十七、复习清单
17.1 优先熟练
vector:[]、push_back、pop_back、size、resize、reserve、insert、erase。set/map:insert、find、count、erase、lower_bound、upper_bound、equal_range。map:[]、at、try_emplace、insert_or_assign的差异。unordered_*:平均/最坏复杂度、reserve、重哈希后的迭代器失效。stack/queue/priority_queue:push、pop、top/front/back、判空。sort、stable_sort、nth_element、二分四函数。find/count、reverse/rotate、remove/unique、min/max、next_permutation。accumulate、partial_sum、iota、gcd/lcm。string:find、substr、insert、erase、replace、npos。bitset:set/reset/flip、count、移位与按位运算。
17.2 六条最关键规则
- 区间一般是
[first, last),end 不能解引用。 - 插入删除后检查迭代器失效,不要继续使用失效位置。
- 二分与集合运算要满足分区/排序前提。
- 排序比较器必须是严格弱序,不用
<=。 remove/unique/pop_heap不会缩短容器。- 复杂度要区分最坏、平均、均摊,也要计算元素复制与比较的实际成本。
参考资料与进一步查阅
- OI Wiki:STL 容器简介
- OI Wiki:序列式容器
- OI Wiki:关联式容器
- OI Wiki:无序关联式容器
- OI Wiki:容器适配器
- OI Wiki:迭代器
- OI Wiki:STL 算法
- OI Wiki:string
- OI Wiki:bitset
- OI Wiki:pair
- C++17 草案 N4659:algorithm 函数声明总表
- C++17 草案 N4659:numeric 算法
- C++23 草案 N4950
版本标注以标准引入时间为准,不等于任意编译器都已实现。若题目明确限制 C++14,需要避开本文标为 C++17 及以上的接口与结构化绑定。