Skip to main content

STL

  • STL是C++标准库;
  • 除了operator new和delete之外,所有库实体都定义在命名空间std或者嵌套在命名空间std内的命名空间中。

STL常用库

分类头文件说明常见内容
通用多用途头文件<cstdlib>通用头文件size_tabortmallocatoi
语言支持库<climits>整数类型限制INT_MIN,INT_MAX
内存管理库<memory>内存管理工具智能指针(shared_ptr)及相关操作函数
容器库<vector> <list> <set> <map> <unordered_map> <uorrdered_set> <stack> <forward_list> <queue>常用容器见下方详细说明
算法库<algorithm>范围操作算法见下方详细说明
字符串库<string> <cstring>字符串模板及字符串相关算法见下方详细说明
迭代器库<iterator>遍历容器中的元素允许以统一的方式访问容器中的元素,而不用关心容器的内部实现细节。STL 提供了多种类型的迭代器,包括随机访问迭代器、双向迭代器、前向迭代器和输入输出迭代器等

容器

  • 容器是可以承载元素的器件;

vector

  • 可变长度的连续数组。
  • 连续存储、尾部高效,自动扩缩容
  • 使用场景:(1)动态增长和缩小的数组;(2)可以根据下标访问;(3)频繁在末尾添加/删除元素;
  • 不适用场景:频繁在中间增删元素(每次都涉及元素移动);
#include <iostream>
#include <vector>
int main() {
// vector常见方法
// 1. 创建vector
std::vector<int> v;
std::vector<int> v2(10, 1);
// 2. 添加元素
v.push_back(1);
v.emplace_back(2);
// 获取大小
std::cout << v.size() << std::endl;
// 获取容量
std::cout << v.capacity() << std::endl;
// 3. 下标访问元素
std::cout << v[0] << std::endl;
// 通过迭代器访问第一个元素
std::cout << *v.begin() << std::endl;
// 迭代器遍历
for (auto it = v.begin(); it != v.end(); it++) {
std::cout << *it << std::endl;
}
// 访问最后一个元素
std::cout << v.back() << std::endl;
// 4. 删除末尾元素
v.pop_back();
// 删除指定位置元素
v.erase(v.begin() + 1);
// 删除指定范围元素
v.erase(v.begin() + 1, v.end() - 1);
// 删除指定值元素
v.erase(std::remove(v.begin(), v.end(), 1), v.end());
// 5. 获取元素个数
std::cout << v.size() << std::endl;
// 6. 清空vector
v.clear();
std::cout << v.size() << std::endl;
// 交换两个vector
v.swap(v2);
return 0;

}
功能分类方法名称功能说明时间复杂度
容量与大小size()返回容器中元素的个数O(1)
capacity()返回容器在不扩容的情况下总共能容纳的元素量O(1)
empty()如果容器为空返回 trueO(1)
reserve(n)预留空间。提前申请内存,避免多次搬家(扩容)O(n)
resize(n)改变容器有效大小。可能会创建新元素或销毁旧元素O(n)
访问元素operator[]下标访问(如 v[0])。速度最快,但不检查越界O(1)
at(pos)强力访问。如果越界会抛出异常,更安全O(1)
front()返回第一个元素的引用O(1)
back()返回最后一个元素的引用O(1)
修改容器push_back(val)在容器末尾添加一个元素均摊 O(1)
emplace_back(args)原地构造。比 push_back 更高效(直接在末尾内存构造对象,不需要多进行一次临时对象拷贝或移动)均摊 O(1)
pop_back()删除末尾的元素O(1)
insert(pos, val)在迭代器指定位置插入元素O(n)
erase(pos)删除迭代器指向的一个或一段元素O(n)
clear()清空所有元素,但通常不会减小 capacityO(n)
迭代器begin() / end()返回指向第一个元素 / 最后一个元素之后的迭代器O(1)
  • reserve vs resize
    • -如果知道要存 100 个数,先 reserve(100)。这样 vector 就不会在增加元素时频繁地“申请内存 $\rightarrow$ 拷贝旧数据 $\rightarrow$ 释放旧内存”,性能提升显著。
    • resize 则是真的改变了 size(),你会发现里面多了很多默认初始化的元素。
  • 迭代器失效
    • 失效场景:引发扩容、 元素挪动位置
    • 失效原因:(1)扩容时需要开辟新的内存并把旧数据全部搬到新内存,此时所有旧的迭代器都会失效;(2)如果往中间insert元素,插入点之后的所有迭代器都会因为元素后移而失效;
// 引发扩容导致失效
std::vector<int> v = {1, 2, 3};
auto it = v.begin();
v.push_back(4); // 如果触发扩容,it 瞬间变成野指针
std::cout << *it; // ❌ 这里的行为不可预测,可能直接崩溃

