Skip to content

std::map/set 实现分析

std::mapstd::set 都基于红黑树(Red-Black Tree)实现。本文基于 GCC 和 LLVM 的源码,分析红黑树的内部实现。


一、核心数据结构

1.1 红黑树节点

cpp
// 源码路径:references/impl/gcc/libstdc++-v3/include/bits/stl_tree.h

struct _Rb_tree_node_base {
    typedef _Rb_tree_node_base* _Base_ptr;
    
    _Rb_tree_color _M_color;   // 节点颜色(红或黑)
    _Base_ptr      _M_parent;  // 父节点
    _Base_ptr      _M_left;    // 左子节点
    _Base_ptr      _M_right;   // 右子节点
};

template<typename _Val>
struct _Rb_tree_node : public _Rb_tree_node_base {
    __gnu_cxx::__aligned_membuf<_Val> _M_storage;  // 对齐存储
    
    _Val* _M_valptr() { return _M_storage._M_ptr(); }
};
红黑树节点布局:

┌─────────────────────────────────────────┐
│ _M_color (1 字节)                        │  ← 红或黑
├─────────────────────────────────────────┤
│ _M_parent (8 字节)                       │  ← 父节点指针
├─────────────────────────────────────────┤
│ _M_left (8 字节)                         │  ← 左子节点
├─────────────────────────────────────────┤
│ _M_right (8 字节)                        │  ← 右子节点
├─────────────────────────────────────────┤
│ _M_storage (对齐存储)                    │  ← 值类型
└─────────────────────────────────────────┘

总大小:4(颜色+填充)+ 8×3(指针)+ sizeof(T) = 28 + sizeof(T)

1.2 红黑树头节点

cpp
struct _Rb_tree_header {
    _Rb_tree_node_base _M_header;    // 头节点
    size_t             _M_node_count; // 节点数量
    
    void _M_reset() {
        _M_header._M_parent = 0;
        _M_header._M_left = &_M_header;
        _M_header._M_right = &_M_header;
        _M_node_count = 0;
    }
};
红黑树的头节点设计:

_header._M_parent → 根节点
_header._M_left   → 最左节点(begin())
_header._M_right  → 最右节点

空树状态:
┌──────────────────┐
│ header           │
│ _M_parent = nullptr │
│ _M_left = &header │  ← 指向自己
│ _M_right = &header │  ← 指向自己
└──────────────────┘

二、红黑树性质

2.1 红黑树的五个性质

红黑树的五个性质:

1. 每个节点是红色或黑色
2. 根节点是黑色
3. 所有叶子节点(NIL)是黑色
4. 红色节点的两个子节点都是黑色
5. 从任一节点到其每个叶子的所有路径包含相同数目的黑色节点

这些性质保证:
· 最长路径不超过最短路径的 2 倍
· 树的高度为 O(log n)
· 插入、删除、查找都是 O(log n)

2.2 插入操作

插入操作的步骤:

1. 找到插入位置(O(log n))
2. 创建新节点(红色)
3. 插入节点
4. 修复红黑树性质(可能需要旋转)

修复规则:
· 情况 1:叔叔节点是红色
  → 重新着色(父+叔变黑,祖父变红)

· 情况 2:叔叔节点是黑色,当前节点是右孩子
  → 左旋转换为情况 3

· 情况 3:叔叔节点是黑色,当前节点是左孩子
  → 右旋 + 重新着色

三、GCC (libstdc++) 的实现

3.1 红黑树插入与旋转(源码分析)

cpp
// 源码路径:references/impl/gcc/libstdc++-v3/src/c++98/tree.cc:194

