通过红黑树封装 map 和 set 容器

一、红黑树的迭代器

红黑树的遍历默认为中序遍历 —— key 从小到大,因此 begin() 应该获取到红黑树的最左节点 —— 最小,end() 获取到红黑树最右节点的下一个位置, operator++() 也应保证红黑树的遍历为中序的状态。

首先对红黑树节点进行改造:

引入一个模板参数 T ,使 RBTreeNode / RBTree 成为一个适配器,当我们向 RBTree 中传 key 时,封装 set 容器;向 RBTree 中传 pair<key, value> 时,封装 map 容器。

1.1 定义红黑树的迭代器:
	template<class T, class Ref, class Ptr> // 与 list 迭代器处没有区别,Ref —— T& ,Ptr —— T*struct RBTreeIterator{typedef RBTreeNode<T> Node;typedef RBTreeNodeIterator<T, Ref, Ptr> Self;Node* _node;RBTreeIterator(Node* node):_node(node){}};
1.2 红黑树 operator++()

请务必记住:红黑树迭代器 ++ 为中序遍历 —— 左子树 — 根 — 右子树 !

假设 cur —— 迭代器 已经走到了 key 为 8 的节点 位置,这代表着 key 为 8 的节点 的左子树已经遍历过了key 为 8 的节点 的右子树不为空,则中序遍历 key 为 8 的节点 的右子树

	iterator operator++(){if (_node == nullptr) // 空树,直接返回 空 的迭代器{return iterator(nullptr); }if (_node->_right){Node* subLeft = _node->_right;while (subLeft->_left){subLeft = subLeft->_left;}_node = subLeft;}return *this;}

经过 operator++() 后,cur 到了 key 为 11 的节点 位置,此时 cur->_right 为空,表明以 key 为 11 的节点 为最右节点的子树已经全部遍历过,要去找从根到当前节点的简单路径中,cur 所在子树左子树最近祖宗节点

	iterator operator++(){// ...if (_node->_right){ //... }else {Node* cur = _node;Node* parent = cur->_parent;while (parent && cur != parent->_left) // parent 存在 且 cur所在子树不是parent的左子树时{cur = parent;parent = cur->_parent;}_node = parent;}return *this;}
总结:
  1. 迭代器指向节点的右子树不为空时, operator++() 的下一个位置就是其右子树的最左节点
  2. 迭代器指向节点的右子树为空,意味当前节点所在的左子树已经全部访问完了,operator++() 的下一个位置是当前子树为左子树的最近祖宗节点
1.3 operator*()
	Ref operator*(){return _node->_data;}
1.4 operator->()
	Ptr operator->(){return &(_node->_data);}

二、改造红黑树

在对红黑树进行修改之前,首先得明确:

  1. 老版本 Node(RBTreeNode) 的 data 类型是 pair<K, V>,我们通常把 K 当做键值,进而实现节点的查找、map::value 的修改等功能。
  2. 改造 Node 的目的是使 RBTree 成为容器适配器,我们对模板参数 T 传 key 时,则编译器封装出 set 容器;对 T 传 pair<K, V> 时,则封装 map 容器。以此减少冗余代码。

为了避免与 STL 中的 map 和 set 冲突,请在自己定义的命名空间内实现!

2.1 初步改造红黑树
	template<class K, class T>class RBTree{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T&, T*> iterator;typedef RBTreeIterator<T, const T&, const T*> const_iterator;private:Node* _root;};
2.2 初步搭建 map 和 set 的框架

map:

	template<class K, class V>class map{typedef RBTree<K, pair<const K, V>>::iterator iterator;typedef RBTree<K, pair<const K, const V>>::const_iterator const_iterator;private:RBTree<K, pair<const K, V>> _t; // 加上 const ,保证 key 是无法被修改的};

set:

	template<class K>class set{typedef RBTree<K, const K>::iterator iterator; typedef RBTree<K, const K>::const_iterator const_iterator;private:RBTree<K, const K> _t; // set 中的节点不能被修改}

无论是 map 还是 set 中,都存着两个 key : Kconst K / pair<const K, V>前者作为键值,用于查找后者则是我们存入 Node 中的数据 —— 对应于 Node 的模板参数 T ,编译器会根据我们传入的数据类型生成对应类型的节点

注意容器内部树的类型要与 typedef 的迭代器类型保持一致,否则编译阶段会报错 —— 比如:在某些地方会出现 const 权限放大的问题。

2.3 引入仿函数 KeyOfT

我们再看一眼,旧版本的 RBTree::Insert()

第一,写死的 const pair<K, V> kv 显然已经不合时宜;

第二,如果 T 的类型是 pair ,我们可以通过 cur->_data.first (新版 Node 已将 _kv 修改为 _data)取到 key 进行比较,若 T 的类型是 int ,则整个程序必然出现问题。

因此,我们需要引入一个仿函数 KeyOfT用于从节点的 _data 中取到相应的键值,进而完成一系列操作。

  • 在 map 和 set 中封装仿函数 KeyOfT
	// map:template<class K, class V>class map{typedef RBTree<K, pair<const K, V>, KeyOfT>::iterator iterator;typedef RBTree<K, pair<const K, const V>, KeyOfT>::const_iterator const_iterator;public:struct KeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};private:RBTree<K, pair<const K, V>, KeyOfT> _t; // 加上 const ,保证 key 是无法被修改的};// set:template<class K>class set{typedef RBTree<K, const K, KeyOfT>::iterator iterator; typedef RBTree<K, const K, KeyOfT>::const_iterator const_iterator;public:struct KeyOfT{const K& operator()(const K& k){return k;}};private:RBTree<K, const K, KeyOfT> _t; // set 中的节点不能被修改}

同时我们要为 RBTree 增加一个接受仿函数的模板参数。

	template<class K, class T, class KeyOfT>class RBTree{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T&, T*> iterator;typedef RBTreeIterator<T, const T&, const T*> const_iterator;private:Node* _root;};
  • 改造 RBTree::Insert()
	pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK; // 默认构造节点为红色,根据红黑树的性质,我们应把根节点修改为黑色return make_pair(iterator(root), true);}Node* cur = _root;Node* parent = nullptr;KeyOfT kot; // 仿函数变量,用于获取 data 中的键值while (cur){if (kot(cur->_data) > kot(data)) // 利用仿函数 kot 获取键值{parent = cur;cur = cur->_left;}else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else // 红黑树中已经存在一个要插入键值的节点{return make_pair(iterator(cur), true); // 返回当前节点所在的迭代器,并 return false}}Node* newnode = cur; // 保存当前节点的位置,防止旋转导致 cur 指向其他节点而出现错误if (kot(parent->_data) > kot(cur->_data)){parent->_left = cur;}else {parent->_right = cur;}// 旋转的一系列过程// ...// 与普通红黑树无异,前面的文章已提到,这里不再赘述_root->_col = BLACK; // 把根节点修改为黑色return make_pair(iterator(newnode), true);}
2.4 改造 Find() ,增加 begin() end()
  • Find()
	iterator Find(const K& key){if (_root == nullptr){return iterator(nullptr); // 空树,返回空迭代器}Node* cur = _root;Node* parent = nullptr;KeyOfT kot; // 仿函数变量,用于获取 data 中的键值while (cur){if (kot(cur->_data) > kot(data)) // 利用仿函数 kot 获取键值{parent = cur;cur = cur->_left;}else if (kot(cur->_data) < kot(data)){parent = cur;cur = cur->_right;}else // 找到了{return iterator(cur);}}return iterator(nullptr);}

其实没有必要写 if (_root == nullptr) 这一语句,如果 _root 为空,cur 自然也为空,不会进入 while 循环,最终还是 return iterator(nullptr) ;为了代码的逻辑性,还是加上。

  • begin()
	iterator begin(){if (_root == nullptr){return iterator(nullptr);}Node* subLeft = _root;while (subLeft->_left){subLeft = subLeft->_left; // 找红黑树的最左节点}return iterator(subLeft);}const_iterator begin() const{if (_root == nullptr){return iterator(nullptr);}Node* subLeft = _root;while (subLeft->_left){subLeft = subLeft->_left; // 找红黑树的最左节点}return iterator(subLeft);}
  • end()
	iterator end(){return iterator(nullptr);}const_iterator end() const{return iterator(nullptr);}

三、实现 map

3.1 对简单接口进行包装
	template<class K, class V>class map{typedef RBTree<K, pair<const K, V>, KeyOfT>::iterator iterator;typedef RBTree<K, pair<const K, const V>, KeyOfT>::const_iterator const_iterator;public:struct KeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};iterator begin() // 封装 begin(){return _t.begin();}iterator end() // end(){return _t.end();}iterator find(const K& key) // find(){return _t.Find(key);}pair<iterator, bool> insert(const pair<K, V>& kv) // insert(){return _t.Insert(kv);}private:RBTree<K, pair<const K, V>, KeyOfT> _t; // 加上 const ,保证 key 是无法被修改的};
3.2 operator[]

在前面的文章中,我们讲到过 operator[]insert() 的关系。

同样,我们也要通过 insert() 实现 operator[] 接口。

	V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));return ret.first->second;}    
解释一下 ret.first->second
  1. ret.first 取到 pair 中的迭代器
  2. ret.first->second 全写为 ret.first->->second

​ 第一个 ->operator->()return &(_node->_data)

​ 第二个 ->(&_data)->second

编译器把两个 -> 简化为一个 —— 显式写两个反而会报错。

四、实现 set

与实现 map 基本一致,这里便不再重复了。

	template<class K>class set{public:struct KeyOfT{const K operator()(const K& data){return data;}};typedef typename RBTree<K, const K, KeyOfT>::iterator iterator;typedef typename RBTree<K, const K, KeyOfT>::const_iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}iterator find(const K& key){return _t.Find(key);}pair<iterator, bool> insert(const K& key){return _t.Insert(key);}private:RBTree<K, const K, KeyOfT> _t;};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

骨传导耳机怎么选?五款高分热销榜骨传导耳机单品推荐!

骨传导耳机哪个牌子好&#xff1f;作为资深数码博主&#xff0c;许多朋友都希望我能够分享一些关于骨传导耳机的选购技巧。随着时间的发展&#xff0c;市面上的骨传导耳机种类日渐繁多&#xff0c;其中品牌的专业实力参差不齐&#xff0c;产品质量也千差万别。一些黑心商家为了…

家装空间3D建模素材:打造理想家园的必备工具

在家装过程中&#xff0c;设计师和业主往往需要通过3D建模技术来实现对空间的精确规划和设计。3D建模素材作为这一领域的基础元素&#xff0c;为设计师提供了丰富的想象空间&#xff0c;帮助他们更好地呈现业主的期望和需求。 这些3D建模素材可以涵盖各种家装元素&#xff0c;如…

roofline model加速模型部署最后一公里

文章目录 模型部署教程来啦:)什么是Roofline Model&#xff1f;算法模型相关指标计算量计算峰值参数量访存量带宽计算密度kernel size对计算密度的影响output size对计算密度的影响channel size对计算密度的影响group convolution对计算密度的影响tensor reshape对计算密度的影…

linux 性能监控命令之dstat

1. dstat 系统默认为安装&#xff0c;直接安装阿里源后&#xff0c;yum install -y dstat安装即可&#xff0c;该命令整合了 vmstat &#xff0c; iostat 和 ifstat&#xff0c;我们先看下效果&#xff1a; 我们先看看具体参数&#xff1a; [rootk8s-master ~]# dstat --help …

Python AI库pandas读写数据库的应用操作——以sqlite3为例

Python AI库pandas读写数据库的应用操作——以sqlite3为例 本文默认读者具备以下技能&#xff1a; 熟悉python基础知识&#xff0c;vscode或其它编辑工具 已阅读Pandas基础操作文章,了解pandas常见操作 具备自主扩展学习能力 在数据分析和人工智能领域&#xff0c;pandas库和s…

怎么批量修改图片的大小?分享几个方法

现在不管是在自媒体还是在各种社交媒体平台中&#xff0c;我们都会用到大量的图片&#xff0c;有时候会发现图片尺寸过大&#xff0c;对上传或者储存带来了不小的困难&#xff0c;在这种情况下&#xff0c;调整图片尺寸显得格外重要&#xff0c;通过修改图片尺寸&#xff0c;我…

# 从浅入深 学习 SpringCloud 微服务架构(八)Sentinel(2)

从浅入深 学习 SpringCloud 微服务架构&#xff08;八&#xff09;Sentinel&#xff08;2&#xff09; 一、sentinel&#xff1a;通用资源保护 1、Rest 实现熔断 Spring Cloud Alibaba Sentinel 支持对 RestTemplate 的服务调用使用 Sentinel 进行保护, 在构造 RestTemplate…

[华为OD]C卷 BFS 亲子游戏 200

题目&#xff1a; 宝宝和妈妈参加亲子游戏&#xff0c;在一个二维矩阵&#xff08;N*N&#xff09;的格子地图上&#xff0c;宝宝和妈妈抽签决定各自 的位置&#xff0c;地图上每个格子有不同的Q糖果数量&#xff0c;部分格子有障碍物。 游戏规则Q是妈妈必须在最短的时间&a…

预兼容性EMC测试基础

介绍――预兼容性EMC测试的重要意义 在产品开发过程中&#xff0c;您最想做的是对器件进行测试&#xff0c;验证其是否正常工作。所有电子器件都必须在经过认证的内部测试中心内成功通过电磁兼容性EMI测试。通过 EMI测试即表明您的器件的 EMI 发射性能达到允许水平&#xff0c;…

Hive UDTF之explode函数、Lateral View侧视图

Hive UDTF之explode函数 Hive 中的 explode() 函数是一种用于处理数组类型数据的 User-Defined Table-Generating Function (UDTF)。它将数组拆分成多行&#xff0c;每个数组元素对应生成的一行数据。这在处理嵌套数据结构时非常有用&#xff0c;例如处理 JSON 格式的数据。 …

详细介绍一下PointPillars算法的网络结构

PointPillars是一种用于3D目标检测的算法&#xff0c;它主要使用了点云数据和深度学习模型。 PointPillars算法的网络结构主要可以分为三个主要阶段&#xff1a; Pillar Feature Net&#xff08;点云特征处理网络&#xff09;&#xff1a;此阶段的主要任务是将输入的点云数据转…

怎样单独提取PDF文件中的一个或几个文件?分割PDF文件的方法

在现代数字化时代&#xff0c;PDF文件已成为我们日常生活和工作中不可或缺的一部分。 一&#xff0c;首先了解什么是PDF&#xff1f; PDF&#xff0c;即“Portable Document Format”&#xff0c;意为“便携式文档格式”&#xff0c;由Adobe Systems开发。由于其跨平台、不易…

探秘Appium:Capability 进阶技巧揭秘!

简介 Appium 的除了基础的 Capability 设置&#xff0c;还提供了许多辅助配置项&#xff0c;用于优化自动化测试。这些配置项旨在执行基础配置之外的附加操作。例如&#xff1a;指定设备别名、设备 ID 或是设置超时时间等&#xff0c;虽然这些不是必需的选项&#xff0c;但是为…

【Linux系统编程】第十五弹---调试器gdb使用

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、背景 2、安装gdb 3、gdb的使用 总结 1、背景 前面我们学习了文件编辑器&#xff0c;项目自动化构建工具&#xff0c;以及g…

目标检测算法YOLOv6简介

YOLOv6由Chuyi Li等人于2022年提出&#xff0c;论文名为&#xff1a;《YOLOv6: A Single-Stage Object Detection Framework for Industrial Applications》&#xff0c;论文见&#xff1a;https://arxiv.org/pdf/2209.02976 &#xff0c;项目网页&#xff1a;https://github.c…

Python程序设计 函数(三)

练习十一 函数 第1关&#xff1a; 一元二次方程的根 定义一个函数qg&#xff0c;输入一元二次方程的系数a,b,c 当判别式大于0&#xff0c;返回1和两个根 当判别式等于0&#xff0c;返回0和两个根 当判别式小于0&#xff0c;访问-1和两个根 在主程序中&#xff0c;根据函数返回…

大模型微调之 在亚马逊AWS上实战LlaMA案例(三)

大模型微调之 在亚马逊AWS上实战LlaMA案例&#xff08;三&#xff09; 使用 QLoRA 增强语言模型&#xff1a;Amazon SageMaker 上 LLaMA 2 的高效微调 语言模型在自然语言处理任务中发挥着关键作用&#xff0c;但训练和微调大型模型可能会占用大量内存且耗时。在本文中&…

STM32 各外设GPIO配置

高级定时器TIM1/TIM8 通用定时器TIM2/3/4/5 USART SPI I2S I2C接口 BxCAN SDIO ADC/DAC 其它I/O功能

【数据库原理及应用】期末复习汇总高校期末真题试卷08

试卷 一、选择题(每题 2 分&#xff0c;共 30 分)    1. ___ ____是长期存储在计算机内的有组织,可共享的数据集合. A.数据库管理系统 B.数据库系统 C.数据库 D.文件组织 2. 数据库类型是按照 来划分…

照片格式怎么转换jpg?利用在线图片处理工具完成操作

图片有许多不同的格式类型&#xff0c;其中我们最常见的是jpg和png等。通常在平台上上传图片时&#xff0c;大多数要求使用jpg格式较多&#xff0c;但你知道吗&#xff1f;不同的设备和软件可能有不同的默认保存格式。如果你发现你的照片不是jpg格式&#xff0c;该如何转换呢&a…