// 删除元素导致的失效
for (auto it = v.begin(); it != v.end(); ++it) {
if (*it % 2 == 0) {
v.erase(it); // ❌ 后面元素前移,it 指向的内容变了,且下次循环 ++it 会跳过一个元素
}
}
操作失效范围根本原因安全建议
push_back可能导致全部失效内存重新分配(扩容)操作后不再使用旧迭代器,重新从begin位置开始遍历
insert插入点之后的迭代器失效元素向后挪动使用 it = v.insert(pos, val) 更新
erase删除点之后的迭代器失效元素向前挪动使用 it = v.erase(pos) 更新
reserve可能导致全部失效内存重新分配尽量在程序初始化阶段执行
resize可能导致全部失效内存重新分配修改大小后重新获取迭代器
  • 总结:在使用vector的迭代器时,一旦进行了可能改变元素位置的操作,就默认迭代器已经过期;

queue

  • (1)先进先出;(2)只能通过队头/队尾访问;
  • queue是一个容器适配器,意思是它自己不负责维护内存,而是像一个壳子套在其它基础容器外;queue默认底层是用 std::deque 实现的,但你也可以换成 std::list
  • 不支持迭代器
  • 常用方法
功能分类方法名称说明时间复杂度
基础信息empty()检查队列是否为空,返回 boolO(1)
size()返回队列中当前元素的个数O(1)
元素访问front()核心方法:返回队头(最早进去的)元素O(1)
back()返回队尾(最后进去的)元素O(1)
修改操作push(val)在队尾插入新元素O(1)
emplace(args)在队尾原位构造元素,效率比 pushO(1)
pop()核心方法:移除队头元素(注意:不返回它)O(1)
  • 使用场景及注意事项
    • 使用时要先判队列是否为空;
    • BFS、层序遍历二叉树是queue最经典的使用场景;
    • 任务调度时可以使用queue来按照接收顺序处理任务;
    • 当消费不及时时可以使用queue来实现缓冲池;

priority_queue

  • 优先队列
  • priority_queue是一个容器适配器,底层默认是vector;
  • 受限访问:只能访问到优先级最高的那个元素(堆顶),无法遍历整个队列;
  • 不支持迭代器
  • 适合处理加权任务
  • 常用方法:
方法名称功能说明时间复杂度
push(val)将元素插入队列并重新排序O(log n)
emplace(args)在队列中原位构造元素,效率更高O(log n)
pop()删除优先级最高(堆顶)的元素O(log n)
top()返回优先级最高(堆顶)元素的引用O(1)
empty()检查队列是否为空O(1)
size()返回队列中元素的个数O(1)
  • 大顶堆与小顶堆的定义方式
堆类型声明方式优先级规则
大顶堆(默认)priority_queue<int> pq;数值越大,优先级越高
小顶堆需要传入三个参数:数据类型、底层容器、比较函数。
priority_queue<int, vector<int>, greater<int>> pq;
数值越小,优先级越高
#include <iostream>
#include <queue>
#include <vector>

int main() {
// 定义一个小顶堆(数值越小越优先)
std::priority_queue<int, std::vector<int>, std::greater<int>> pq;

pq.push(30);
pq.push(10);
pq.push(50);
pq.push(20);

// 此时 10 是最小的,排在堆顶
std::cout << "当前队头元素: " << pq.top() << std::endl; // 输出 10

// 依次弹出,会发现输出是升序的
while (!pq.empty()) {
std::cout << pq.top() << " ";
pq.pop();
}
// 输出: 10 20 30 50

return 0;
}
  • 常用场景:任务调度、Dijkstra 算法;
  • 自定义排序算法
auto cmp = [](int a,int b){return a>b;};// 小的优先
// 注意:这里需要 decltype(cmp) 来获取类型,并在构造函数中传入 cmp
std::priority_queue<int,std::vector<int>,decltype(cmp)> pq(cmp);

“左真右在后”原则:在 STL 的比较逻辑中,如果比较函数返回 true,意味着第一个参数的优先级较低,会被排在后面。这就是为什么实现“小顶堆”时要用 > 号的原因。

map

  • 有序性:它内部会自动根据“键”的大小进行排序(默认是升序)。你遍历 map 时,结果永远是有序的。
  • 唯一性:每个键只出现一次,不能重复;
  • 低层实现:红黑树,它的查找、插入、删除操作的时间复杂度都是稳定的 O(log n)。
  • 常用方法:
