【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

DeepFace深度学习库+OpenCV实现——情绪分析器

DeepFace深度学习库+OpenCV实现——情绪分析器

目录 应用场景 实现组件 1. 硬件组件 2. 软件库与依赖 3. 功能模块 代码详解(实现思路) 导入必要的库 打开摄像头并初始化变量 主循环 FPS计算 情绪分析及结果展示 显示FPS和图像 退出条件 编辑 完整代码 效果展示 自然的 开心的 伤心的 恐惧的 惊讶的  效果展示 自然的 开心的 伤心的 恐惧的 惊讶的   应用场景         应用场景比较广泛,尤其是在需要了解和分析人类情感反应的场合。: 1. 心理健康评估:在心理健康领域,可以通过长期监控和分析一个人的情绪变化来辅助医生进行诊断或治疗效果评估。 2. 用户体验研究:在产品设计、广告制作或网站开发过程中,通过观察用户在使用过程中的情绪反应,来优化产品的用户体验。 3. 互动娱乐:在游戏或虚拟现实应用中,根据玩家的情绪状态动态调整游戏难度或故事情节,以增加沉浸感和互动性。

By Ne0inhk
最全java面试题及答案(208道)

最全java面试题及答案(208道)

本文分为十九个模块,分别是:「Java 基础、容器、多线程、反射、对象拷贝、Java Web 、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、RabbitMQ、Kafka、Zookeeper、MySQL、Redis、JVM」 ,如下图所示: 共包含 208 道面试题,本文的宗旨是为读者朋友们整理一份详实而又权威的面试清单,下面一起进入主题吧。 Java 基础 1. JDK 和 JRE 有什么区别? * JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java

By Ne0inhk
用 DeepSeek 打造你的超强代码助手

用 DeepSeek 打造你的超强代码助手

DeepSeek Engineer 是啥? 简单来说,DeepSeek Engineer 是一个基于命令行的智能助手。它能帮你完成这些事: * 快速读文件内容:比如你有个配置文件,直接用命令把它加载进助手,后续所有操作都可以基于这个文件。 * 自动改文件:它不仅能提建议,还可以直接生成差异表(diff),甚至自动应用修改。 * 智能代码生成:比如你让它生成代码片段,它会按照指定格式和规则直接返回。 更重要的是,这一切都是通过 DeepSeek 的强大 API 来实现的。想象一下,你有个贴身助手,不仅能听懂你的代码需求,还能直接动手帮你写! 核心功能拆解 我们先来看 DeepSeek Engineer 的几个核心能力,让你更好地理解它的强大之处。 1. 自动配置 DeepSeek 客户端 启动这个工具时,你只需要准备一个 .env 文件,里面写上你的 API Key,比如: DEEPSEEK_API_

By Ne0inhk
10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

10分钟打造专属AI助手!ToDesk云电脑/顺网云/海马云操作DeepSeek哪家强?

文章目录 * 一、引言 * 云计算平台概览 * ToDesk云电脑:随时随地用上高性能电脑 * 二 .云电脑初体验 * DeekSeek介绍 * 版本参数与特点 * 任务类型表现 * 1、ToDesk云电脑 * 2、顺网云电脑 * 3、海马云电脑 * 三、DeekSeek本地化实操和AIGC应用 * 1. ToDesk云电脑 * 2. 海马云电脑 * 3、顺网云电脑 * 四、结语 * 总结:云电脑如何选择? 一、引言 DeepSeek这些大模型让 AI 开发变得越来越有趣,但真要跑起来,可没那么简单! * 本地配置太麻烦:显卡不够、驱动难装、环境冲突,光是折腾这些就让人心态崩了。 * 云端性能参差不齐:选错云电脑,可能卡到爆、加载慢,还容易掉线,搞得效率直线下降。 * 成本难控:有的平台按小时计费,价格一会儿一个样,

By Ne0inhk