Ayin 的头像Ayin
skill

C++ STL 总结:容器、函数、算法与竞赛用法

ai大人总结

57 分钟阅读

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 基本规则

CODE
#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
BFSqueue
括号匹配、单调栈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(...)删除元素序列容器一般按迭代器;关联容器还可按键

常用初始化:

CODE
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:动态数组

CODE
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),通常保留容量
CODE
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:固定长度数组

CODE
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 的常数复杂度。

CODE
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

CODE
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 返回 iteratorO(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)返回上述两个边界组成的 pairO(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 也按比较器定义边界,表中的 >=、> 仅适用于默认升序。

CODE
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 个匹配项。

CODE
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()返回键相等判定对象
CODE
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:栈

CODE
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:队列

CODE
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:优先队列

CODE
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()删除堆顶,返回 voidO(log n)
size()、empty()大小、判空O(1)
swap(other)交换堆通常 O(1),取决于底层与比较器
由现有区间批量构造建堆O(n) 次比较,另计数据复制/构造

不能直接删除任意位置,也没有标准的 decrease_key。Dijkstra 常用“插入新状态,弹出时跳过旧状态”。默认比较器 less<T> 使最大的元素优先,greater<T> 使最小的元素优先。

三个适配器的 pop() 都不返回元素,取值要先访问再删除:

CODE
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。

CODE
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 等算法。

CODE
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
CODE
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。

CODE
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元素数与元素类型;类型特征
CODE
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,可接收解包赋值
ignoretie 解包时忽略某项
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查询元素个数与类型
CODE
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 向前到达。

CODE
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_ofall_of(first, last, pred)全部满足,boolO(n)
any_ofany_of(first, last, pred)至少一个满足,boolO(n)
none_ofnone_of(first, last, pred)全部不满足,boolO(n)
for_eachfor_each(first, last, f)对每项调用 f,普通重载返回函数对象O(n)
for_each_nfor_each_n(first, n, f)C++17,处理前 n 项,返回后继迭代器O(n)
countcount(first, last, x)等于 x 的数量O(n)
count_ifcount_if(first, last, pred)满足条件的数量O(n)

空区间上 all_of 和 none_of 返回 true,any_of 返回 false。

11.2 查找、匹配与比较

函数用途/返回值复杂度或条件
find(first, last, x)第一个等于 x 的位置,或 lastO(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])首个不匹配位置的迭代器 pairO(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,逻辑新终点之后的元素不要作为有效结果使用。

CODE
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)原地反转,返回 voidO(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)随机重排,返回 voidO(n),随机访问
sample(first, last, out, k, rng)C++17,随机抽至多 k 项,返回输出尾后位置O(n)
CODE
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])全部排序,返回 voidO(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])是否已经有序,boolO(n)
is_sorted_until(first, last[, comp])首个使区间不再有序的位置,完全有序返回 lastO(n)

前三种原地排序与 nth_element 要求随机访问迭代器。partial_sort_copy 的输出要求随机访问,输入可以更弱。nth_element 在有重复值时允许左右出现与 nth 等价的元素,不能理解成“左侧全严格小于、右侧全严格大于”。

CODE
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)第一个 >= xiterator
upper_bound(first, last, x)第一个 > xiterator
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)。

CODE
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 写法中也会执行一次循环体。

CODE
std::sort(v.begin(), v.end());
do {
    // 处理当前排列
} while (std::next_permutation(v.begin(), v.end()));

排序、二分、分区、集合、堆、最值与排列的精确保证参照 C++17 排序及相关算法。

十二、numeric 数值算法

头文件:<numeric>。

函数典型用法/作用说明
accumulateaccumulate(first,last,init[,op]),依次累积顺序明确,累加类型由 init 决定
inner_productinner_product(f1,l1,f2,init[,op1,op2])默认点积;第二段须足够长
partial_sumpartial_sum(first,last,out[,op])默认前缀和,无初始值参数
adjacent_differenceadjacent_difference(first,last,out[,op])首项复制,之后默认当前项减前项
iotaiota(first,last,start)C++11,依次填入 start、start+1…
reducereduce(first,last[,init[,op]])C++17,允许重组运算顺序
transform_reduce变换后归约;单区间或双区间C++17,双区间默认计算内积式归约
inclusive_scan包含当前元素的扫描C++17,例如 1,2,3 -> 1,3,6
exclusive_scan不包含当前元素的扫描,需 initC++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 的复杂度标准未规定,常见实现基于欧几里得算法。

CODE
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_searcherC++17,普通搜索器
boyer_moore_searcher、boyer_moore_horspool_searcherC++17,字符串/序列搜索器,要求相应迭代器与匹配约束

空的 std::function 被调用会抛 bad_function_call。旧 unary_function、binary_function、bind1st、bind2nd 等已在 C++17 移除;旧 not1/not2、ptr_fun 等也不适合作为新代码模板。工具接口来源:C++17 函数对象。

13.2 lambda 与排序比较器

CODE
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a > b;  // 降序
});

排序比较器必须形成严格弱序:自身不小于自身,比较规则一致且具有必要的传递性。例如按分数降序、同分编号升序:

CODE
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_nC++17,在未初始化存储上移动构造
uninitialized_default_construct、uninitialized_default_construct_nC++17,默认初始化构造
uninitialized_value_construct、uninitialized_value_construct_nC++17,值初始化构造
destroy_at、destroy、destroy_nC++17,销毁单对象/区间/n 个对象,不释放存储
construct_atC++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::seqC++17顺序执行策略
std::execution::parC++17允许并行
std::execution::par_unseqC++17允许并行且无序执行
std::execution::unseqC++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 提供带范围概念约束的算法,许多函数可直接接收容器:

CODE
// 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_multisetbegin/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_compextract() 移出整个底层容器,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 在空数组上为 -1size_type 通常无符号,先判空或采用安全循环
accumulate 初始值 0 一定不溢出累加 long long 时用 0LL
输出 prefix 是 long long,partial_sum 就不会 int 溢出内部按输入类型累加,调整输入或改循环
_Find_first、pb_ds、bits/stdc++.h 都是标准 STL属于实现扩展,依评测环境使用

15.3 边遍历边删除

CODE
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 排序、去重与离散化

离散化保留大小关系,不保留原值差。

CODE
#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:滑动窗口最大值

队列存下标,队首始终对应当前窗口最大值。

CODE
#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

仅用于非负边权,小根堆保存 (当前距离, 节点),跳过过期状态。

CODE
#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 动态前驱与后继

CODE
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 计数与最大/最小堆

CODE
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 子集和

CODE
// 要统计的和必须落在 [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 六条最关键规则

  1. 区间一般是 [first, last),end 不能解引用。
  2. 插入删除后检查迭代器失效,不要继续使用失效位置。
  3. 二分与集合运算要满足分区/排序前提。
  4. 排序比较器必须是严格弱序,不用 <=。
  5. remove/unique/pop_heap 不会缩短容器。
  6. 复杂度要区分最坏、平均、均摊,也要计算元素复制与比较的实际成本。

参考资料与进一步查阅

版本标注以标准引入时间为准,不等于任意编译器都已实现。若题目明确限制 C++14,需要避开本文标为 C++17 及以上的接口与结构化绑定。