功能分类方法名称说明时间复杂度
基础信息size()返回键值对的数量O(1)
empty()检查是否为空O(1)
修改操作insert({k, v})插入一个键值对(如果键已存在则失败)O(log n)
operator[]核心:访问/修改。如果键不存在,会自动创建一个O(log n)
erase(key)根据键删除元素O(log n)
clear()清空所有元素O(n)
查找操作find(key)查找键,返回指向该元素的迭代器;若没找到返回 end()O(log n)
count(key)返回键出现的次数(对于 map 只有 0 或 1)O(log n)
at(key)访问键对应的值,若不存在则抛出异常O(log n)
边界查找lower_bound(k)返回指向第一个大于或等于 k 的元素的迭代器O(log n)
upper_bound(k)返回指向第一个大于 k 的元素的迭代器O(log n)
  • std:map vs std::unordered_map:
特性std::mapstd::unordered_map
底层实现红黑树 (有序)哈希表 (无序)
查找复杂度O(log n)平均 O(1),最差 O(n)
内存占用较低较高(需要维护哈希桶)
适用场景需要元素有序、需要范围查找追求极致的查找速度、不需要排序

set

  • 唯一性:集合中不允许出现重复元素;
  • 有序性:所有集合中的元素按照大小排列(默认是升序);
  • 低层实现:红黑树;
  • 使用场景:有序去重
  • 常用方法:
功能分类方法名称说明时间复杂度
基础信息size()返回集合中元素的个数O(1)
empty()检查是否为空O(1)
修改操作insert(val)插入元素。若已存在则插入失败O(log n)
erase(val/it)根据值或迭代器删除元素O(log n)
clear()清空集合O(n)
查找操作find(val)查找元素,返回迭代器。若没找到返回 end()O(log n)
count(val)返回元素出现次数(由于唯一性,结果只能是 0 或 1)O(log n)
边界查找lower_bound(v)返回第一个 大于或等于 v 的元素的迭代器O(log n)
upper_bound(v)返回第一个 大于 v 的元素的迭代器O(log n)

unordered_map

  • 无序
  • 快速查找;
  • 低层实现:哈希表;
  • 唯一性:每个键只存在一次;
  • 常用方法:
功能分类方法名称说明时间复杂度
基础信息size()返回键值对的数量O(1)
empty()检查是否为空O(1)
访问与修改operator[]访问/修改。如果键不存在,会自动创建一个如果key已经存在则修改其值平均 O(1)
at(key)访问。如果键不存在,抛出 std::out_of_range平均 O(1)
insert({k, v})插入键值对,返回键值对pair<iterator, bool>,当key已经存在时,拒绝插入,保留旧值平均 O(1)
emplace(k, v)推荐:在容器中直接构造,性能更好;返回键值对pair<iterator, bool>,当key已经存在时,拒绝插入,保留旧值平均 O(1)
查找与删除find(key)返回迭代器,若没找到返回 end()平均 O(1)
count(key)返回键出现的次数(0 或 1)平均 O(1)
erase(key)根据键删除元素平均 O(1)
哈希特有bucket_count()返回底层哈希桶的数量O(1)
load_factor()返回负载因子(元素数/桶数),用于衡量哈希冲突概率O(1)
  • 哈希冲突:两个不同的key经过哈希计算后指向了同一个下标;stl一般通过拉链法解决哈希冲突;
  • 重哈希:当负载因子达到容器的最大拥挤限度时(一般是1),就会开始重哈希;

$$\text{Load Factor负载因子} (\alpha) = \frac{\text{Current Elements Count(元素数量)}}{\text{Current Buckets Count(桶数量)}}}$$

  • 重哈希的步骤:(1)申请新内存:通常将桶的数量翻倍;(2)重新分配:遍历旧表里的每一个元素,根据新的桶数量重新计算哈希值。(3)搬迁:把元素放进新地址,并释放旧内存。
  • unordered_map 的 Key 支持哪些类型
    • 要成为unordered_map的key要满足以下条件:(1)可哈希;(2)可比较;
    • 支持的常见key:
类型分类是否默认支持备注
基础类型支持int, float, double, bool, char
指针类型支持原生指针(如 int*, void*)和智能指针均支持
标准库字符串支持std::string, std::wstring
容器类型不支持std::vector, std::pair, std::tuple 默认没有哈希实现
自定义结构体/类不支持需要手动提供 std::hash 特化和 operator==

stack

  • 先进后出;
  • 受限访问:只能看top的元素;
  • 底层实现:stack也是个容器适配器,其底层默认基于std::deque实现,但也可以指定为vector或list;
  • 不支持迭代器
  • 常用方法:
功能分类方法名称说明时间复杂度
基础信息empty()检查栈是否为空O(1)
size()返回栈中元素的个数O(1)
访问元素top()核心:返回栈顶元素的引用(不删除)O(1)
修改容器push(val)在栈顶压入一个元素O(1)
emplace(args)在栈顶原位构造元素,效率更高O(1)
pop()核心:移除栈顶元素(注意:不返回该元素)O(1)

