撕 STL 系列:封装红黑树实现 mymap 和 myset

在 STL 容器中,mapset是高频使用的关联式容器,其底层均基于红黑树 实现 ------ 一种自平衡的二叉搜索树,能保证增删查改的时间复杂度为O(logN)。本文将从 SGI-STL 源码框架出发,讲解如何通过泛型思想复用红黑树,分别实现自定义的mymapmyset,核心掌握红黑树的泛型封装KeyOfT 仿函数设计迭代器实现 以及mapoperator[]重载等关键技术点。

一、核心设计思路:红黑树的泛型复用

SGI-STL 中mapset的核心设计精髓是复用同一棵红黑树,通过模板参数和仿函数区分两者的存储逻辑,避免代码冗余。

1.1 map 与 set 的本质区别

  • set:仅存储键值(Key),且键值唯一,不允许修改,支持基于 Key 的查找、插入、删除;
  • map:存储键值对(pair<const Key, T>) ,Key 唯一且不可修改,T 为映射值可修改,支持通过 Key 访问映射值(如operator[])。

1.2 红黑树的泛型适配

红黑树本身不关心存储的是单一 Key 还是键值对,只需通过三个核心模板参数实现泛型适配:

cpp 复制代码
template<class K, class T, class KeyOfT>
class RBTree;
  • K:键的类型,为find/erase等操作提供参数类型;
  • T:红黑树节点实际存储的数据类型(set中 T=K,map中 T=pair<const K, V>);
  • KeyOfT键提取仿函数,用于从 T 类型中提取 Key 进行比较,解决红黑树节点数据比较的统一化问题。

1.3 KeyOfT 仿函数的核心作用

由于setmap的存储类型 T 不同,直接比较 T 无法满足红黑树的二叉搜索特性(如map的 pair 默认比较会同时对比 Key 和 Value),因此需要为setmap分别实现 KeyOfT 仿函数,统一从 T 中提取 Key 进行比较

  • SetKeyOfT:从 K 类型中直接返回自身(因为 set 的 T=K);
  • MapKeyOfT:从 pair<const K, V> 中返回 first(即 Key)。

一、实现 mymap 和 myset

基于上述泛型红黑树,分别封装mysetmymap,核心是实现各自的 KeyOfT 仿函数,并复用红黑树的所有接口。

5.1 封装 myset

myset的存储类型为 K,KeyOfT 仿函数直接返回 K 本身,且set的迭代器为 const 迭代器(键不可修改)。

复制代码
namespace bit
{
    // 自定义set
    template<class K>
    class set
    {
        // set的键提取仿函数:T=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;

        // 迭代器接口
        iterator begin() { return _t.Begin(); }
        iterator end() { return _t.End(); }
        const_iterator begin() const { return _t.Begin(); }
        const_iterator end() const { return _t.End(); }

        // 插入:返回pair<iterator, bool>
        pair<iterator, bool> insert(const K& key)
        {
            return _t.Insert(key);
        }

        // 查找:根据Key查找
        iterator find(const K& key)
        {
            return _t.Find(key);
        }
    private:
        // 红黑树:K=键类型,T=const K(不可修改),KeyOfT=SetKeyOfT
        RBTree<K, const K, SetKeyOfT> _t;
    };
}

5.2 封装 mymap

mymap的存储类型为pair<const K, V>,KeyOfT 仿函数返回 pair 的 first;核心实现operator[],支持通过 Key 直接访问 / 修改映射值。

复制代码
namespace bit
{
    // 自定义map
    template<class K, class V>
    class map
    {
        // map的键提取仿函数:T=pair<const K,V>,返回first(Key)
        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;

        // 迭代器接口
        iterator begin() { return _t.Begin(); }
        iterator end() { return _t.End(); }
        const_iterator begin() const { return _t.Begin(); }
        const_iterator end() const { return _t.End(); }

        // 插入:返回pair<iterator, bool>
        pair<iterator, bool> insert(const pair<const K, V>& kv)
        {
            return _t.Insert(kv);
        }

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

        // 核心:operator[]重载,支持map[key] = value
        V& operator[](const K& key)
        {
            // 插入键值对,若Key已存在则返回原有迭代器
            pair<iterator, bool> ret = insert(make_pair(key, V()));
            // 返回映射值的引用,支持修改
            return ret.first->second;
        }
    private:
        // 红黑树:K=键类型,T=pair<const K,V>,KeyOfT=MapKeyOfT
        RBTree<K, pair<const K, V>, MapKeyOfT> _t;
    };
}
map::operator[]的核心原理

