红黑树解析:C++ STL 中 map 与 set 的底层原理
在 C++ STL 中,std::set、std::map、std::multiset、std::multimap 这四个关联式容器,底层几乎全部使用红黑树(Red-Black Tree) 实现(主流实现如 libstdc++、libc++ 都是如此)。
红黑树是一种自平衡二叉搜索树,它在插入/删除后通过颜色调整和旋转操作,始终保证树的高度接近 log n,从而让查找、插入、删除的时间复杂度稳定在 O(log n)。
1. 为什么选择红黑树?
| 普通二叉树 | O(n) | O(n) | O(n) | O(n) | 中等 | 无 | 最坏退化严重 |
| AVL 树 | O(log n) | O(log n) | O(log n) | O(n) | 中等 | 高(旋转多) | 旋转次数多,工程复杂 |
| 红黑树 | O(log n) | O(log n) | O(log n) | O(n) | 中等 | 中等 | 平衡性好,旋转次数少,常用于工程 |
| B+ 树 | O(log n) | O(log n) | O(log n) | O(n) | 高 | 复杂 | 更适合数据库/文件系统 |
| 哈希表 | O(1) | O(1) | O(1) | 无序 | 高 | 无 | 无序,无法实现有序容器 |
结论:红黑树在平衡性、插入/删除代价、实现复杂度三者之间取得了最佳折中,非常适合 STL 的有序关联容器。
2. 红黑树的五大性质(必须死记)
红黑树是一棵二叉搜索树 + 颜色约束:
这五条性质共同保证了:最长路径不超过最短路径的 2 倍,从而树高 ≈ log n。
3. STL 中红黑树的节点结构(简化版)
struct _Rb_tree_node_base {
typedef _Rb_tree_node_base* base_ptr;
base_ptr parent, left, right;
_Rb_tree_color color; // 枚举:_S_red, _S_black
};
template<typename Value>
struct _Rb_tree_node : public _Rb_tree_node_base {
Value value_field; // 真正存储的数据
};
- map 中 Value = std::pair<const Key, T>
- set 中 Value = Key(或者说 set 把 key 当 value)
4. map 与 set 的区别(核心差异在 KeyOfValue)
STL 通过模板参数和萃取技巧,让同一棵红黑树同时支持 map / set / multimap / multiset。
// 伪代码风格(简化)
template <typename Key, typename Value,
typename KeyOfValue, // 关键差异点!
typename Compare = less<Key>,
typename Alloc = allocator<Value>>
class _Rb_tree {
// …
};
// set 的 KeyOfValue
struct _Identity {
template<typename T> const T& operator()(const T& x) const { return x; }
};
// map 的 KeyOfValue
struct _Select1st {
template<typename Pair> const typename Pair::first_type&
operator()(const Pair& x) const { return x.first; }
};
一句话总结差异:
| set | 否 | Key | 返回元素本身 | 重复不插入 |
| multiset | 是 | Key | 返回元素本身 | 允许重复 |
| map | 否(key 唯一) | pair<const Key, T> | 返回 pair.first (key) | key 重复不插入([] 会覆盖) |
| multimap | 是 | pair<const Key, T> | 返回 pair.first (key) | 允许 key 重复 |
5. 红黑树的核心操作(插入/删除)
插入(大致流程):
删除(更复杂):
STL 的实现非常精巧,使用了大量模板元编程和内联函数来减少开销。
6. 时间复杂度对比表(最常用操作)
| 查找(find) | O(log n) | O(log n) | 平均 O(1),最坏 O(n) |
| 插入(insert) | O(log n) | O(log n) | 平均 O(1),最坏 O(n) |
| 删除(erase) | O(log n) | O(log n) | 平均 O(1),最坏 O(n) |
| 顺序遍历 | O(n) | O(n) | O(n)(无序) |
| 按 key 排序 | 是 | 是 | 否 |
7. 面试/实战高频问题
map 和 set 为什么用红黑树而不是 AVL 树? → 红黑树插入/删除时的旋转次数更少(工程上更高效),虽然 AVL 更平衡,但维护代价高。
map 的 [] 运算符为什么会插入新元素? → 因为返回的是 mapped_type&,如果 key 不存在会默认构造 value 并插入。
multimap 如何实现 key 重复? → 红黑树插入时不检查相等性,只按 < 排序,允许相同 key 连续存放。
为什么 map 的 key 是 const 的? → 防止修改 key 破坏红黑树排序规则。
红黑树和 B+ 树的主要区别? → 红黑树是内存结构(每个节点只存少量数据),B+ 树是磁盘友好结构(叶子存数据,非叶子只存索引)。
总结一句话
C++ STL 中的 set / map / multiset / multimap 底层都是红黑树,通过KeyOfValue 萃取和比较器的模板技巧,实现了有序性、唯一性/可重复性的差异,在 O(log n) 的复杂度下提供了高效的键值关联存储能力。
如果你想继续深入以下任一方向,告诉我:
- 红黑树插入/删除的详细步骤 + 旋转图解
- 自己手写一个简易版红黑树(带 set/map 接口)
- map 与 unordered_map 的性能对比场景
- STL 源码中 _Rb_tree 的关键实现细节
随时说~