容器总结

容器类型容器名称查找 (查找值)随机访问 (下标)插入/删除 (两端)插入/删除 (中间)底层结构
序列容器vectorO(n)O(1)尾部 O(1)O(n)动态连续数组
dequeO(n)O(1)两端 O(1)O(n)分段连续数组
listO(n)O(n)O(1)O(1)双向链表
关联容器map/setO(log n)不支持O(log n)O(log n)红黑树
unordered_map/setO(1)不支持O(1)O(1)哈希表
容器适配器stack不支持不支持O(1) (栈顶)不支持默认 deque
queue不支持不支持O(1) (头尾)不支持默认 deque
priority_queue不支持不支持O(log n)不支持二叉堆

算法

  • <algorithm> 头文件

常用算法

分类算法名称返回类型功能说明支持的容器时间复杂度
查找与计数std::findIterator查找第一个匹配元素所有容器O(n)
std::countdifference_type统计匹配值的个数所有容器O(n)
std::any_ofbool是否有任一元素满足条件所有容器O(n)
排序与搜索std::sortvoid原地排序仅限随机访问: vector, dequeO(n log n)
std::lower_boundIterator返回第一个 $\ge$ 给定值的位置有序序列: vector, deque, set*, map*O(log n)
std::upper_boundIterator返回第一个 $>$ 给定值的位置有序序列: vector, deque, set*, map*O(log n)
std::binary_searchbool告知目标是否存在有序序列: vector, deque, set, mapO(log n)
修改与转换std::copyOutputIterator复制区间到目标位置所有序列容器O(n)
std::transformOutputIterator对区间元素做变换所有序列容器O(n)
std::replacevoid原地替换元素值所有序列容器O(n)
生成与删除std::fillvoid用指定值填充区间所有序列容器O(n)
std::uniqueIterator返回去重后的逻辑末尾序列容器 (通常先 sort)O(n)
std::reversevoid原地反转区间双向访问容器: vector, list, dequeO(n)

sort

  • sort底层使用内省排序,可以根据数据量和分布情况在快排、堆排和插入排序中切换;
  • sort不是稳定排序(相同元素的相对位置不变),如果想要稳定排序可以使用std::stable_sort,但效率会有所降低,stable_sort的底层是归并排序;
  • 排序算法选择标准:
    • 1、快排:数据量较大时,主要选择快排,因为它平均速度较快;
    • 2、堆排序:当快排的递归深度超过一定阈值(发现数据分布极不均匀,快排可能退化)时,它会果断切换到堆排,保证最坏情况下的复杂度依然是 O(n \log n);
    • 3、插入排序:当数据量缩减到很小(通常是 16 个元素以内)时,由于插入排序在小规模数据上具有极佳的缓存局部性,它会切换到插入排序来收尾;
  • 自定义排序函数:
    • 使用lamda函数;
#include <iostream>
#include <vector>
#include <algorithm>

struct Student {
std::string name;
int score;
};

int main() {
std::vector<Student> students = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 95}};

// 需求:按分数从高到低排序 (降序)
std::sort(students.begin(), students.end(), [](const Student& a, const Student& b) {
return a.score > b.score; // 如果 a 的分更高,a 排在前面
});

for (const auto& s : students) std::cout << s.name << ":" << s.score << " ";
// 输出: Charlie:95 Alice:90 Bob:85
return 0;
}

常见排序算法

  • 快速排序:选一个基准值,左边全比它小,右边全比它大;
// 核心分区函数
int partition(std::vector<int>& v, int low, int high) {
int pivot = v[high]; // 选最后一个作为基准
int i = low - 1;
for (int j = low; j < high; j++) {
if (v[j] < pivot) {
i++;
std::swap(v[i], v[j]);
}
}
std::swap(v[i + 1], v[high]);
return i + 1;
}

void quickSort(std::vector<int>& v, int low, int high) {
if (low < high) {
int pi = partition(v, low, high);
quickSort(v, low, pi - 1); // 递归左半部分
quickSort(v, pi + 1, high); // 递归右半部分
}
}
  • 归并排序:递归二分到最底层,再合并
void merge(std::vector<int>& v, int l, int m, int r) {
std::vector<int> L(v.begin() + l, v.begin() + m + 1);
std::vector<int> R(v.begin() + m + 1, v.begin() + r + 1);

int i = 0, j = 0, k = l;
while (i < L.size() && j < R.size()) {
v[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
}
while (i < L.size()) v[k++] = L[i++]; // 补齐剩余部分
while (j < R.size()) v[k++] = R[j++];
}

void mergeSort(std::vector<int>& v, int l, int r) {
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(v, l, m);
mergeSort(v, m + 1, r);
merge(v, l, m, r);
}
}