operator[]的本质是插入 + 返回引用,一行代码实现:

复制代码
V& operator[](const K& key)
{
    pair<iterator, bool> ret = insert(make_pair(key, V()));
    return ret.first->second;
}
  • 若 Key 不存在:插入pair(key, V())(V () 为默认构造),返回新插入的映射值引用,支持赋值(如map["a"] = 1);
  • 若 Key 已存在:插入失败,返回原有映射值的引用,支持修改(如map["a"] = 2)。

六、测试:验证 mymap 和 myset 的功能

编写测试代码,验证myset的有序插入、查找,以及mymapinsertoperator[]、迭代器遍历等功能。

6.1 测试 myset

复制代码
void test_set()
{
    bit::set<int> s;
    int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };
    // 插入数据
    for (auto e : a)
    {
        s.insert(e);
    }
    // 中序遍历:有序输出
    for (auto e : s)
    {
        cout << e << " ";
    }
    cout << endl; // 输出:1 2 3 4 5 6 7 14 15 16

    // 查找
    auto it = s.find(6);
    if (it != s.end())
    {
        cout << "找到:" << *it << endl; // 输出:找到:6
    }
}

6.2 测试 mymap

复制代码
void test_map()
{
    bit::map<string, string> dict;
    // 插入键值对
    dict.insert(make_pair("sort", "排序"));
    dict.insert(make_pair("left", "左边"));
    dict.insert(make_pair("right", "右边"));

    // operator[]修改已有值
    dict["left"] = "左边,左侧";
    // operator[]插入新值
    dict["insert"] = "插入";
    dict["string"]; // 插入默认值:string("")

    // 遍历map:有序输出Key
    for (auto& kv : dict)
    {
        cout << kv.first << " : " << kv.second << endl;
    }
    // 查找
    auto it = dict.find("sort");
    if (it != dict.end())
    {
        cout << "找到:" << it->first << " : " << it->second << endl;
    }
}

6.3 主函数调用

复制代码
int main()
{
    cout << "测试myset:" << endl;
    test_set();
    cout << endl << "测试mymap:" << endl;
    test_map();
    return 0;
}

编译与运行

编译时直接使用 g++,无需额外链接库:

复制代码
g++ mymap_set.cpp -o mymap_set
./mymap_set

运行结果符合预期,set输出有序序列,map支持operator[]的插入和修改,迭代器遍历有序。

七、核心总结

本文通过复刻 SGI-STL 的设计思路,实现了基于红黑树的mymapmyset,核心知识点梳理如下:

  1. 泛型复用 :红黑树通过模板参数KTKeyOfT实现泛型,适配set(T=K)和map(T=pair<const K,V>)的不同存储需求;
  2. KeyOfT 仿函数:解决红黑树节点数据的统一比较问题,从 T 中提取 Key 进行二叉搜索,是泛型复用的关键;
  3. 双向迭代器 :基于红黑树的中序遍历实现,++--操作是难点,需处理右子树 / 左子树存在和不存在的情况,以及end()的特殊处理;
  4. 红黑树自平衡 :新节点默认红色,通过变色旋转 + 变色处理连续红节点,保证根节点始终为黑色;
  5. map::operator[] :本质是insert操作的封装,实现「插入 + 返回引用」,支持一键式访问 / 修改映射值。

通过本次实现,不仅能深入理解mapset的底层原理,还能掌握泛型编程仿函数设计迭代器封装等 C++ 核心技术,为手撕 STL 容器打下坚实基础。

相关推荐
xh didida2 小时前
数据结构--实现链式结构二叉树
c语言·数据结构·算法
ab1515172 小时前
3.15二刷基础90、105、106、110
数据结构·c++·算法
C蔡博士2 小时前
最近点对问题(Closest Pair of Points)
java·python·算法
APIshop2 小时前
Java调用亚马逊商品详情API接口完全指南
java·开发语言·python
白太岁2 小时前
算法:链表:指针变化与环
数据结构·算法·链表
寻寻觅觅☆2 小时前
东华OJ-进阶题-10-分解质因数(C++)
数据结构·c++·算法
不光头强2 小时前
jwt学习
java·大数据·学习
Darkwanderor2 小时前
数据结构——ST表和RMQ问题
数据结构·c++·动态规划·st表·rmq问题
凸头2 小时前
美团Leaf发号器
java