【C++:封装红黑树】C++红黑树封装实战:从零实现MyMap与MySet

【C++:封装红黑树】C++红黑树封装实战:从零实现MyMap与MySet

🔥艾莉丝努力练剑:个人主页

专栏传送门:《C语言》《数据结构与算法》C/C++干货分享&学习过程记录Linux操作系统编程详解笔试/面试常见算法:从基础到进阶测试开发要点全知道

⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平


🎬艾莉丝的简介:


🎬艾莉丝的C++专栏简介:


目录

C++的两个参考文档

1  ~>  分析:源码及框架

1.1  见一见源码

1.2  对比set和map的源码:泛型编程的应用

2  ~>  map和set的模拟实现

2.1  实现出复用红黑树的框架(支持insert)

2.2  迭代器iterator的实现

2.3  迭代器iterator实现思路分析

2.4  map支持[]

bit::map和bit::set完整代码示例与实践演示

RBTree.h:

Map.h:

Set.h:

Test.cpp:

运行结果

结尾


C++的两个参考文档

老朋友(非官方文档):cplusplus

官方文档(同步更新): cppreference
set和multiset的参考文档:setmultiset

map和multimap的参考文档:mapmultimap


1  ~>  分析:源码及框架

大伙都知道封装红黑树这一块难度那是嘎嘎高的,这个难度其实不在于逻辑,而在于结构。

1.1  见一见源码

还是那句话,虽然没吃过猪肉但我们可以见见过猪跑呀。艾莉丝又来带大家看源码啦——

SGI—STL30版本源代码,map和set的源代码在map / set / stl_map.h / stl_set.h / stl_tree.h等几个头文件中。

源码大家不要去看最新的,最新的源码有的经过十几年的优化,看起来很费劲,我们挑中间的看。

stl_tree.h:

stl_set.h:

stl_map.h:

1.2  对比set和map的源码:泛型编程的应用

虽然底层都是用红黑树实现的,这里我们看源码,第一个模板参数都是Key,区别就在于第二个模板参数,value对于set是key,对map不是——

map和set不是同一棵红黑树实现的,这里其实是用红黑树类模板实现的。

我们通过上面的框架可以看到源码中的rb_tree实现了一个非常巧妙的泛型思想,rb_tree不管是实现Key的搜索场景还是Key / value的搜索场景不是直接写死的,而是由第二个模板参数value来决定_rb_tree_node中存储的数据类型。

set实例化rb_tree的时候第二个模板参数给的是Key,map实例化rb_tree时第二个模板参数给的是pair<const key , T>,这样一个红黑树既可以实现Key搜索场景的set,也可以实现Key / value搜索场景的map。

大家注意,源码里面模板参数是用了T来代表value,而内部写的value_type不是我们日常Key / value场景中说的value,源码中的value_type反而是红黑树节点中,存储的是真实的数据类型。

rb_tree第二个模板参数value已经控制了红黑树节点存储的数据类型,为什么还要穿第一个模板参数Key呢?尤其是set,两个模板参数是一样的,这是很多uu会有的有个疑问。要注意的是对于map和set,find / erase时候的模板参数都是Key,所以第一个模板参数是传给find / erase等函数来做形参的类型的。对于set而言,两个参数是一样的;但是对于map而言就不一样了,map容器Insert的是pair对象,但是find / erase的是Key对象。


2  ~>  map和set的模拟实现

2.1  实现出复用红黑树的框架(支持insert)

参考前面的源码框架,map和set确实是复用之前我们实现的红黑树。

这里相比源码调整一下,key参数就用K,value参数就用V,红黑树中的数据类型,我们使用T。

其次因为RBTree实现了泛型不知道T参数导致是K,还是pair<K,V>,那么insert内部进行插入逻辑比较时,就没办法进行比较,因为pair的默认支持的是key和value一起参与比较,我们需要时的任何时候只比较key,所以我们在map和set层分别实现一个MapKeyOfT和SetKeyOfT的仿函数传给
RBTree的KeyOfT,然后RBTree中通过KeyOfT仿函数取出T类型对象中的key,再进行比较,具体
细节参考如下代码实现。

2.2  迭代器iterator的实现

iterator核心代码——

2.3  迭代器iterator实现思路分析

iterator实现的大框架跟list的iterator思路是一致的,用一个类型封装结点的指针,再通过重载运算符实现,迭代器有像指针一样访问的行为。