void _Rb_tree_insert_and_rebalance(const bool __insert_left,
                                   _Rb_tree_node_base* __x,
                                   _Rb_tree_node_base* __p,
                                   _Rb_tree_node_base& __header) throw () {
    _Rb_tree_node_base*& __root = __header._M_parent;

    // 1. 初始化新节点
    __x->_M_parent = __p;
    __x->_M_left = 0;
    __x->_M_right = 0;
    __x->_M_color = _S_red;  // 新节点总是红色

    // 2. 插入节点
    if (__insert_left) {
        __p->_M_left = __x;
        if (__p == &__header) {
            __header._M_parent = __x;
            __header._M_right = __x;
        } else if (__p == __header._M_left)
            __header._M_left = __x;
    } else {
        __p->_M_right = __x;
        if (__p == __header._M_right)
            __header._M_right = __x;
    }

    // 3. 重新平衡(核心算法)
    while (__x != __root && __x->_M_parent->_M_color == _S_red) {
        _Rb_tree_node_base* const __xpp = __x->_M_parent->_M_parent;
        
        if (__x->_M_parent == __xpp->_M_left) {
            // 情况 1:父节点是左子节点
            _Rb_tree_node_base* const __y = __xpp->_M_right;
            if (__y && __y->_M_color == _S_red) {
                // 叔叔节点是红色:重新着色
                __x->_M_parent->_M_color = _S_black;
                __y->_M_color = _S_black;
                __xpp->_M_color = _S_red;
                __x = __xpp;
            } else {
                // 叔叔节点是黑色
                if (__x == __x->_M_parent->_M_right) {
                    __x = __x->_M_parent;
                    local_Rb_tree_rotate_left(__x, __root);
                }
                __x->_M_parent->_M_color = _S_black;
                __xpp->_M_color = _S_red;
                local_Rb_tree_rotate_right(__xpp, __root);
            }
        } else {
            // 情况 2:父节点是右子节点(对称)
            _Rb_tree_node_base* const __y = __xpp->_M_left;
            if (__y && __y->_M_color == _S_red) {
                __x->_M_parent->_M_color = _S_black;
                __y->_M_color = _S_black;
                __xpp->_M_color = _S_red;
                __x = __xpp;
            } else {
                if (__x == __x->_M_parent->_M_left) {
                    __x = __x->_M_parent;
                    local_Rb_tree_rotate_right(__x, __root);
                }
                __x->_M_parent->_M_color = _S_black;
                __xpp->_M_color = _S_red;
                local_Rb_tree_rotate_left(__xpp, __root);
            }
        }
    }
    __root->_M_color = _S_black;  // 根节点总是黑色
}

3.2 红黑树旋转(源码分析)

cpp
// 源码路径:references/impl/gcc/libstdc++-v3/src/c++98/tree.cc:132

// 左旋
static void local_Rb_tree_rotate_left(_Rb_tree_node_base* const __x,
                                      _Rb_tree_node_base*& __root) {
    _Rb_tree_node_base* const __y = __x->_M_right;
    
    __x->_M_right = __y->_M_left;
    if (__y->_M_left != 0)
        __y->_M_left->_M_parent = __x;
    __y->_M_parent = __x->_M_parent;
    
    if (__x == __root)
        __root = __y;
    else if (__x == __x->_M_parent->_M_left)
        __x->_M_parent->_M_left = __y;
    else
        __x->_M_parent->_M_right = __y;
    
    __y->_M_left = __x;
    __x->_M_parent = __y;
}

// 右旋(对称)
static void local_Rb_tree_rotate_right(_Rb_tree_node_base* const __x,
                                       _Rb_tree_node_base*& __root) {
    _Rb_tree_node_base* const __y = __x->_M_left;
    
    __x->_M_left = __y->_M_right;
    if (__y->_M_right != 0)
        __y->_M_right->_M_parent = __x;
    __y->_M_parent = __x->_M_parent;
    
    if (__x == __root)
        __root = __y;
    else if (__x == __x->_M_parent->_M_right)
        __x->_M_parent->_M_right = __y;
    else
        __x->_M_parent->_M_left = __y;
    
    __y->_M_right = __x;
    __x->_M_parent = __y;
}

3.3 迭代器递增(源码分析)

