C++进阶:(八)基于红黑树泛型封装实现 map 与 set 容器

目录

前言

[一、STL 源码框架分析](#一、STL 源码框架分析)

[1.1 核心头文件依赖关系](#1.1 核心头文件依赖关系)

[1.2 关键类模板定义](#1.2 关键类模板定义)

[1.2.1 set 的类模板结构](#1.2.1 set 的类模板结构)

[1.2.2 map 的类模板结构](#1.2.2 map 的类模板结构)

[1.2.3 红黑树(rb_tree)的核心模板参数](#1.2.3 红黑树(rb_tree)的核心模板参数)

[1.3 核心设计思想:泛型复用](#1.3 核心设计思想:泛型复用)

[1.3.1 节点存储类型的灵活控制](#1.3.1 节点存储类型的灵活控制)

[1.3.2 key 提取机制:KeyOfValue 仿函数](#1.3.2 key 提取机制:KeyOfValue 仿函数)

[1.3.3 模板参数 Key 的作用](#1.3.3 模板参数 Key 的作用)

[1.4 关于源码设计的小吐槽](#1.4 关于源码设计的小吐槽)

二、模拟实现的核心思路

[2.1 关键调整与优化](#2.1 关键调整与优化)

三、完整代码实现

[3.1 红黑树的实现(RBTree.h)](#3.1 红黑树的实现(RBTree.h))

[3.2 set 的实现(Myset.h)](#3.2 set 的实现(Myset.h))

[3.3 map 的实现(Mymap.h)](#3.3 map 的实现(Mymap.h))

四、核心细节解析

[4.1 红黑树的平衡规则与调整](#4.1 红黑树的平衡规则与调整)

[4.2 迭代器的实现原理](#4.2 迭代器的实现原理)

[4.2.1 operator++(中序下一个节点)](#4.2.1 operator++(中序下一个节点))

[4.2.2 operator--(中序前一个节点)](#4.2.2 operator--(中序前一个节点))

[4.3 key 不可修改的实现](#4.3 key 不可修改的实现)

[4.4 map 的 operator [] 实现](#4.4 map 的 operator [] 实现)

总结


前言

在 C++ STL 中,map 和 set 是常用的关联式容器,它们的底层实现都依赖于红黑树这一高效的数据结构。红黑树作为一种自平衡二叉搜索树,既保证了查找、插入、删除操作的时间复杂度为 O (log n),又通过特定的规则维持树的平衡,避免了普通二叉搜索树在极端情况下退化为链表的问题。本文将从 STL 源码框架分析入手,详细讲解如何基于红黑树封装实现 map 和 set 容器,帮助大家深入理解关联式容器的底层机制。下面就让我们正式开始吧!


一、STL 源码框架分析

要实现 map 和 set,首先需要理解 STL 中 map 和 set 的底层实现逻辑。SGI-STL3.0 版本中,map 和 set 的源代码主要分布在stl_map.hstl_set.hstl_tree.h三个头文件中,其核心设计思想是通过红黑树的泛型实现,同时支撑 map(key-value 映射)和 set(key 集合)两种场景

1.1 核心头文件依赖关系

STL 中 map 和 set 的实现高度依赖红黑树(rb_tree),其头文件包含关系如下:

  • set 的实现依赖stl_tree.hstl_set.hstl_multiset.h
  • map 的实现依赖stl_tree.hstl_map.hstl_multimap.h

这种依赖关系的本质是:set 和 map 本身不直接实现数据结构,而是通过封装红黑树,对外提供特定的接口,形成符合自身语义的容器。

1.2 关键类模板定义

1.2.1 set 的类模板结构

cpp 复制代码
template <class Key, class Compare = less<Key>, class Alloc = alloc>
class set {
public:
    typedef Key key_type;
    typedef Key value_type;  // set的value_type就是key本身
private:
    // 红黑树类型定义,作为set的内部存储容器
    typedef rb_tree<key_type, value_type, identity<value_type>, key_compare, Alloc> rep_type;
    rep_type t;  // 红黑树对象,真正存储数据
};

1.2.2 map 的类模板结构

cpp 复制代码
template <class Key, class T, class Compare = less<Key>, class Alloc = alloc>
class map {
public:
    typedef Key key_type;
    typedef T mapped_type;  // map的value(映射值)类型
    typedef pair<const Key, T> value_type;  // 红黑树存储的元素类型(key-value对)
private:
    // 红黑树类型定义,作为map的内部存储容器
    typedef rb_tree<key_type, value_type, select1st<value_type>, key_compare, Alloc> rep_type;
    rep_type t;  // 红黑树对象,真正存储数据
};

1.2.3 红黑树(rb_tree)的核心模板参数

红黑树的类模板定义是实现 map 和 set 复用的关键,其核心模板参数如下:

cpp 复制代码
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:
    typedef __rb_tree_node<Value> rb_tree_node;  // 红黑树节点类型
    typedef rb_tree_node* link_type;
    size_type node_count;  // 树中节点个数
    link_type header;      // 头节点(哨兵节点)
public:
    // 插入唯一元素(不允许重复key)
    pair<iterator, bool> insert_unique(const value_type& x);
    // 根据key删除元素
    size_type erase(const key_type& x);
    // 根据key查找元素
    iterator find(const key_type& x);
};

1.3 核心设计思想:泛型复用

STL 通过红黑树的泛型设计,实现了 map 和 set 的复用,其关键在于以下两点:

1.3.1 节点存储类型的灵活控制

红黑树的第二个模板参数**Value指定了节点中存储的数据类型**:

  • set 实例化红黑树时,Value传入Key(即 set 的 value_type就是 key),因此红黑树节点存储的是单个 key 值
  • map 实例化红黑树时,Value传入pair<const Key, T>,因此红黑树节点存储的是key-value 键值对

通过这种方式,一颗红黑树既可以作为 set 的底层存储(存储单个 key),也可以作为 map 的底层存储(存储 key-value 对),实现了数据结构的复用。

1.3.2 key 提取机制:KeyOfValue 仿函数

红黑树的查找、插入、删除操作都需要基于 key 进行比较 ,但节点存储的是Value类型 (可能是 key,也可能是 key-value 对)。为了从Value中提取出 key,红黑树引入了第三个模板参数**KeyOfValue------ 一个仿函数** ,用于从Value对象中获取 key 值。

  • set 对应的KeyOfValueidentity<value_type>,直接返回Value本身 (因为 set 的Value就是 key)。
  • map 对应的KeyOfValueselect1st<value_type>返回pair<const Key, T>中的第一个元素(即 key)

1.3.3 模板参数 Key 的作用

红黑树的第一个模板参数**Key查找、删除操作的参数类型**:

  • 对于 set,KeyValue类型相同,查找(find)、删除(erase)时直接传入 key 即可。
  • 对于 map,Key是 key 的类型,而**Valuepair<const Key, T>**,查找、删除时传入 key,红黑树通过KeyOfValue仿函数从节点的Value中提取 key 进行比较。

这一设计解决了 map 中 "插入元素是 key-value 对,而查找删除是 key" 的场景差异。

下为对STL源码的框图分析:

1.4 关于源码设计的小吐槽

STL 源码的模板参数命名风格其实是并不完全统一的:

  • set 的模板参数用Key命名;
  • map 的模板参数用KeyT(映射值类型)命名;
  • 红黑树的模板参数用KeyValue命名。

这种命名差异可能会给初学者带来困惑,但核心逻辑其实是一致的 ------ 通过泛型参数的灵活配置,实现数据结构的复用。

二、模拟实现的核心思路

基于 STL 的设计思想,我们将来模拟实现 map 和 set,核心步骤如下:

  1. 实现一个通用的红黑树类(RBTree),支持泛型参数配置。
  2. 在 map 和 set 中封装红黑树 ,通过自定义仿函数(KeyOfT)实现 key 的提取
  3. 实现迭代器(iterator) ,支持中序遍历(保证 map 和 set 的有序性)。
  4. 解决 key 不可修改的问题(set 的 key 完全不可改,map 的 key 不可改但 value 可改)。
  5. 实现 map 的**operator[]**运算符,支持便捷的 key-value 访问和插入。

2.1 关键调整与优化

为了简化实现并提高可读性,我们可以对 STL 源码的命名和结构做出如下调整:

  • 红黑树的模板参数统一命名为**K(key 类型)、T(节点存储数据类型)、KeyOfT(key 提取仿函数)**。
  • 去掉 STL 中复杂的哨兵节点(header),用nullptr表示迭代器的end()
  • 红黑树的节点结构直接明确定义,避免源码中嵌套的结构体依赖。

三、完整代码实现

3.1 红黑树的实现(RBTree.h)

红黑树是 map 和 set 的底层核心,需要实现节点结构、插入(含平衡调整)、旋转、迭代器等核心功能。由于在之前的博客中已经为大家介绍过了红黑树的实现,这里就不再过多赘述了,感兴趣的话可以参考一下之前的博客:C++进阶:(七)红黑树深度解析与 C++ 实现

cpp 复制代码
#include <iostream>
#include <utility>
using namespace std;

// 红黑树节点颜色枚举
enum Colour {
    RED,    // 红色节点
    BLACK   // 黑色节点
};

// 红黑树节点结构
template <class T>
struct RBTreeNode {
    T _data;                  // 节点存储的数据(set中是key,map中是pair<const K, V>)
    RBTreeNode<T>* _left;     // 左孩子
    RBTreeNode<T>* _right;    // 右孩子
    RBTreeNode<T>* _parent;   // 父节点
    Colour _col;              // 节点颜色

    // 构造函数
    RBTreeNode(const T& data)
        : _data(data)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _col(RED)  // 新节点默认红色(减少平衡调整次数)
    {}
};

// 红黑树迭代器
template <class T, class Ref, class Ptr>
struct RBTreeIterator {
    typedef RBTreeNode<T> Node;
    typedef RBTreeIterator<T, Ref, Ptr> Self;

    Node* _node;  // 当前迭代器指向的节点
    Node* _root;  // 红黑树根节点(用于--end()时查找最右节点)

    // 构造函数
    RBTreeIterator(Node* node, Node* root)
        : _node(node)
        , _root(root)
    {}

    // 前置++:迭代器指向中序遍历的下一个节点
    Self& operator++() {
        if (_node->_right) {
            // 情况1:右子树不为空,下一个节点是右子树的最左节点
            Node* leftMost = _node->_right;
            while (leftMost->_left) {
                leftMost = leftMost->_left;
            }
            _node = leftMost;
        } else {
            // 情况2:右子树为空,向上找第一个"当前节点是父节点左孩子"的祖先
            Node* cur = _node;
            Node* parent = cur->_parent;
            while (parent && cur == parent->_right) {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;  // 找到祖先或nullptr(end())
        }
        return *this;
    }

    // 前置--:迭代器指向中序遍历的前一个节点
    Self& operator--() {
        if (_node == nullptr) {
            // 情况1:当前是end(),--后指向最右节点(中序最后一个节点)
            Node* rightMost = _root;
            while (rightMost && rightMost->_right) {
                rightMost = rightMost->_right;
            }
            _node = rightMost;
        } else if (_node->_left) {
            // 情况2:左子树不为空,前一个节点是左子树的最右节点
            Node* rightMost = _node->_left;
            while (rightMost->_right) {
                rightMost = rightMost->_right;
            }
            _node = rightMost;
        } else {
            // 情况3:左子树为空,向上找第一个"当前节点是父节点右孩子"的祖先
            Node* cur = _node;
            Node* parent = cur->_parent;
            while (parent && cur == parent->_left) {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;
        }
        return *this;
    }

    // 解引用运算符:返回节点数据的引用
    Ref operator*() {
        return _node->_data;
    }

    // 箭头运算符:返回节点数据的指针
    Ptr operator->() {
        return &_node->_data;
    }

    // 相等运算符
    bool operator==(const Self& s) const {
        return _node == s._node;
    }

    // 不相等运算符
    bool operator!=(const Self& s) const {
        return _node != s._node;
    }
};

// 红黑树类模板
template <class K, class T, class KeyOfT>
class RBTree {
    typedef RBTreeNode<T> Node;

public:
    typedef RBTreeIterator<T, T&, T*> Iterator;         // 普通迭代器
    typedef RBTreeIterator<T, const T&, const T*> ConstIterator;  // 常量迭代器

    // 构造函数
    RBTree() : _root(nullptr) {}

    // 析构函数
    ~RBTree() {
        Destroy(_root);
        _root = nullptr;
    }

    // 迭代器 begin():指向中序遍历的第一个节点(最左节点)
    Iterator Begin() {
        Node* leftMost = _root;
        while (leftMost && leftMost->_left) {
            leftMost = leftMost->_left;
        }
        return Iterator(leftMost, _root);
    }

    // 常量迭代器 begin()
    ConstIterator Begin() const {
        Node* leftMost = _root;
        while (leftMost && leftMost->_left) {
            leftMost = leftMost->_left;
        }
        return ConstIterator(leftMost, _root);
    }

    // 迭代器 end():指向nullptr(中序遍历的末尾)
    Iterator End() {
        return Iterator(nullptr, _root);
    }

    // 常量迭代器 end()
    ConstIterator End() const {
        return ConstIterator(nullptr, _root);
    }

    // 插入元素:返回pair<迭代器, bool>,bool表示是否插入成功(避免重复key)
    pair<Iterator, bool> Insert(const T& data) {
        // 情况1:树为空,直接创建根节点(根节点为黑色)
        if (_root == nullptr) {
            _root = new Node(data);
            _root->_col = BLACK;
            return make_pair(Iterator(_root, _root), true);
        }

        KeyOfT kot;  // key提取仿函数
        Node* parent = nullptr;
        Node* cur = _root;

        // 查找插入位置
        while (cur) {
            if (kot(cur->_data) < kot(data)) {
                parent = cur;
                cur = cur->_right;
            } else if (kot(cur->_data) > kot(data)) {
                parent = cur;
                cur = cur->_left;
            } else {
                // 存在重复key,插入失败
                return make_pair(Iterator(cur, _root), false);
            }
        }

        // 创建新节点(默认红色),并链接到父节点
        cur = new Node(data);
        Node* newNode = cur;
        cur->_col = RED;
        if (kot(parent->_data) < kot(data)) {
            parent->_right = cur;
        } else {
            parent->_left = cur;
        }
        cur->_parent = parent;

        // 平衡调整:父节点为红色时需要调整(红黑树不能有连续红色节点)
        while (parent && parent->_col == RED) {
            Node* grandfather = parent->_parent;  // 祖父节点(一定存在,因为根节点是黑色)

            // 情况1:父节点是祖父节点的左孩子
            if (parent == grandfather->_left) {
                Node* uncle = grandfather->_right;  // 叔叔节点

                // 子情况1.1:叔叔节点存在且为红色(变色调整)
                if (uncle && uncle->_col == RED) {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 向上继续调整(祖父节点变为红色,可能与曾祖父节点冲突)
                    cur = grandfather;
                    parent = cur->_parent;
                } else {
                    // 子情况1.2:叔叔节点不存在或为黑色(旋转+变色调整)
                    // 子情况1.2.1:当前节点是父节点的左孩子(右单旋)
                    if (cur == parent->_left) {
                        RotateR(grandfather);  // 对祖父节点右旋
                        parent->_col = BLACK;   // 父节点变黑
                        grandfather->_col = RED;  // 祖父节点变红
                    } else {
                        // 子情况1.2.2:当前节点是父节点的右孩子(左旋+右旋)
                        RotateL(parent);        // 对父节点左旋
                        RotateR(grandfather);  // 对祖父节点右旋
                        cur->_col = BLACK;      // 当前节点变黑
                        grandfather->_col = RED;  // 祖父节点变红
                    }
                    break;  // 调整完成,跳出循环
                }
            } else {
                // 情况2:父节点是祖父节点的右孩子(与情况1对称)
                Node* uncle = grandfather->_left;  // 叔叔节点

                // 子情况2.1:叔叔节点存在且为红色(变色调整)
                if (uncle && uncle->_col == RED) {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 向上继续调整
                    cur = grandfather;
                    parent = cur->_parent;
                } else {
                    // 子情况2.2:叔叔节点不存在或为黑色(旋转+变色调整)
                    // 子情况2.2.1:当前节点是父节点的右孩子(左单旋)
                    if (cur == parent->_right) {
                        RotateL(grandfather);  // 对祖父节点左旋
                        parent->_col = BLACK;   // 父节点变黑
                        grandfather->_col = RED;  // 祖父节点变红
                    } else {
                        // 子情况2.2.2:当前节点是父节点的左孩子(右旋+左旋)
                        RotateR(parent);        // 对父节点右旋
                        RotateL(grandfather);  // 对祖父节点左旋
                        cur->_col = BLACK;      // 当前节点变黑
                        grandfather->_col = RED;  // 祖父节点变红
                    }
                    break;  // 调整完成,跳出循环
                }
            }
        }

        // 确保根节点始终为黑色
        _root->_col = BLACK;
        return make_pair(Iterator(newNode, _root), true);
    }

    // 根据key查找元素:返回迭代器
    Iterator Find(const K& key) {
        Node* cur = _root;
        KeyOfT kot;
        while (cur) {
            if (kot(cur->_data) < key) {
                cur = cur->_right;
            } else if (kot(cur->_data) > key) {
                cur = cur->_left;
            } else {
                // 找到key,返回对应的迭代器
                return Iterator(cur, _root);
            }
        }
        // 未找到,返回end()
        return End();
    }

private:
    // 左旋转:以parent为旋转中心
    void RotateL(Node* parent) {
        Node* subR = parent->_right;    // 父节点的右孩子
        Node* subRL = subR->_left;      // 右孩子的左子树

        // 步骤1:将subRL链接到parent的右孩子
        parent->_right = subRL;
        if (subRL) {
            subRL->_parent = parent;
        }

        // 步骤2:处理parent的父节点
        Node* parentParent = parent->_parent;
        subR->_left = parent;
        parent->_parent = subR;

        // 步骤3:如果parent是根节点,更新根;否则链接到祖父节点
        if (parentParent == nullptr) {
            _root = subR;
            subR->_parent = nullptr;
        } else {
            if (parent == parentParent->_left) {
                parentParent->_left = subR;
            } else {
                parentParent->_right = subR;
            }
            subR->_parent = parentParent;
        }
    }

    // 右旋转:以parent为旋转中心
    void RotateR(Node* parent) {
        Node* subL = parent->_left;     // 父节点的左孩子
        Node* subLR = subL->_right;     // 左孩子的右子树

        // 步骤1:将subLR链接到parent的左孩子
        parent->_left = subLR;
        if (subLR) {
            subLR->_parent = parent;
        }

        // 步骤2:处理parent的父节点
        Node* parentParent = parent->_parent;
        subL->_right = parent;
        parent->_parent = subL;

        // 步骤3:如果parent是根节点,更新根;否则链接到祖父节点
        if (parentParent == nullptr) {
            _root = subL;
            subL->_parent = nullptr;
        } else {
            if (parent == parentParent->_left) {
                parentParent->_left = subL;
            } else {
                parentParent->_right = subL;
            }
            subL->_parent = parentParent;
        }
    }

    // 递归销毁红黑树
    void Destroy(Node* root) {
        if (root == nullptr) {
            return;
        }
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }

private:
    Node* _root;  // 红黑树根节点
};

3.2 set 的实现(Myset.h)

set 是 key 的集合,其特点是 key 唯一且有序,key 不可修改。通过封装红黑树,并重定义KeyOfT仿函数实现。代码如下:

cpp 复制代码
#include "RBTree.h"
#include <iostream>
using namespace std;

namespace bit {
    template <class K>
    class set {
        // 自定义KeyOfT仿函数:从节点数据(K类型)中提取key
        struct SetKeyOfT {
            const K& operator()(const K& key) {
                return key;
            }
        };

    public:
        // 迭代器类型定义(复用红黑树的迭代器)
        typedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;
        typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;

        // 普通迭代器 begin()
        iterator begin() {
            return _t.Begin();
        }

        // 普通迭代器 end()
        iterator end() {
            return _t.End();
        }

        // 常量迭代器 begin()
        const_iterator begin() const {
            return _t.Begin();
        }

        // 常量迭代器 end()
        const_iterator end() const {
            return _t.End();
        }

        // 插入元素:返回pair<iterator, bool>,bool表示是否插入成功
        pair<iterator, bool> insert(const K& key) {
            return _t.Insert(key);
        }

        // 根据key查找元素
        iterator find(const K& key) {
            return _t.Find(key);
        }

    private:
        // 红黑树对象:第二个模板参数为const K,确保key不可修改
        RBTree<K, const K, SetKeyOfT> _t;
    };

    // 测试set
    void test_set() {
        bit::set<int> s;
        int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
        for (auto e : a) {
            auto ret = s.insert(e);
            if (ret.second) {
                cout << "插入成功:" << e << endl;
            } else {
                cout << "插入失败(重复key):" << e << endl;
            }
        }

        // 遍历set(中序遍历,有序)
        cout << "set遍历(有序):";
        for (auto e : s) {
            cout << e << " ";
        }
        cout << endl;

        // 测试find
        auto it = s.find(5);
        if (it != s.end()) {
            cout << "找到key:" << *it << endl;
        } else {
            cout << "未找到key:5" << endl;
        }

        // 测试key不可修改(编译报错)
        // *it = 100;  // 错误:set的key是const类型,不可修改
    }
}

3.3 map 的实现(Mymap.h)

map 是 key-value 的映射容器,其特点是 key 唯一且有序,key 不可修改但 value 可修改。同样通过封装红黑树实现,核心是KeyOfT仿函数和operator[]的实现。

cpp 复制代码
#include "RBTree.h"
#include <iostream>
#include <string>
using namespace std;

namespace bit {
    template <class K, class V>
    class map {
        // 自定义KeyOfT仿函数:从pair<const K, V>中提取key(first成员)
        struct MapKeyOfT {
            const K& operator()(const pair<const K, V>& kv) {
                return kv.first;
            }
        };

    public:
        // 迭代器类型定义(复用红黑树的迭代器)
        typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;
        typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::ConstIterator const_iterator;

        // 普通迭代器 begin()
        iterator begin() {
            return _t.Begin();
        }

        // 普通迭代器 end()
        iterator end() {
            return _t.End();
        }

        // 常量迭代器 begin()
        const_iterator begin() const {
            return _t.Begin();
        }

        // 常量迭代器 end()
        const_iterator end() const {
            return _t.End();
        }

        // 插入元素:返回pair<iterator, bool>,bool表示是否插入成功
        pair<iterator, bool> insert(const pair<K, V>& kv) {
            return _t.Insert(kv);
        }

        // 根据key查找元素
        iterator find(const K& key) {
            return _t.Find(key);
        }

        // operator[]:支持map[key]访问和插入
        V& operator[](const K& key) {
            // 插入key-value对(value为默认构造),返回pair<iterator, bool>
            pair<iterator, bool> ret = insert(make_pair(key, V()));
            // 返回value的引用(可修改)
            return ret.first->second;
        }

    private:
        // 红黑树对象:节点存储pair<const K, V>,确保key不可修改
        RBTree<K, pair<const K, V>, MapKeyOfT> _t;
    };

    // 测试map
    void test_map() {
        bit::map<string, string> dict;
        // 插入元素
        dict.insert({"sort", "排序"});
        dict.insert({"left", "左边"});
        dict.insert({"right", "右边"});

        // 使用operator[]修改value
        dict["left"] = "左边(剩余)";
        // 使用operator[]插入新元素(key不存在时自动插入)
        dict["insert"] = "插入";
        dict["string"];  // value为默认构造(空字符串)

        // 遍历map(中序遍历,按key有序)
        cout << "map遍历(按key有序):" << endl;
        for (auto it = dict.begin(); it != dict.end(); ++it) {
            // 测试key不可修改(编译报错)
            // it->first += 'x';  // 错误:key是const类型
            // 修改value
            it->second += "x";
            cout << it->first << " : " << it->second << endl;
        }

        // 测试find
        auto it = dict.find("insert");
        if (it != dict.end()) {
            cout << "找到key:" << it->first << ",value:" << it->second << endl;
        } else {
            cout << "未找到key:insert" << endl;
        }
    }
}

四、核心细节解析

4.1 红黑树的平衡规则与调整

红黑树的核心是通过以下规则维持平衡的,确保所有操作的时间复杂度为 O (log n)

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点是黑色。
  3. 所有叶子节点(NIL 节点)是黑色。
  4. 如果一个节点是红色,那么它的两个子节点都是黑色(无连续红色节点)。
  5. 从任意节点到其所有叶子节点的路径上,黑色节点的数量相同(黑高相同)。

当插入新节点(默认红色)时,可能会破坏规则 4,因此需要通过变色旋转 + 变色进行调整。调整的核心思路是:

  • 如果叔叔节点是红色:将父节点、叔叔节点变为黑色,祖父节点变为红色,向上继续调整。
  • 如果叔叔节点是黑色或不存在:通过旋转(左旋、右旋、双旋)调整树的结构,再变色维持规则。

4.2 迭代器的实现原理

map 和 set 的迭代器需要支持中序遍历(保证有序性),其核心是**operator++operator--**的实现:

4.2.1 operator++(中序下一个节点)

  1. 如果当前节点的右子树不为空:下一个节点是右子树的最左节点(中序遍历右子树的第一个节点)。
  2. 如果当前节点的右子树为空:向上查找第一个 "当前节点是父节点左孩子" 的祖先(该祖先即为下一个节点)。

4.2.2 operator--(中序前一个节点)

  1. 如果当前是end()(nullptr):前一个节点是树的最右节点(中序遍历的最后一个节点)。
  2. 如果当前节点的左子树不为空:前一个节点是左子树的最右节点(中序遍历左子树的最后一个节点)。
  3. 如果当前节点的左子树为空:向上查找第一个 "当前节点是父节点右孩子" 的祖先(该祖先即为前一个节点)。

4.3 key 不可修改的实现

  • set 的 key 不可修改:set 封装红黑树时,将红黑树的节点存储类型设为const K,因此迭代器解引用后得到的是const K&,无法修改。
  • map 的 key 不可修改:map 的节点存储类型是pair<const K, V>,其中 key 被定义为const,因此迭代器访问it->first时是const K&,无法修改;而it->secondV&,可以修改。

4.4 map 的 operator [] 实现

map 的**operator[]**是最常用的接口,其实现逻辑如下:

cpp 复制代码
V& operator[](const K& key) {
    pair<iterator, bool> ret = insert(make_pair(key, V()));
    return ret.first->second;
}
  • 调用insert插入**(key, V())**(value 为默认构造)。
  • 如果 key 已存在,insert返回已存在节点的迭代器和false,直接返回该节点的 value 引用。
  • 如果 key 不存在,insert插入新节点并返回新节点的迭代器和true,返回新节点的 value 引用。

这种实现支持三种用法:

  1. 访问已存在的 key:dict["left"]
  2. 修改已存在的 key 的 value:dict["left"] = "左边(剩余)"
  3. 插入新的 key-value 对:dict["insert"] = "插入"

总结

通过本文的实现,希望大家可以深入理解 map 和 set 的底层机制,掌握红黑树的核心原理和泛型编程的思想,为后续学习更复杂的容器和数据结构打下坚实基础。

相关推荐
xlq2232213 分钟前
22.多态(上)
开发语言·c++·算法
666HZ66615 分钟前
C语言——高精度加法
c语言·开发语言·算法
星释22 分钟前
Rust 练习册 100:音乐音阶生成器
开发语言·后端·rust
D_evil__1 小时前
[C++高频精进] 并发编程:线程基础
c++
风生u1 小时前
go进阶语法
开发语言·后端·golang
666HZ6661 小时前
C语言——黑店
c语言·开发语言
Gomiko1 小时前
JavaScript基础(八):函数
开发语言·javascript·ecmascript
〝七夜5691 小时前
JVM内存结构
java·开发语言·jvm
初级炼丹师(爱说实话版)1 小时前
JAVA泛型作用域与静态方法泛型使用笔记
java·开发语言·笔记
Mr_WangAndy1 小时前
C++17 新特性_第二章 C++17 语言特性_std::any和string_view
c++·string_view·c++40周年·c++17新特性·c++新特性any