这里的难点是operator++和operator--的实现。之前在使用部分,我们分析map和set的迭代器走的是中序遍历,左子树~>根结点~>右子树,那么begin()会返回中序第一个结点的iterator也就是10所在结点的迭代器。

迭代器++的核心逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下一个结点

迭代器++时,如果it指向的结点的右子树不为空,代表当前结点已经访问完了,要访问下一个结点是右子树的中序第一个,一棵树中序第一个是最左结点,所以直接找右子树的最左结点即可。

迭代器++时,如果it指向的结点的右子树空,代表当前结点已经访问完了且当前结点所在的子树也
访问完了,要访问的下一个结点在当前结点的祖先里面,所以要沿着当前结点到根的祖先路径向上
找。

如果当前结点是父亲的左,根据中序左子树~>根结点~>右子树,那么下一个访问的结点就是当前结点的父亲;如下图——

it指向25,25右为空,25是30的左,所以下一个访问的结点就是30。

如果当前结点是父亲的右,根据中序左子树~>根结点~>右子树,当前当前结点所在的子树访问完了,当前结点所在父亲的子树也访问完了,那么下一个访问的需要继续往根的祖先中去找,直到找
到孩子是父亲左的那个祖先就是中序要问题的下一个结点。如下图——

it指向15,15右为空,15是10的右,15所在子树话访问完了,10所在子树也访问完了,继续往上找,10是18的左,那么下一个访问的结点就是18。

end()如何表示呢?如下图——

当it指向50时,++it时,50是40的右,40是30的右,30是18的右,18到根没有父亲,没有找到孩子是父亲左的那个祖先,这是父亲为空了,那我们就把it中的结点指针置为nullptr,我们用nullptr去充当end。需要注意的是stl源码空,红黑树增加了一个哨兵位头结点作为end(),这哨兵位头结点和根互为父亲,左指向最左结点,右指向最右结点。相比我们用nullptr作为end(),差别不大,他能实现的,我们也能实现。只是--end()判断到结点时空,特殊处理一下,让迭代器结点指向最右结点。具体大家可以参考迭代器--的实现。

迭代器-的实现跟++的思路完全类似,逻辑正好反过来即可,因为他访问顺序是右子树~>根结点~>
左子树,具体参考可以看艾莉丝放在文章最后的代码实现。

set的iterator也不支持修改,我们把set的第二个模板参数改成const K即可——

RBTree<K,const K,SetKeyOfT> _t;

map的iterator不支持修改key但是可以修改value,我们把map的第二个模板参数pair的第一个参
数改成const K即可——

RBTree<K,pair<constK,V>,MapKeyOfT>_t;

如下图所示,header是“哨兵位”,在学习数据结构中的二叉树时,我们就已经了解过哨兵位了——

2.4  map支持[]

1、map要支持[ ]主要需要修改insert返回值支持,修改RBtree中的insert返回值为:

pair<Iterator,bool> Insert(const T& data)

2、map支持[ ],insert支持[ ]实现就很简单了。


bit::map和bit::set完整代码示例与实践演示

RBTree.h:

template<class T, class Ref, class Ptr> struct RBTreeIterator { typedef RBTreeNode<T> Node; typedef RBTreeIterator<T, Ref, Ptr> Self; Node* _node; RBTreeIterator(Node* node) :_node(node) { } Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } Self& operator++() { if (_node->_right) { Node* minRight = _node->_right; while (minRight->_left) { minRight = minRight->_left; } _node = minRight; } else { Node* cur = _node; Node* parent = cur->_parent; while (parent && cur == parent->_right) { cur = parent; parent = parent->_parent; } _node = parent; } return *this; } bool operator!=(const Self& s) { return _node != s._node; } bool operator==(const Self& s) { return _node == s._node; } }; template<class K, class T, class KeyOfT> struct RBTree { typedef RBTreeNode<T> Node; public: typedef RBTreeIterator<T, T&, T*> Iterator; typedef RBTreeIterator<T, const T&, const T*> ConstIterator; ~RBTree() { Destroy(_root); _root = nullptr; } Iterator Begin() { Node* minLeft = _root; while (minLeft && minLeft->_left) { minLeft = minLeft->_left; } return Iterator(minLeft); } Iterator End() { return Iterator(nullptr); } ConstIterator Begin() const { Node* minLeft = _root; while (minLeft && minLeft->_left) { minLeft = minLeft->_left; } return ConstIterator(minLeft); } ConstIterator End() const { return ConstIterator(nullptr); } pair<Iterator, bool> Insert(const T& data) { if (_root == nullptr) { _root = new Node(data); _root->_col = BLACK; return { Iterator(_root), true }; } KeyOfT kot; 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)) else if (kot(data) < kot(cur->_data)) { parent = cur; cur = cur->_left; } else { return { Iterator(cur), 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; if (grandfather->_left == parent) { // g // p u Node* uncle = grandfather->_right; // 叔叔存在且为空 if (uncle && uncle->_col == RED) { // 变色+继续往上处理 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent; } else // 叔叔不存在或者叔叔存在且为黑 { // g // p u //c // 单旋+变色 if (cur == parent->_left) { RotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { // g // p u // c // 双旋+变色 RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } else { // g // u p Node* uncle = grandfather->_left; // 叔叔存在且为红,-》变色即可 if (uncle && uncle->_col == RED) { parent->_col = uncle->_col = BLACK; grandfather->_col = RED; // 继续往上处理 cur = grandfather; parent = cur->_parent; } else { // 情况二:叔叔不存在或者存在且为黑 // 旋转+变色 // g // u p // c if (cur == parent->_right) { RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { // g // u p // c RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK; return { Iterator(newnode), true }; } void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; Node* parentParent = parent->_parent; subL->_right = parent; parent->_parent = subL; if (parent == _root) { _root = subL; subL->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = subL; } else { parentParent->_right = subL; } subL->_parent = parentParent; } } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* parentParent = parent->_parent; subR->_left = parent; parent->_parent = subR; if (parentParent == nullptr) { _root = subR; subR->_parent = nullptr; } else { if (parent == parentParent->_left) { parentParent->_left = subR; } else { parentParent->_right = subR; } subR->_parent = parentParent; } } Iterator Find(const K& key) { KeyOfT kot; Node* cur = _root; while (cur) { if (kot(cur->_data) < key) { cur = cur->_right; } else if (kot(cur->_data) > key) { cur = cur->_left; } else { return Iterator(cur); } } return End(); } int Height() { return _Height(_root); } int Size() { return _Size(_root); } private: int _Size(Node* root) { if (root == nullptr) return 0; return _Size(root->_left) + _Size(root->_right) + 1; } int _Height(Node* root) { if (root == nullptr) return 0; int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; } void Destroy(Node* root) { if (root == nullptr) return; Destroy(root->_left); Destroy(root->_right); delete root; } private: Node* _root = nullptr; };

Map.h:

#pragma once #include"RBTree.h" namespace jqj { template<class K, class V> class map { struct MapKeyOfT { const K& operator()(const pair<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> insert(const pair<K, V>& kv) { return _t.Insert(kv); } iterator find(const K& key) { return _t.Find(key); } V& operator[](const K& key) { //pair<iterator, bool> ret = _t.Insert({ key, V() }); auto [it, flag] = _t.Insert({ key, V() }); return it->second; } private: RBTree<K, pair<const K, V>, MapKeyOfT> _t; }; }

Set.h:

#pragma once #include"RBTree.h" namespace jqj { template<class K> class set { 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> insert(const K& key) { return _t.Insert(key); } iterator find(const K& key) { return _t.Find(key); } private: RBTree<K, const K, SetKeyOfT> _t; }; }

Test.cpp:

#define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<vector> using namespace std; #include"RBTree.h" #include"Map.h" #include"Set.h" template<class T> void func(const jqj::set<T>& s) { typename jqj::set<T>::const_iterator it = s.begin(); while (it != s.end()) { //*it = 1; cout << *it << " "; ++it; } cout << endl; } void Test_set() { jqj::set<int> s; s.insert(1); s.insert(2); s.insert(1); s.insert(5); s.insert(0); s.insert(10); s.insert(8); jqj::set<int>::iterator it = s.begin(); // *it += 10; while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; func(s); } void Test_map() { jqj::map<string, string> dict; dict.insert({ "sort", "排序" }); dict.insert({ "left", "左边" }); dict.insert({ "right", "右边" }); dict["string"] = "字符串"; // 插入+修改 dict["left"] = "左边xxx"; // 修改 auto it = dict.begin(); while (it != dict.end()) { // it->first += 'x'; // 不能修改 it->second += 'x'; cout << it->first << ":" << it->second << endl; ++it; } cout << endl; for (auto& [k, v] : dict) { cout << k << ":" << v << endl; } cout << endl; string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" }; jqj::map<string, int> countMap; for (auto& e : arr) { /*auto it = countMap.find(e); if (it != countMap.end()) { it->second++; } else { countMap.insert({ e, 1 }); }*/ countMap[e]++; } for (auto& [k, v] : countMap) { cout << k << ":" << v << endl; } cout << endl; } int main() { Test_set(); Test_map(); return 0; }

运行结果


结尾

uu们,本文的内容到这里就全部结束了,艾莉丝再次感谢您的阅读!

往期回顾:

【C++:红黑树】深入理解红黑树的平衡之道:从原理、变色、旋转到完整实现代码

结语:都看到这里啦!那请大佬不要忘记给博主来个“一键四连”哦! 

🗡博主在这里放了一只小狗,大家看完了摸摸小狗放松一下吧!🗡

૮₍ ˶ ˊ ᴥ ˋ˶₎ა


Read more

Visual C++ 6.0中文版安装包下载教程及win11安装教程

本文分享的是Visual C++ 6.0(简称VC++6.0)中文版安装包下载及安装教程,关于win11系统下安装和使用VC++6.0使用问题解答,大家在安装使用的过程中会遇到不同的问题,如遇到解决不了的问题请给我留言! 一、安装包的下载 vc6.0安装包下载连接: https://pan.quark.cn/s/710dc0efe636 二、安装vc++6.0 1.鼠标右键解压到“VC++ 6.0”安装包,解压后如图所示: 2.双击Steup.exe,进行安装; 3.点击下一步 4.更改路径,建议不要安装在C盘(默认盘符),可以选择其他的盘符,点击浏览进行更改盘符。 5.选择C盘(默认盘或系统盘)以外的盘符。

By Ne0inhk
全网最全100道C++高频经典面试题及答案解析:C++程序员面试题库分类总结

全网最全100道C++高频经典面试题及答案解析:C++程序员面试题库分类总结

前言 C++作为一门兼具高性能与灵活性的语言,持续推动着量子计算、自动驾驶、区块链、AI编译器等领域的技术革命。本题库精选100道高频面试题,涵盖从内存模型、编译器内部机制到跨学科前沿应用的深度内容,专为资深工程师、系统架构师及科研岗位设计。无论是准备顶级科技公司面试,还是探索C++在安全关键系统(如航天、医疗)与新兴领域(如脑机接口、边缘AI)的工程实践,这些题目将帮助您展现对语言本质的理解和对复杂场景的掌控力。 题库特点: 垂直深入:超越语法层面,聚焦标准演进(C++20/23)、硬件协同优化及形式化验证等高级主题。 跨领域融合:结合LLVM/MLIR编译器开发、CUDA加速、实时操作系统等场景,体现C++的系统级控制能力。 第一部分:面向对象与内存管理(1-10题) 1. 虚函数实现原理(字节跳动/腾讯) 题目:虚函数表(vtable)在C++中是如何工作的?写出示例代码说明动态多态的实现。

By Ne0inhk

3.6-Web后端基础(java操作数据库)

目录 前言 JDBC 介绍 查询数据 需求 准备工作 代码实现 代码剖析 ResultSet 预编译SQL SQL注入 SQL注入解决 性能更高 增删改数据 需求 代码实现 Mybatis 介绍 快速入门 辅助配置 配置SQL提示 配置Mybatis日志输出 JDBC VS Mybatis 数据库连接池 介绍 产品 增删改查操作 删除 新增 修改 查询 XML映射配置 XML配置文件规范 XML配置文件实现 MybatisX的使用 SpringBoot配置文件 介绍 语法 案例 前言 在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。 我们做为后端程序开发人员,

By Ne0inhk
SkyWalking - .NET / C++ / Lua 探针现状与社区支持

SkyWalking - .NET / C++ / Lua 探针现状与社区支持

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕SkyWalking这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * SkyWalking - .NET / C++ / Lua 探针现状与社区支持 🌐 * 一、SkyWalking 多语言探针架构概览 🧩 * 二、Java 探针:成熟稳定,功能最全 ☕️ * 示例:Spring Boot 应用接入 SkyWalking * Java 探针高级特性 * 三、.NET 探针现状:渐趋成熟,生产可用 🖥️ * 技术原理 * 使用方式 * 当前支持的功能 * 局限性 * 四、C++ 探针现状:SDK 形式,适合嵌入式场景 ⚙️ * cpp2sky SDK

By Ne0inhk