【C++】红黑树模拟实现STL库中的map与set

目录

改造红黑树

红黑树的迭代器

map的模拟实现 

 set的模拟实现


在上一篇博客中,我们实现了红黑树,但是红黑树节点中的值是pair<K,V> _kv形式,这种红黑树适用于map的底层,那么如果我们想要红黑树节点中的值是key的形式(适用于set的底层),我们该怎么搞呢?难道我们只把红黑树节点的类型改一下,其它基本保持不变,再来实现一遍吗?这未免太繁琐了!

我们先来看一看stl库中如何实现map和set的:

它们貌似用的同样的红黑树实现的,它们的区别在于红黑树的第二个模版参数,一个是key,一个是kv的pair,我们再来看看stl库里怎么实现的rb_tree,

在stl中,红黑树在实现时,并没有直接确定是key还是kv的pair,而是由模版的第二个参数Value决定,本质上这是一个高级的泛型编程,不得不说,大佬写的模版比我们技高一筹!

改造红黑树

为了达到和stl库中一样的效果,我们需要对上节的红黑树进行改造,即对红黑树中值的类型不指定,使用模版:

//写成一个模版,T有可能是key,有可能是pair
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Color _color;T _data;RBTreeNode(const T& data): _left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _color(RED){}
};

 调用示意图如下:

肯定有人会有疑问,RBTree中第一个模版参数class K好像没用到,其实在Find函数中可以用到,Find函数肯定要通过Key来查找(Node* Find(const K& key))。

当我们对红黑树改造后,在Insert函数中,会遇到这样的问题:我们需要对待插入的数据节点值进行比较以便插入,但是,待比较数据和节点值有可能是key,也有可能是pair,为了解决这样的问题,在class set和class map中分别增加一个内部类,

template<class K>
class set
{struct SetKeyOfT{const K& operator()(const K& key){return key;}};...
};template<class K,class V>
class map
{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};...
};

在class RBTree上加模版参数class KeyOfT,然后在类中定义KeyOfT的对象,

template<class K,class T,class KeyOfT>
class RBTree
bool Insert(const T& data)
{if (_root == nullptr){_root->_color = BLACK;return true;}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;while (cur){//K//pair<K,V>//kot对象,是用来取T类型的data对象中的key//如果data是pair,则通过kot仿函数去调用上面的内部类,从而得到pair中的keyif (kot(cur->_data) > kot(data)){parent = cur;cur = cur->_left;}else if(kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else{return false;}}...
}

红黑树的迭代器

我们先来看stl里是怎么实现的:

迭代器里就是一个节点指针去封装,

然后实现operator++、operator->等的重载,我们自己来实现一下红黑树的迭代器:

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;}bool operator!=(const Self& s){return _node != s._node; }Self& operator++(){if (_node->_right){//下一个,右树最左节点Node* leftMin = _node->_right;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}_node = leftMin;}else{//下一个,孩子等于父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}
};

上面中最复杂的是operator++的实现,我们分情况讨论一下:

        1.当前结点的右子树不为空,中序下一个访问的节点,是右子树的最左节点。

        2.右为空,下一个访问,倒着在祖先里找,孩子是父亲左的祖先

在红黑树的实现中,我们typedef一下:

typedef __RBTreeIterator<T, T&, T*> Iterator;

在红黑树中,我们要实现begin()、end()等接口:

//begin是最左节点
Iterator begin()
{Node* leftMin = _root;while (leftMin && leftMin->_left){leftMin = leftMin->_left;}return Iterator(leftMin);
}
//end是最后一个节点的下一个,也就是nullptr
Iterator end()
{return Iterator(nullptr);
}

map的模拟实现 

如果我们想要实现map或set的拷贝构造,我们知道默认的拷贝构造,对于内置类型完成值拷贝,针对自定义类型会调用它的拷贝构造,这样会造成浅拷贝,所以,我们需要自己写拷贝构造函数:

//强制生成默认构造函数
RBTree() = default;RBTree(const RBTree<K, T, KeyOfT>& t)
{_root = Copy(t._root);
}
private:Node* Copy(Node* root){if (root == nullptr)return nullptr;//构造新节点Node* newroot = new Node(root->_data);//改变新节点的颜色newroot->_color = root->_color;//把递归Copy得到的子树链接到newroot的左侧newroot->_left = Copy(root->_left);//让newroot->_left向上链接到newrootif (newroot->_left)newroot->_left->_parent = newroot;//把递归Copy得到的子树链接到newroot的右侧newroot->_right = Copy(root->_right);//让newroot->_left向上链接到newrootif (newroot->_right)newroot->_right->_parent = newroot;return newroot;}

除此之外,我们还要写析构函数:

~RBTree()
{Destroy(_root);_root = nullptr;
}
void Destroy(Node* root)
{//走一个后序遍历if (root == nullptr)return;Destroy(root->_left);Destroy(root->_right);delete root;root = nullptr;
}

在map中我们还要实现operator[]重载,我们之前学过,库里的[]重载是调用insert函数,operator[]返回值是key所在的迭代器,

因此,我们主要就是调整insert函数,insert函数的返回值类型是pair<Iterator,bool>,修改insert函数的返回值:

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

实现operator[]:

V& operator[](const K& key)
{pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));//ret.first.operator->()->secondreturn ret.first->second;
}

