30.stl容器了解吗?底层如何实现:vector数组,map红黑树,红黑树的实现

2026-01-23
2073 字 · 10 分钟

🔬 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. 分配新的内存空间,通常是原容量的 1.5 倍或 2 倍
  2. 将原有元素复制到新内存空间
  3. 释放原内存空间
  4. 更新三个指针的指向

关键代码结构

// 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),这是一种自平衡的二叉查找树。

红黑树的核心特性

红黑树是一种满足以下性质的二叉查找树:

  1. 每个节点要么是红色,要么是黑色
  2. 根节点是黑色
  3. 每个叶子节点(NIL节点)是黑色
  4. 如果一个节点是红色,则它的两个子节点都是黑色
  5. 对于每个节点,从该节点到其所有后代叶子节点的简单路径上,均包含相同数目的黑色节点

红黑树的优势

  • 自平衡:插入和删除操作后会自动调整,保持树的高度平衡
  • 高效的查找性能:时间复杂度 O(log n)
  • 高效的插入删除性能:时间复杂度 O(log n),优于 AVL 树

红黑树的实现关键点

  1. 节点结构
struct RBNode {
RBNode* parent; // 父节点
RBNode* left; // 左子节点
RBNode* right; // 右子节点
int color; // 颜色:0=黑色,1=红色
Key key; // 关键字
Value value; // 值(仅map需要)
};
  1. 旋转操作
    • 左旋(Left Rotation)
    • 右旋(Right Rotation)
  2. 插入调整
    • 插入新节点后,可能破坏红黑树性质
    • 通过旋转和变色操作恢复平衡
  3. 删除调整
    • 删除节点后,可能破坏红黑树性质

map 与 set 的区别

特性mapset
存储内容键值对(key-value)仅键(key)
键的唯一性键唯一键唯一
遍历结果按键排序按键排序
查找方式通过键查找值通过键查找元素

🛠️ 3. unordered_map/unordered_set 底层实现

核心实现原理

unordered_map 和 unordered_set 的底层实现都是哈希表(Hash Table)

哈希表的实现结构

  1. 桶数组:存储指向链表或红黑树的指针
  2. 哈希函数:将键转换为桶数组的索引
  3. 冲突处理
    • 链地址法:每个桶维护一个链表或红黑树
    • 开放地址法:线性探测、二次探测等

关键实现要点

  • 负载因子:元素数量与桶数量的比值,超过阈值时触发扩容
  • 扩容机制:重新分配更大的桶数组,重新计算所有元素的哈希值
  • 哈希函数:影响哈希表性能的关键因素

性能特性

操作时间复杂度(平均)时间复杂度(最坏)
查找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;
}

红黑树的插入操作

插入操作的核心步骤:

  1. 按照二叉查找树的规则插入新节点
  2. 将新节点标记为红色
  3. 调用调整函数恢复红黑树性质

红黑树的删除操作

删除操作的核心步骤:

  1. 按照二叉查找树的规则删除节点
  2. 处理删除后可能破坏的红黑树性质 📋 总结与最佳实践
  3. 容器选择原则
    • 需要随机访问:选择 vector 或 deque
    • 需要频繁插入删除:选择 list 或 deque
    • 需要按键查找:选择 map(有序)或 unordered_map(无序)
    • 需要集合操作:选择 set(有序)或 unordered_set(无序)
    • 需要特定数据结构:选择 stack、queue 或 priority_queue
  4. 性能考虑
    • vector 在尾部插入删除效率高,中间插入删除效率低
    • map/set 适合需要有序遍历的场景
    • unordered_map/unordered_set 在平均情况下查找效率更高
    • list 适合频繁在任意位置插入删除的场景
  5. 内存使用
    • vector 内存连续,内存利用率高
    • list 每个节点有额外指针开销
    • map/set 每个节点有颜色和指针开销
    • unordered_map/unordered_set 存在哈希表桶的额外开销
  6. 红黑树的优势
    • 自平衡,保证 O(log n) 的查找、插入、删除复杂度
    • 相比于 AVL 树,旋转操作更少,插入删除性能更好
    • 广泛应用于 STL 的关联式容器 理解 STL 容器的底层实现原理,有助于在实际开发中选择合适的容器,编写高效的代码。不同容器有不同的适用场景,需要根据具体需求进行选择。

Thanks for reading!

30.stl容器了解吗?底层如何实现:vector数组,map红黑树,红黑树的实现

2026-01-23
2073 字 · 10 分钟

已复制链接

评论区

目录