标准模板库(STL)
STL 即标准模板库(Standard Template Library),是 C++ 标准库的一部分,里面包含了一些模板化的通用的数据结构和算法。由于其模板化的特点,它能够兼容自定义的数据类型,避免大量的造轮子工作。NOI 和 ICPC 赛事都支持 STL 库的使用,因此合理利用 STL 可以避免编写无用算法,并且充分利用编译器对模板库优化提高效率。
STL容器
迭代器
在 STL 中,迭代器(Iterator)用来访问和检查 STL 容器中元素的对象,它的行为模式和指针类似,但是它封装了一些有效性检查,并且提供了统一的访问格式。
迭代器听起来比较晦涩,其实迭代器本身可以看作一个数据指针。迭代器主要支持两个运算符:自增 (++
) 和解引用(单目 *
运算符),其中自增用来移动迭代器,解引用可以获取或修改它指向的元素。
1 | vector<int> data(10); |
STL 容器 一般支持从一端或两端开始的访问,以及对 const 修饰符 的支持。例如容器的 begin()
函数可以获得指向容器第一个元素的迭代器,rbegin()
函数可以获得指向容器最后一个元素的反向迭代器,cbegin()
函数可以获得指向容器第一个元素的 const 迭代器,end()
函数可以获得指向容器尾端(「尾端」并不是最后一个元素,可以看作是最后一个元素的后继;「尾端」的前驱是容器里的最后一个元素,其本身不指向任何一个元素)的迭代器。
序列式容器
- 向量(
vector
) 后端可高效增加元素的顺序表。 - 双端队列(
deque
) 双端都可高效增加元素的顺序表。 - 列表(
list
) 可以沿双向遍历的链表。 - 单向列表(
forward_list
) 只能沿一个方向遍历的链表。
vector
std::vector
是 STL 提供的 内存连续的、可变长度 的数组(亦称列表)数据结构。能够提供线性复杂度的插入和删除,以及常数复杂度的随机访问。
vector定义
1 |
|
1 | // 1. 创建空vector; 常数复杂度 |
vector容器内元素的访问
1 | //1.通过下标访问 |
1 |
|
vector常用函数
push_back()
在末尾插入一个元素,均摊复杂度为 常数,最坏为线性复杂度。pop_back()
删除末尾元素,常数复杂度。front()
返回vector的第一个元素,等价于*a.begin() 和 a[0]。back()
返回vector的最后一个元素,等价于*==a.end() 和 a[a.size() – 1]。size()
返回容器长度(元素数量),即std::distance(v.begin(), v.end())
。v.data()
返回指向数组第一个元素的指针。insert()
支持在某个迭代器位置插入元素、可以插入多个。insert(it,x)
在it处插入x。clear()
清除所有元素。erase()
删除某个迭代器或者区间的元素,返回最后被删除的迭代器。
erase(it)删除迭代器为it处的元素,erase(first,last)即删除[first,last)内所有元素。
deque
std::deque
(双端队列)支持在头部和尾部进行高效插入删除的序列容器,与vector相比:
- 头尾插入/删除时间复杂度为O(1)
- 支持随机访问(通过下标)
- 存储空间分块管理,迭代器比vector复杂
- 中间插入删除效率较低
deque定义
1 |
|
deque容器内元素的访问
方式 | 示例 | 说明 |
---|---|---|
下标访问 | dq[0] |
无越界检查 |
at() 方法 |
dq.at(0) |
有越界检查,越界抛出异常 |
首元素 | dq.front() |
等价于 dq[0] |
末尾元素 | dq.back() |
等价于 dq[dq.size()-1] |
deque常用函数
函数 | 功能说明 |
---|---|
push_front(x) |
在头部插入元素x |
pop_front() |
删除头部元素 |
push_back(x) |
在尾部插入元素x |
pop_back() |
删除尾部元素 |
insert(pos, x) |
在迭代器pos前插入元素x |
erase(pos) |
删除迭代器pos指向的元素 |
resize(n) |
调整容器大小为n |
shrink_to_fit() |
请求移除未使用的容量(C++11) |
特殊说明
1 | // 头尾操作示例 |
综合对比
特性\容器 | vector | deque |
---|---|---|
头插效率 | O(n) | O(1) |
尾插效率 | 均摊O(1) | O(1) |
中间插入 | O(n) | O(n) |
内存布局 | 单块连续内存 | 多块连续内存 |
迭代器失效 | 容易失效 | 部分操作不失效 |
缓存友好性 | 高 | 较低 |
关联式容器
- 集合(
set
) 用以有序地存储 互异 元素的容器,其实现是由节点组成的红黑树。 - 多重集合(
multiset
) 用以有序地存储元素的容器。允许存在相等的元素。
头文件set,主要包括set和multiset两个容器,分别是“有序集合”和“有序多重集合”,即前者的元素不能重复,而后者可以包含若干个相等的元素。set和multiset的内部实现是一棵红黑树,它们支持的函数基本相同。
set
set
是关联容器,含有键值类型对象的已排序集,搜索、移除和插入拥有对数复杂度。set
内部通常采用 红黑树 实现。平衡二叉树 的特性使得 set
非常适合处理需要同时兼顾查找、插入与删除的情况。
set定义
1 |
|
set容器内元素的访问
set只能通过迭代器访问
set和multiset的迭代器称为“双向访问迭代器”,不支持“随机访问”,支持星号(*)解除引用,仅支持”++”和–“两个与算术相关的操作。
若把it++,则it会指向“下一个”元素。这里的“下一个”元素是指在元素从小到大排序的结果中,排在it下一名的元素。同理,若把it–,则it将会指向排在“上一个”的元素。
1 | set<typename>::iterator it; //这样就得到了迭代器,可以通过*it来访问set |
1 |
|
set常用函数
insert(x)
当容器中没有等价元素的时候,将元素 x 插入到set
中。find(x)
在set
内存在键为 x 的元素时会返回该元素的迭代器,否则返回end()
。erase(x)
删除值为 x 的 所有 元素,返回删除元素的个数。
erase(pos)
删除迭代器为 pos 的元素,要求迭代器必须合法。
erase(first,last)
删除迭代器在 [first,last) 范围内的所有元素。
clear()
清空set
。count(x)
返回set
内键为 x 的元素数量。lower_bound(x)
返回指向首个不小于给定键的元素的迭代器。如果不存在这样的元素,返回end()
。upper_bound(x)
返回指向首个大于给定键的元素的迭代器。如果不存在这样的元素,返回end()
。empty()
返回容器是否为空。size()
返回容器内元素个数。
1 | // 现存可用的元素 |
- 映射(
map
) 由 {键,值} 对组成的集合,以某种比较键大小关系的谓词进行排列。 - 多重映射(
multimap
) 由 {键,值} 对组成的多重集合,亦即允许键有相等情况的映射。
map
map
是有序键值对容器,它的元素的键是唯一的。搜索、移除和插入操作拥有对数复杂度。map
通常实现为 红黑树。
map定义
1 | map<key,value> mp; |
map容器内元素的访问
1 | //1.通过下标访问 |
map常用函数
通过向
map
中插入一个类型为pair<Key, T>
的值可以达到插入元素的目的,例如mp.insert(pair<string,int>("Alan",100));
;find(x)
: 若容器内存在键为 x 的元素,会返回该元素的迭代器;否则返回end()
。erase(key)
函数会删除键为key
的 所有 元素。返回值为删除元素的数量。
erase(pos)
: 删除迭代器为 pos 的元素,要求迭代器必须合法。
erase(first,last)
: 删除迭代器在 [first,last) 范围内的所有元素。
clear()
函数会清空整个容器。count(x)
: 返回容器内键为 x 的元素数量。复杂度为(关于容器大小对数复杂度,加上匹配个数)。
lower_bound(x)
: 返回指向首个不小于给定键的元素的迭代器。upper_bound(x)
: 返回指向首个大于给定键的元素的迭代器。若容器内所有元素均小于或等于给定键,返回end()
。empty()
: 返回容器是否为空。size()
: 返回容器内元素个数。
无序(关联式)容器
无序(多重)集合(
unordered_set
/unordered_multiset
)C++11,与set
/multiset
的区别在于元素无序,只关心「元素是否存在」,使用哈希实现。无序(多重)映射(
unordered_map
/unordered_multimap
)C++11,与map
/multimap
的区别在于键 (key) 无序,只关心 “键与值的对应关系”,使用哈希实现。
容器适配器
容器适配器其实并不是容器。它们不具有容器的某些特点(如:有迭代器、有 clear()
函数……)。
「适配器是使一种事物的行为类似于另外一种事物行为的一种机制」,适配器对容器进行包装,使其表现出另外一种行为。
- 栈(
stack
) 后进先出 (LIFO) 的容器,默认是对双端队列(deque
)的包装。 - 队列(
queue
) 先进先出 (FIFO) 的容器,默认是对双端队列(deque
)的包装。 - 优先队列(
priority_queue
) 元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列,默认是对向量(vector
)的包装。
stack(栈)
STL 栈(std::stack
) 是一种后进先出 (Last In, First Out) 的容器适配器,仅支持查询或删除最后一个加入的元素(栈顶元素),不支持随机访问,且为了保证数据的严格有序性,不支持迭代器。用来模拟实现一些递归。
stack定义
1 |
|
stack容器内元素的访问
由于栈本身就是一种后进先出的数据结构,在STL的stack中只能通过top()来访问栈顶元素。
stack常用函数
top()
访问栈顶元素(如果栈为空,此处会出错)push(x)
向栈中插入元素 xpop()
删除栈顶元素size()
查询容器中的元素数量empty()
询问容器是否为空
queue(队列)
STL 队列(std::queue
) 是一种先进先出 (First In, First Out) 的容器适配器,仅支持查询或删除第一个加入的元素(队首元素),不支持随机访问,且为了保证数据的严格有序性,不支持迭代器。
queue定义
1 |
|
queue容器内元素的访问
由于队列本身就是一种先进先出的数据结构,在STL的queue中只能通过front()来访问队首元素,通过back()访问队尾元素。
queue常用函数
front()
访问队首元素(如果队列为空,此处会出错)back()
访问队尾元素push(x)
向队列中插入元素 xpop()
删除队首元素size()
查询容器中的元素数量empty()
询问容器是否为空
priority_queue(优先队列)
优先队列 std::priority_queue
是一种 堆,一般为 二叉堆。队首元素一定是当前队列中优先级最高的那一个。
priority_queue定义
1 |
|
priority_queue容器内元素的访问
和队列不一样的是,优先队列没有front()和back(),只能通过top()访问队首元素(堆顶元素)。
priority_queue常用函数
top()
访问堆顶元素(此时优先队列不能为空)pop()
删除堆顶元素(此时优先队列不能为空)push(x)
插入元素,并对底层容器排序size()
查询容器中的元素数量empty()
询问容器是否为空
STL算法
STL 提供了大约 100 个实现算法的模版函数,基本都包含在 <algorithm>
之中,还有一部分包含在 <numeric>
和 <functional>
。
algorithm
<algorithm>
头文件提供了大量通用算法,适用于多种容器。所有算法均通过迭代器操作,不对容器进行直接修改(除非明确说明)。
常用算法列表
算法 | 功能说明 |
---|---|
sort(beg, end, cmp) |
对区间[beg,end)排序,cmp为可选比较函数(默认升序) |
reverse(beg, end) |
反转指定区间的元素顺序 |
max(a, b) / min(a, b) |
返回两个值的较大/较小值(C++11支持初始化列表:max({1,2,3}) ) |
swap(a, b) |
交换两个变量的值 |
find(beg, end, val) |
在区间内查找值,返回首个匹配的迭代器,未找到返回end |
count(beg, end, val) |
统计区间内指定值出现的次数 |
fill(beg, end, val) |
用指定值填充区间 |
copy(src_beg, src_end, dest_beg) |
复制源区间到目标位置 |
unique(beg, end) |
去除相邻重复元素,返回去重后的新结尾迭代器(通常先排序后使用) |
lower_bound(beg, end, val) |
在有序区间中找第一个不小于val的元素位置 |
upper_bound(beg, end, val) |
在有序区间中找第一个大于val的元素位置 |
binary_search(beg, end, val) |
检查有序区间中是否存在指定值 |
典型使用示例
1 | // 自定义排序 |
pair
std::pair
是一个模板类,用于将两个值组合成一个单元。常用于需要返回两个值的场景,或作为map
容器的元素类型。通过灵活使用 pair
,可以轻松应对 需要将关联数据捆绑存储、处理 的场景。
pair定义
1 |
|
pair元素访问
成员变量 | 说明 |
---|---|
first |
访问第一个元素 |
second |
访问第二个元素 |
常用操作
操作 | 说明 |
---|---|
比较运算符(==, !=, <等) | 按字典序比较:先比较first,相等时再比较second |
swap |
使用 swap 函数交换 pair 的值。 |
赋值 | 将 pair 的值赋给另一个类型一致的 pair 。p0 = p1; |
string
std::string
是 C++ 标准库提供的字符串类,用于存储和操作字符序列。它在内存中以连续块存储字符,支持高效的随机访问和动态调整大小。
string定义
1 |
|
string容器内元素的访问
1 | //1. 通过下标访问 |
string常用函数
函数 | 功能说明 |
---|---|
append(str) |
在字符串末尾追加内容 |
push_back(c) |
追加单个字符 |
pop_back() |
删除最后一个字符(C++11) |
insert(pos, str) |
在指定位置插入字符串 |
erase(pos, len) |
从pos开始删除len个字符 |
replace(pos, len, str) |
替换从pos开始的len个字符为str |
find(str, pos) |
从pos开始查找子串,返回首次出现的位置,未找到返回string::npos |
compare(str) |
比较字符串(返回0表示相等,负数表示小于,正数表示大于) |
c_str() |
返回C风格字符串(const char*) |
clear() |
清空字符串内容 |
resize(n, c) |
调整字符串长度为n,多出部分用字符c填充 |
capacity() |
返回当前分配的存储容量 |
reserve(n) |
预分配至少能存储n个字符的内存空间 |