C++ 容器库概述:序列容器、关联容器与无序关联容器的原理、性能与应用
C++ 标准库提供了多种容器,用于存储和管理数据集合。容器分为三大类:序列容器、关联容器和无序关联容器。每类容器有不同的特性和适用场景,下面从时间复杂度、空间复杂度和作用三个方面进行详细讲解,并附代码示例:
一、序列容器(Sequence Containers)
序列容器按线性顺序存储元素,允许指定插入位置。共有 6 种标准序列容器(严格分类),但若包括容器适配器(如 stack
和 queue
),则可能达到 8 种。
1. vector(动态数组)
-
作用:动态数组,支持快速随机访问。
-
时间复杂度:
-
尾部插入/删除:均摊
O(1)
-
中间插入/删除:
O(n)
-
随机访问:
O(1)
-
-
空间复杂度:连续内存,预分配空间。
-
示例:
#include <vector> std::vector<int> v = {1, 2, 3}; v.push_back(4); // 尾部插入 int val = v[2]; // 随机访问
2. deque(双端队列)
-
作用:双端队列,支持头尾快速插入。
-
时间复杂度:
-
头尾插入/删除:
O(1)
-
中间插入/删除:
O(n)
-
随机访问:
O(1)
-
-
空间复杂度:分块存储,内存非连续。
-
示例:
#include <deque> std::deque<int> d = {2, 3}; d.push_front(1); // 头部插入 d.push_back(4); // 尾部插入
3. list(双链表)
-
作用:双向链表,支持任意位置快速插入。
-
时间复杂度:
-
插入/删除:
O(1)
-
随机访问:
O(n)
-
-
空间复杂度:每个元素含前后指针。
-
示例:
#include <list> std::list<int> l = {1, 3}; auto it = l.begin(); it++; l.insert(it, 2); // 在第二个位置插入
4. forward_list(单链表)
-
作用:单向链表,更节省内存。
-
时间复杂度:
-
插入/删除:
O(1)
-
随机访问:
O(n)
-
-
空间复杂度:每个元素含单个指针。
-
示例:
#include <forward_list> std::forward_list<int> fl = {2, 3}; fl.push_front(1); // 只能头部插入
5. array(静态数组)
-
作用:固定大小数组,替代原生数组。
-
时间复杂度:
-
插入/删除:不支持
-
随机访问:
O(1)
-
-
空间复杂度:栈或静态内存分配。
-
示例:
#include <array> std::array<int, 3> arr = {1, 2, 3}; int val = arr.at(1); // 安全访问
6. string(字符串动态数组)
-
作用:专为字符串设计的动态数组。
-
特性:类似
vector<char>
,但支持字符串操作。 -
示例:
#include <string> std::string s = "Hello"; s += " World!";
二、容器适配器(Container Adapters)
容器适配器是对标准容器的封装,提供特定的接口和行为。使用序列容器,可以实现容器适配器。
栈(stack
)
1. 时间复杂度
-
插入(
push
):O(1)
-
在栈顶插入元素,直接添加到容器尾部。
-
-
删除(
pop
):O(1)
-
从栈顶删除元素,直接移除容器尾部元素。
-
-
访问栈顶(
top
):O(1)
-
直接访问容器尾部元素。
-
2. 空间复杂度
-
空间占用:
O(n)
-
栈的空间占用取决于存储的元素数量。
-
-
底层容器:默认使用
deque
,也可以使用vector
或list
。
3. 适用场景
-
需要后进先出(LIFO)行为的场景,如:
-
函数调用栈(递归)。
-
表达式求值(如括号匹配)。
-
撤销操作(如编辑器中的撤销功能)。
-
4. 代码示例
#include <stack>
std::stack<int> s;
s.push(1); // O(1)
s.push(2); // O(1)
std::cout << s.top(); // 输出 2, O(1)
s.pop(); // O(1)
std::cout << s.top(); // 输出 1, O(1)
队列(queue
)
1. 时间复杂度
-
插入(
push
):O(1)
-
在队尾插入元素,直接添加到容器尾部。
-
-
删除(
pop
):O(1)
-
从队首删除元素,直接移除容器头部元素。
-
-
访问队首(
front
):O(1)
-
直接访问容器头部元素。
-
2. 空间复杂度
-
空间占用:
O(n)
-
队列的空间占用取决于存储的元素数量。
-
-
底层容器:默认使用
deque
,也可以使用list
。
3. 适用场景
-
需要先进先出(FIFO)行为的场景,如:
-
任务调度(如打印任务队列)。
-
消息队列(如事件处理系统)。
-
广度优先搜索(BFS)算法。
-
4. 代码示例
#include <queue>
std::queue<int> q;
q.push(1); // O(1)
q.push(2); // O(1)
std::cout << q.front(); // 输出 1, O(1)
q.pop(); // O(1)
std::cout << q.front(); // 输出 2, O(1)
优先队列(priority_queue
)
1. 时间复杂度
-
插入(
push
):O(log n)
-
插入元素并调整堆结构。
-
-
删除(
pop
):O(log n)
-
删除堆顶元素并调整堆结构。
-
-
访问堆顶(
top
):O(1)
-
直接访问堆顶元素。
-
2. 空间复杂度
-
空间占用:
O(n)
-
优先队列的空间占用取决于存储的元素数量。
-
-
底层容器:默认使用
vector
,也可以使用deque
。
3. 适用场景
-
需要动态获取最大或最小元素的场景,如:
-
任务调度(按优先级处理任务)。
-
最小生成树算法(如 Prim 算法)。
-
最短路径算法(如 Dijkstra 算法)。
-
4. 代码示例
#include <queue>
std::priority_queue<int> pq;
pq.push(3); // O(log n)
pq.push(1); // O(log n)
pq.push(4); // O(log n)
std::cout << pq.top(); // 输出 4, O(1)
pq.pop(); // O(log n)
std::cout << pq.top(); // 输出 3, O(1)
总结对比
容器适配器 | 插入时间复杂度 | 删除时间复杂度 | 访问时间复杂度 | 空间复杂度 | 底层容器 | 适用场景 |
---|---|---|---|---|---|---|
栈 (stack ) | O(1) | O(1) | O(1) | O(n) | deque | 后进先出(LIFO)场景 |
队列 (queue ) | O(1) | O(1) | O(1) | O(n) | deque | 先进先出(FIFO)场景 |
优先队列 (priority_queue ) | O(log n) | O(log n) | O(1) | O(n) | vector | 动态获取最大/最小元素场景 |
三、关联容器(Associative Containers)
关联容器基于红黑树实现,元素按键有序存储,共有 4 种。
1. set
-
作用:有序唯一键集合。
-
时间复杂度:插入/删除/查找
O(log n)
。 -
示例:
#include <set> std::set<int> s = {3, 1, 2}; s.insert(4); bool exists = s.count(2); // 返回 1
2. map
-
作用:键值对集合,键唯一。
-
时间复杂度:
O(log n)
。 -
示例:
#include <map> std::map<std::string, int> m; m["apple"] = 5; m.insert({"banana", 3});
3. multiset
-
作用:允许重复键的有序集合。
-
示例:
std::multiset<int> ms = {1, 1, 2}; ms.insert(1);
4. multimap
-
作用:允许重复键的键值对集合。
-
示例:
std::multimap<std::string, int> mm; mm.insert({"apple", 1}); mm.insert({"apple", 2});
四、无序关联容器(Unordered Associative Containers)
无序容器基于哈希表实现,元素无序,共有 4 种。
1. unordered_set
-
作用:唯一键的无序集合。
-
时间复杂度:平均
O(1)
,最坏O(n)
。 -
示例:
#include <unordered_set> std::unordered_set<int> us = {3, 1, 2}; us.insert(4);
2. unordered_map
-
作用:键值对的无序集合。
-
示例:
#include <unordered_map> std::unordered_map<std::string, int> um; um["apple"] = 5; um["banana"] = 3;
3. unordered_multiset
和 unordered_multimap
-
作用:允许重复键的无序集合/映射。
-
示例:
std::unordered_multiset<int> ums = {1, 1, 2}; std::unordered_multimap<std::string, int> umm; umm.insert({"apple", 1}); umm.insert({"apple", 2});
总结
-
序列容器:适用于需要保留插入顺序的场景(如
vector
用于快速随机访问,list
用于频繁插入)。 -
关联容器:适用于需要有序遍历的场景(如按顺序处理数据)。
-
无序关联容器:适用于快速查找且不关心顺序的场景(如缓存、字典)。