完整的map模拟实现代码如下:

namespace ghs
{template<class K,class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public://此时RBtree还没有实例化,需要加typename告诉编译器,等实例化了再去确认typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, MapKeyOfT>::ConstIterator const_iterator;const_iterator begin()const{return _t.begin();}const_iterator end()const{return _t.end();}iterator begin(){return _t.begin();}iterator end(){return _t.end();}pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){pair<iterator, bool> ret = _t.Insert(make_pair(key, V()));//ret.first.operator->()->secondreturn ret.first->second;}private://由于RBTree的第二个模版参数是用来确定节点的类型,map要求pair中给的key不能变,但是val可以变//因此,pair<const K, V>中第一个参数加const,第二个参数不加constRBTree<K, pair<const K, V>, MapKeyOfT> _t;};}

 set的模拟实现

namespace ghs
{template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public://没有实例化的类,去取,需要加typenametypedef typename RBTree<K, const K, SetKeyOfT>::Iterator iterator;typedef typename RBTree<K, const K, SetKeyOfT>::ConstIterator const_iterator;const_iterator begin()const{return _t.begin();}const_iterator end()const{return _t.end();}iterator begin(){return _t.begin();}iterator end(){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的第二个模版参数是用来确定节点的类型,set的key是不可以改变的,所以要加constRBTree<K, const K, SetKeyOfT> _t;};
}

在学完vector、list、map、set的迭代器iterator后,我们可以进一步理解一下封装:它们底层的operator++、operator*、operator->完全不一致,但是对外提供的方式是一致的(统一的迭代器行为)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/3247701.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

Vue3项目基于Axios封装request请求

在 Vue 3 的项目开发中&#xff0c;使用 Axios 进行 HTTP 请求是非常常见的作法&#xff0c;为了更方便开发者更高效的进行代码编写和项目的维护&#xff0c;可以通过再次封装 Axios 来实现。 在本文中&#xff0c;博主将详细指导你如何在自己的 Vue 3 项目中使用 Axios 二次封…

【web】-反序列化-to_string

<?php highlight_file(__FILE__); class A{public $s;public function __destruct(){echo "hello".$this->s;}} class B{public $cmd;public function __toString(){system($this->cmd);return 1;} } unserialize($_GET[code]); __toString()当对象被当着…

android之selinux问题解决记录 一

文章目录 简述流程分析编译验证 简述 主要是使用第三方应用来读写usb设备中的mode值&#xff0c;遇到的selinux权限问题的处理&#xff1b; 流程分析 当logcat日志中有avc:denied关键字段打印时&#xff0c;说明存在selinux问题 1.读取 读取配置中的mode值时&#xff0c;第…

Linux入门攻坚——28、php、mysql基础

httpdphp&#xff1a;是在httpd中启用模块&#xff0c;不同的工作模式&#xff0c;使用的模块不同 modules httpd&#xff1a;prefork --> libphp5.so httpd&#xff1a;event or worker --> libphp5-zts.so php&#xff1a;引入zend engine后&#xff0c;分为…

Burp安全扫描Web应用

一、浏览器设置代理 如下图所示&#xff0c;点击火狐浏览器的“扩展和主题”&#xff0c;搜索“代理”。 如下图所示&#xff0c;选择搜索到的第一个代理&#xff08;选择任何一个都可以&#xff09;。 如上图所示&#xff0c;第一个点击后&#xff0c;进入如下页面&#xff0…

51单片机学习(4)

一、串口通信 1.串口通信介绍 写完串口函数时进行模块化编程&#xff0c;模块化编程之后要对其进行注释&#xff0c;以便之后使用模块化函数&#xff0c;对模块化.c文件中的每一个函数进行注释。 注意&#xff1a;一个函数不能既在主函数又在中断函数中 模式1最常用&#xf…

Kafka Producer发送消息流程之消息异步发送和同步发送

文章目录 1. 异步发送2. 同步发送 1. 异步发送 Kafka默认就是异步发送&#xff0c;在Main线程中的多条消息&#xff0c;没有严格的先后顺序&#xff0c;Sender发送后就继续下一条&#xff0c;异步接受结果。 public class KafkaProducerCallbackTest {public static void mai…

linux搭建mysql主从复制(一主一从)

目录 0、环境部署 1、主服务器配置 1.1 修改mysql配置文件 1.2 重启mysql 1.3 为从服务器授权 1.4 查看二进制日志坐标 2、从服务器配置 2.1 修改mysql配置文件 2.2 重启mysql 2.3 配置主从同步 2.4 开启主从复制 3、验证主从复制 3.1 主服务器上创建test…

单片机开发中,如何在断电前保存数据到dataflash?

在单片机开发中&#xff0c;保存数据到 DataFlash&#xff08;数据闪存&#xff09;是一项常见任务&#xff0c;尤其是在断电前需要保留重要数据时。 我收集归类了一份嵌入式学习包&#xff0c;对于新手而言简直不要太棒&#xff0c;里面包括了新手各个时期的学习方向编程教学…

多线程实现方式和常用方法

1 进程和线程 进程Process&#xff1a; 每个进程都有独立的代码和数据空间&#xff08;进程上下文&#xff09;&#xff0c;进程间的切换会有较大的开销&#xff0c;一个进程包含1--n个线程。可以把进程简单理解为操作系统中运行的一个程序。 线程Thread&#xff1a;同一类线程…

音视频开发入门教程(2)配置FFmpeg编译 ~共210节

在上一篇博客介绍了安装&#xff0c;音视频开发入门教程&#xff08;1&#xff09;如何安装FFmpeg&#xff1f;共210节-CSDN博客 感兴趣的小伙伴&#xff0c;可以继续跟着老铁&#xff0c;一起开始音视频剪辑功能&#xff0c;&#x1f604;首先查看一下自己的电脑是几核的&…

【代码规范】out = model(data)和out = model.forward(data.detach())的相似性和区别

【代码规范】out model(data)和out model.forward(data.detach())的相似性和区别 一、out model(data)和out model.forward(data.detach())的功能 二、out model(data)和out model.forward(data.detach())的区别 三、推理攻击下使用哪一个 文章目录 一、out model(data)…

Keka for Mac v1.4.3 中文下载 解压/压缩工具

Mac分享吧 文章目录 效果一、下载软件二、开始安装1、双击运行软件&#xff0c;将其从左侧拖入右侧文件夹中&#xff0c;等待安装完毕2、应用程序显示软件图标&#xff0c;表示安装成功 三、运行测试1、打开软件2、文件访问权限修改3、访达扩展 安装完成&#xff01;&#xff…

ppt文本框复制到word自动缩进的问题

ppt里的字是无缩进的&#xff1a; 复制粘贴到word中&#xff0c;突然出现2字符缩进&#xff1a; 微软官方嘴硬说没问题我也是无语&#xff01;&#xff01;word保留原格式复制后&#xff0c;出现莫名其妙的缩进 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直…

Kafka Producer发送消息流程之Sender发送线程和在途请求缓存区

文章目录 1. Sender发送数据1. 发送数据的详细过程&#xff1a;2. 关键参数配置 2. 在途请求缓存区 1. Sender发送数据 Sender线程负责将已经在RecordAccumulator中准备好的消息批次发送到Kafka集群。虽然消息在RecordAccumulator中是按照分区组织的&#xff0c;但Sender线程在…

强化学习的数学原理(2)

Value iteration & Policy itreation Value iteration algorithm 之前我们已经讲过怎么去求解贝尔曼最优公式&#xff0c;是利用contraction mapping theorem 来进行求解&#xff0c;我们知道这个contraction mapping theorem是一个迭代算法&#xff0c;实际上这个算法他有…

Android中OkHttp3中超时时间概述

目录 前言connectTimeoutreadTimeoutwriteTimeoutcallTimeoutpingInterval拓展 前言 可以看到&#xff0c;使用还是很简单的。主要相关的有这五个参数&#xff0c;其中我们常用到是就是connectTimeout、readTimeout和writeTimeout。 再看上图&#xff0c;可以看到默认下connec…

独立游戏《星尘异变》UE5 C++程序开发日志6——实现存档和基础设置系统

目录 一、存档类 1.创建一个SaveGame类 2.存储关卡内数据 3.加载关卡数据 4.关于定时器 5.存储全局数据 6.加载全局数据 二、存档栏 1.存档栏的数据结构 2.创建新存档 3.覆盖已有存档 4.删除存档 三、游戏的基础设置 1.存储游戏设置的数据结构 2.初始化设置 3.…

链表面试练习习题集(Java)

1. 思路&#xff1a; 因为杨辉三角是由二维数组构成&#xff0c;所以要先创建一个二维数组&#xff0c;如何用顺序表表示二维数组&#xff0c;可以通过List<List<Interger>>来表示一个二维数组&#xff0c;可以这样理解&#xff1a;先创建一个一维数组List&#x…

智慧消防建设方案(完整方案参考PPT)

智慧消防系统建设方案旨在通过物联网、大数据与云计算技术&#xff0c;集成火灾自动报警、智能监控、应急指挥等功能于一体。方案部署智能传感器监测火情&#xff0c;实时数据分析预警&#xff0c;实现火灾早发现、早处置。构建可视化指挥平台&#xff0c;优化应急预案&#xf…