30.stl容器了解吗?底层如何实现:vector数组,map红黑树,红黑树的实现
🔬 STL容器底层实现详解
📖 内容概览
本文将详细解析C++ STL容器的底层实现原理,包括vector动态数组、map/set红黑树、unordered_map/unordered_set哈希表等核心数据结构,以及红黑树的实现细节。
🎯 核心概念
STL容器的分类
STL容器主要分为三大类:
| 容器类型 | 特点 | 代表容器 |
|---|---|---|
| 序列式容器 | 按顺序存储元素,元素位置由插入顺序决定 | vector、list、deque、array |
| 关联式容器 | 元素按关键字排序,支持快速查找 | map、set、multimap、multiset |
| 无序关联式容器 | 元素无序,通过哈希表实现快速访问 | unordered_map、unordered_set、unordered_multimap、unordered_multiset |
| 适配器容器 | 基于其他容器实现,提供特定接口 | stack、queue、priority_queue |
🛠️ 1. vector 底层实现
核心实现原理
vector 的底层是一个动态数组,使用连续的内存空间存储元素。其核心结构包含三个指针:
start:指向数组起始位置finish:指向数组中已存储元素的末尾end_of_storage:指向数组容量的末尾
扩容机制
当向 vector 中添加元素导致容量不足时,会触发扩容操作:
- 分配新的内存空间,通常是原容量的 1.5 倍或 2 倍
- 将原有元素复制到新内存空间
- 释放原内存空间
- 更新三个指针的指向
关键代码结构
// vector 核心数据结构template <typename T, typename Alloc = allocator<T>>class vector {private: iterator start; // 指向数组起始位置 iterator finish; // 指向数组中已存储元素的末尾 iterator end_of_storage; // 指向数组容量的末尾
public: // 构造函数 vector() : start(nullptr), finish(nullptr), end_of_storage(nullptr) {} // 析构函数 ~vector() { if (start) { destroy(start, finish); deallocate(); } } // 其他成员函数...};性能特性
| 操作 | 时间复杂度 |
|---|---|
| 随机访问 | O(1) |
| 尾部插入/删除 | 平均 O(1),最坏 O(n)(需扩容) |
| 中间插入/删除 | O(n) |
| 空间复杂度 | O(n) |
🛠️ 2. map/set 底层实现
核心实现原理
map 和 set 的底层实现都是红黑树(Red-Black Tree),这是一种自平衡的二叉查找树。
红黑树的核心特性
红黑树是一种满足以下性质的二叉查找树:
- 每个节点要么是红色,要么是黑色
- 根节点是黑色
- 每个叶子节点(NIL节点)是黑色
- 如果一个节点是红色,则它的两个子节点都是黑色
- 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点
红黑树的优势
- 自平衡:插入和删除操作后会自动调整,保持树的高度平衡
- 高效的查找性能:时间复杂度 O(log n)
- 高效的插入删除性能:时间复杂度 O(log n),优于 AVL 树
红黑树的实现关键点
- 节点结构
struct RBNode { RBNode* parent; // 父节点 RBNode* left; // 左子节点 RBNode* right; // 右子节点 int color; // 颜色:0=黑色,1=红色 Key key; // 关键字 Value value; // 值(仅map需要)};- 旋转操作
- 左旋(Left Rotation)
- 右旋(Right Rotation)
- 插入调整
- 插入新节点后,可能破坏红黑树性质
- 通过旋转和变色操作恢复平衡
- 删除调整
- 删除节点后,可能破坏红黑树性质
map 与 set 的区别
| 特性 | map | set |
|---|---|---|
| 存储内容 | 键值对(key-value) | 仅键(key) |
| 键的唯一性 | 键唯一 | 键唯一 |
| 遍历结果 | 按键排序 | 按键排序 |
| 查找方式 | 通过键查找值 | 通过键查找元素 |
🛠️ 3. unordered_map/unordered_set 底层实现
核心实现原理
unordered_map 和 unordered_set 的底层实现都是哈希表(Hash Table)。
哈希表的实现结构
- 桶数组:存储指向链表或红黑树的指针
- 哈希函数:将键转换为桶数组的索引
- 冲突处理:
- 链地址法:每个桶维护一个链表或红黑树
- 开放地址法:线性探测、二次探测等
关键实现要点
- 负载因子:元素数量与桶数量的比值,超过阈值时触发扩容
- 扩容机制:重新分配更大的桶数组,重新计算所有元素的哈希值
- 哈希函数:影响哈希表性能的关键因素
性能特性
| 操作 | 时间复杂度(平均) | 时间复杂度(最坏) |
|---|---|---|
| 查找 | O(1) | O(n) |
| 插入 | O(1) | O(n) |
| 删除 | O(1) | O(n) |
🛠️ 4. 其他常用容器底层实现
list
- 底层实现:双向链表
- 核心特性:
- 每个节点包含数据和前后指针
- 支持常数时间的插入和删除操作
- 不支持随机访问
- 空间开销较大(每个节点需要额外存储两个指针)
deque
- 底层实现:分段连续的动态数组
- 由多个固定大小的数组块组成
- 支持在队首和队尾进行常数时间的插入和删除
- 支持随机访问
- 内部维护一个指向数组块的指针数组
queue
- 底层实现:默认基于 deque 实现
- 先进先出(FIFO)的数据结构
- 仅支持在队尾插入,队首删除
priority_queue
- 底层实现:默认基于 vector 实现的堆结构
- 优先级队列,每次取出优先级最高的元素
- 默认使用大根堆实现
- 插入和删除操作的时间复杂度为 O(log n)
stack
- 底层实现:默认基于 deque 实现
- 后进先出(LIFO)的数据结构
- 仅支持在栈顶插入和删除
- 插入和删除操作的时间复杂度为 O(1)
🔄 5. 红黑树实现详解
红黑树的节点结构
enum Color { BLACK, RED };template <typename Key, typename Value>struct RBNode { Key key; Value value; Color color; RBNode* left; RBNode* right; RBNode* parent;
RBNode(const Key& k, const Value& v, Color c = RED) : key(k), value(v), color(c), left(nullptr), right(nullptr), parent(nullptr) {}};红黑树的旋转操作
左旋操作
// 对节点x进行左旋void leftRotate(RBNode* x) { RBNode* y = x->right; // 将y的左子树作为x的右子树 x->right = y->left; if (y->left != nullptr) { y->left->parent = x; } // 将x的父节点作为y的父节点 y->parent = x->parent; if (x->parent == nullptr) { root = y; // x是根节点 } else if (x == x->parent->left) { x->parent->left = y; } else { x->parent->right = y; } // 将x作为y的左子树 y->left = x; x->parent = y;}右旋操作
// 对节点y进行右旋void rightRotate(RBNode* y) { RBNode* x = y->left; // 将x的右子树作为y的左子树 y->left = x->right; if (x->right != nullptr) { x->right->parent = y; } // 将y的父节点作为x的父节点 x->parent = y->parent; if (y->parent == nullptr) { root = x; // y是根节点 } else if (y == y->parent->left) { y->parent->left = x; } else { y->parent->right = x; } // 将y作为x的右子树 x->right = y; y->parent = x;}红黑树的插入操作
插入操作的核心步骤:
- 按照二叉查找树的规则插入新节点
- 将新节点标记为红色
- 调用调整函数恢复红黑树性质
红黑树的删除操作
删除操作的核心步骤:
- 按照二叉查找树的规则删除节点
- 处理删除后可能破坏的红黑树性质 📋 总结与最佳实践
- 容器选择原则:
- 需要随机访问:选择 vector 或 deque
- 需要频繁插入删除:选择 list 或 deque
- 需要按键查找:选择 map(有序)或 unordered_map(无序)
- 需要集合操作:选择 set(有序)或 unordered_set(无序)
- 需要特定数据结构:选择 stack、queue 或 priority_queue
- 性能考虑:
- vector 在尾部插入删除效率高,中间插入删除效率低
- map/set 适合需要有序遍历的场景
- unordered_map/unordered_set 在平均情况下查找效率更高
- list 适合频繁在任意位置插入删除的场景
- 内存使用:
- vector 内存连续,内存利用率高
- list 每个节点有额外指针开销
- map/set 每个节点有颜色和指针开销
- unordered_map/unordered_set 存在哈希表桶的额外开销
- 红黑树的优势:
- 自平衡,保证 O(log n) 的查找、插入、删除复杂度
- 相比于 AVL 树,旋转操作更少,插入删除性能更好
- 广泛应用于 STL 的关联式容器 理解 STL 容器的底层实现原理,有助于在实际开发中选择合适的容器,编写高效的代码。不同容器有不同的适用场景,需要根据具体需求进行选择。