cpp
// 源码路径:references/impl/gcc/libstdc++-v3/src/c++98/tree.cc:59

// 中序遍历的下一个节点
static _Rb_tree_node_base* local_Rb_tree_increment(_Rb_tree_node_base* __x) throw () {
    if (__x->_M_right != 0) {
        // 有右子树:找到右子树的最左节点
        __x = __x->_M_right;
        while (__x->_M_left != 0)
            __x = __x->_M_left;
    } else {
        // 无右子树:向上回溯
        _Rb_tree_node_base* __y = __x->_M_parent;
        while (__x == __y->_M_right) {
            __x = __y;
            __y = __y->_M_parent;
        }
        if (__x->_M_right != __y)
            __x = __y;
    }
    return __x;
}

3.2 node_handle 支持 (C++17)

cpp
// GCC 支持 node_handle 用于提取/插入节点
// 这允许在容器之间移动节点而不拷贝数据

node_type extract(const_iterator __position);
iterator insert(node_type&& __nh);

四、LLVM (libc++) 的实现

4.1 __tree 结构

LLVM 使用 __tree 类实现红黑树:

cpp
// 源码路径:references/impl/llvm-project/libcxx/include/__tree

template <class _Tp, class _Compare, class _Allocator>
class __tree {
    using __node = __tree_node_types<_Tp>::__node;
    using __node_pointer = __node*;
    
    __node_pointer __begin_node_;  // 指向最左节点
    pair<__node_pointer, compressed_pair<__end_node_t, __tree_balance>> __pair3_;
};

4.2 压缩存储

LLVM 使用 compressed_pair 压缩比较器和平衡信息:

LLVM __tree 的内存布局:

┌─────────────────────────────────────┐
│ __begin_node_ (8 字节)               │  ← 指向最左节点
├─────────────────────────────────────┤
│ __pair3_                             │
│   first: __end_node_ (包含根节点)    │
│   second: compressed_pair            │
│     first: __node_count_            │
│     second: __comp_ (比较器)         │
└─────────────────────────────────────┘

五、map vs set

map 和 set 的区别:

set:
  · 只存储键(key)
  · 节点类型:_Rb_tree_node<key_type>
  · 比较器:key_compare

map:
  · 存储键值对(key-value)
  · 节点类型:_Rb_tree_node<value_type>
  · value_type = pair<const Key, T>
  · 比较器:key_compare(只比较 key)

内存布局差异:
  set 节点:sizeof(node) = 28 + sizeof(Key)
  map 节点:sizeof(node) = 28 + sizeof(pair<const Key, T>)

六、GCC vs LLVM 差异对比

┌────────────────────────┬──────────────────────┬──────────────────────┐
│ 特性                    │ GCC (libstdc++)      │ LLVM (libc++)        │
├────────────────────────┼──────────────────────┼──────────────────────┤
│ 红黑树实现             │ _Rb_tree             │ __tree               │
│ 节点存储               │ aligned_membuf       │ 直接存储             │
│ 头节点设计             │ _Rb_tree_header      │ __end_node_t         │
│ 迭代器                 │ _Rb_tree_iterator    │ __tree_iterator      │
│ node_handle (C++17)    │ 支持                 │ 支持                 │
│ extract/insert 节点    │ 支持                 │ 支持                 │
│ 比较器存储             │ _Rb_tree_key_compare │ compressed_pair      │
└────────────────────────┴──────────────────────┴──────────────────────┘

七、性能特征

map/set 的性能:

查找:O(log n)
插入:O(log n)(摊销)
删除:O(log n)
迭代:O(n)

内存开销:
  · 每个节点:28 字节(颜色+3指针+填充)+ 数据
  · 100 万个 int → 约 32 MB(28 + 4 = 32 字节/节点)

与 unordered_map 对比:
  · map 查找:O(log n) vs O(1) 平均
  · map 内存:更少(无桶数组)
  · map 迭代:有序 vs 无序

延伸阅读

基于 MIT 许可发布