利用红黑树封装map,和set,实现主要功能

如果不知道红黑树是什么的时候可以去看看这个红黑树

思路

首先我们可以把封装分为两个层面理解,上层代码就是set,和map,底层就是红黑树
就相当于根据红黑树上面套了两个map,set的壳子,像下面这张图一样
在这里插入图片描述
对于map和set,map里面存放的是pair一个键值对,而set就只是一个key,一般的想法是实现两个红黑树,一个里面的节点放key一个放pair,那么这样就太冗余了,那么我们只用实现一个红黑树就行了,我们利用模板就可以很轻松的解决冗余的问题
下面就来看看怎么具体的实现的

创建set,map

这里的set和map我们统称为myset,mymap
我们首先根据红黑树的基础把set,和map的架子搭起来
先来实现基本的插入功能

# include"RBtree.h"
namespace mymap
{template<class key, class value>class mymap{public:struct k_of_t_map{const key& operator()(const pair<key,value>& input_map){return input_map.first;}};bool Insert(const pair<key, value>& input_map){return _t.insert(input_map);}private:RBtree<key, pair< const key, value>, k_of_t_map> _t;};
}

我们在插入的时候是根据搜索树的规则进行插入的,搜索树肯定是要比较大小的,我们的pair他原生是支持比较大小的,但是库里面的比较大小是根据first和second来进行比较的,我们这里肯定不是这样的,我们是想两个pair不同的first进行比较大小的,所以库里面的比较无法满足我们的需求
这里我们的解决方法是利用仿函数

struct k_of_t_map{const key& operator()(const pair<key,value>& input_map){return input_map.first;}};

我们通过实现一个内部类对operator()的重载,实现类似于函数()的功能,就能像函数一样使用,去实现我们想要的功能,这里我们通过operator()我们直接返回input_map.first,就得到了我们想要的pair的first也就是用来比较大小的key

同样set也是一样的道理
这里的set其实有点冗余了,是为了配合map而创建的,他本身就不需要都行

# include"RBtree.h"
namespace myset
{template<class key>class myset{public:struct k_of_t_set{const key& operator()(const key& input_key){return input_key;}};bool Insert(const key& input_key){return _t.insert(input_key);}private:RBtree< key,const key,k_of_t_set> _t;};
}

有了上面的架子之后,我们红黑树的插入功能里面的比较大小的方法,也需要改一下,改成在比较大小的时候去调用mymap,myset里面的内部类里面的成员函数。

所以我们在创建mymap,myset的成员变量时,就要根据RBtree在加上自身的性质来创建,这里mymap和myset最核心的差别就是一个数据类型,另一个是比较大小的逻辑不同,所以我们在RBtree的模板参数就要变成

template<class key, class T, class k_of_t>

多提供一个k_of_t的模板参数 这个就是用来设置比较大小的方法用的
所以我们对于的mymap,myset就要变成

RBtree<key, pair< const key, value>, k_of_t_map> _t;
RBtree< key,const key,k_of_t_set> _t;

然后再插入的比较大小的逻辑也要套用这个内部类的成员函数
首先通过k_of_t来创建一个 kot的对象
然后再根据这个对象去调用相应的成员函数
在这里插入图片描述
关于mymap,myset的成员变量的初始化
其实我们根据代码我们就可以发现mymap,myset的成员函数的初始化其实就是去复用RBtree的初始化
就像一个映射一样,**假设初始化mymap把mymap里面的模板参数传过去,然后再去初始化RBtree的_root。**把相应的数据类型比较大小的方法替换的mymap的比较方法
myset也是同理。
在这里插入图片描述
有了以上的步骤之后我们就可以实现mymap和myset的插入了

map set的迭代器

下面我们讲讲迭代器的实现
我们先来看看迭代器的构造

template<class T,class ref,class ptr>
//迭代器就相当于一个节点的指针
struct RBtreeIterator//默认公有
{typedef RBtreenode<T> Node;typedef RBtreeIterator<T, ref, ptr> self;Node* _it_node;RBtreeIterator (Node* input_node, Node* root):_it_node(input_node){}

这里我们传了三个模板参数一个是数据类型一个是引用,最后一个是指针
这样的目的是为了方便我们想要引用的时候用引用,想要指针的时候用指针
这里我们的成员变量是一个RBtreenode类型,我们需要构造一个节点,把他当作一个指针,做为我们迭代器遍历容器的一个媒介迭代器就相当于一个节点的指针
了解了上面的结构之后
下面我们来看看迭代器的operator++和–这个是迭代器的核心,也是比较难的

operator++

先来看看代码

self operator++()
{if (_it_node->_right != nullptr)//找右最小节点{Node* right_min = this->_it_node->_right;while (right_min->_left != nullptr){right_min = right_min->_left;}_it_node = right_min;}else//右边访问完,代表以他为基本的左子树右子树都完了,就要向上走{Node* cur = _it_node;Node* parent = cur->_pre_node;//这里要找的是相对于儿子的父亲,父亲的右节点不是孩子的节点//如果一直是右节点就代表当前节点的左右子树都完了//如果找到父亲的左节点是孩子,就代表父亲还有右节点没访问完。while (parent != nullptr && parent->_right == cur){cur = parent;parent = cur->_pre_node;}_it_node = parent;}return *this;
}

这里我们需要和红黑树的性质一起来说一下,我们知道红黑树的中序遍历出来是一个从小到大的有序序列
我们的++也是利用这个思想来的,但是中序遍历是一个看全局思想来的,但我们这里是要看局部,只考虑当前中序局部要访问的下⼀个结点。可以看成一个局部的左根右
为什么不能直接用中序遍历来写红黑树的迭代器
那是因为
随机访问需求 迭代器通常需要支持随机访问操作,这意味着可以在常数时间内访问任意一个元素。而中序遍历是线性的,不支持随机访问。
也不能更加灵活的访问容器,每次都要从根开始
我们现在来具体说一下代码

if (_it_node->_right != nullptr)//找右最小节点{Node* right_min = this->_it_node->_right;while (right_min->_left != nullptr){right_min = right_min->_left;}_it_node = right_min;}

这里是找除开当前节点,如果当前节点的右边不为空,我们就要一直找右边的最小节点(也就是最左节点)
为什么要这样找,我们可以联系下面的这个beign()这个函数来看看

typedef RBtreenode<T> node;
typedef RBtreeIterator<T, T&, T*> Iterator;
typedef RBtreeIterator<T, const T&, const T*> ConstIterator;
Iterator begin()
{node* cur = _root;while (cur != nullptr && cur->_left != nullptr){cur = cur->_left;}return Iterator(cur);
}

这个begin()返回的是一个迭代器类型,他是一直找红黑树的最小节点,找到之后用最小节点去构造一个迭代器类型,当找到最小节点的时候就代表以这个节点为基准他的左子树已经访问完了然后要去看看右子树,我们可以画图看看
在这里插入图片描述
然后我们来看看下面的代码

	else//右边访问完,代表以他为基本的左子树右子树都完了,就要向上走{Node* cur = _it_node;Node* parent = cur->_pre_node;//这里要找的是相对于儿子的父亲,父亲的右节点不是孩子的节点//如果一直是右节点就代表当前节点的左右子树都完了//如果找到父亲的左节点是孩子,就代表父亲还有右节点没访问完。while (parent != nullptr && parent->_right == cur){cur = parent;parent = cur->_pre_node;}_it_node = parent;}return *this;
}

当到15之后我们的第一个if进不去就要进else,走到else说明15的左右子树都访问完了,右边为空了,就要向上返回,去看看15的父亲,也就是10的左右子树访问完没有,15是10的右子树代表以10为基础的左右子树都访问完了,又让10去找他的父亲18,看看18的右子树是不是指向10的,但通过图片来看,18的左子树的指向10的,说明18的右子树还没有访问,然后就需要去访问18和18的右子树
在这里插入图片描述
总的来说就一句话:就要去找不是自己父亲的右边是指向自己的节点
下面来说说operator–

operator- -

–的逻辑正好相反,可以看成一个局部的右根左
我们先来看看end()

Iterator end()
{return Iterator(nullptr,_root);
}

首先end()代表末尾,我们这里直接用空表示的库里面是有一个哨兵位节点
就像这样
在这里插入图片描述
当然用空也可以实现这个功能
我们只需要特殊处理一下,end我们就想表示红黑树里面的最右节点,所以我们多再operator–里面多添加一种情况就行了,去找他的最右节点,但我们需要多传一个_root的参数进来

self operator--()
{//处理特殊情况if (this->_it_node == nullptr){Node* rightmost = _it_root;while (rightmost != nullptr && rightmost->_right != nullptr){rightmost = rightmost->_right;}_it_node = rightmost;}//这里和begin()类似//右根左else if (this->_it_node->_left != nullptr)//找左节点的最大节点{Node* left_max = _it_node->_left;while (left_max != nullptr && left_max->_right != nullptr){left_max = left_max->_right;}this->_it_node = left_max;}else//左边为空代表当前节点为基本的左右子树都访问完了{//就要往上面寻找不是父亲节点指向左边的孩子节点,要找父亲节点指向右边是孩子节点的父亲节点Node* cur = _it_node;Node* parent = cur->_pre_node;while (parent != nullptr && parent->_left == cur){cur = parent;parent = cur->_pre_node;}_it_node = parent;}return *this;
}

–就相当于反过来这里就不作过多说明了

迭代器整体代码

还有一些小的函数就不作过多说明了

template<class T,class ref,class ptr>
//迭代器就相当于一个节点的指针
struct RBtreeIterator//默认公有
{typedef RBtreenode<T> Node;typedef RBtreeIterator<T, ref, ptr> self;Node* _it_node;Node* _it_root;RBtreeIterator (Node* input_node, Node* root):_it_node(input_node), _it_root(root){}self operator++(){if (_it_node->_right != nullptr)//找右最小节点{Node* right_min = this->_it_node->_right;while (right_min->_left != nullptr){right_min = right_min->_left;}_it_node = right_min;}else//右边访问完,代表以他为基本的左子树右子树都完了,就要向上走{Node* cur = _it_node;Node* parent = cur->_pre_node;//这里要找的是相对于儿子的父亲,父亲的右节点不是孩子的节点//如果一直是右节点就代表当前节点的左右子树都完了//如果找到父亲的左节点是孩子,就代表父亲还有右节点没访问完。while (parent != nullptr && parent->_right == cur){cur = parent;parent = cur->_pre_node;}_it_node = parent;}return *this;}self operator--(){//处理特殊情况if (this->_it_node == nullptr){Node* rightmost = _it_root;while (rightmost != nullptr && rightmost->_right != nullptr){rightmost = rightmost->_right;}_it_node = rightmost;}//右根左else if (this->_it_node->_left != nullptr)//找左节点的最大节点{Node* left_max = _it_node->_left;while (left_max != nullptr && left_max->_right != nullptr){left_max = left_max->_right;}this->_it_node = left_max;}else//左边为空代表当前节点为基本的左右子树都访问完了{//就要往上面寻找不是父亲节点指向左边的孩子节点,要找父亲节点指向右边是孩子节点的父亲节点Node* cur = _it_node;Node* parent = cur->_pre_node;while (parent != nullptr && parent->_left == cur){cur = parent;parent = cur->_pre_node;}_it_node = parent;}return *this;}bool operator==(const self& input) const{return input._it_node == _it_node;}bool operator!=(const self& input) const{return input._it_node != _it_node;}ref operator*(){return this->_it_node->_data;}ptr operator->(){return &this->_it_node->_data;}
};

operator[]

这个是map比较有特色的一个功能,他支持插入,查询和修改,我们来看看是怎么实现的
来看看代码

value& operator[](const key& input_key)   // int 1   0 
{                                         //key    valuepair<iterator, bool> ret = Insert({ input_key,value() });iterator it = ret.first;return it->second;
}

首先要实现这个功能我们的插入的返回值需要修改一下
需要返回一个键值对
在这里插入图片描述
然后我们再来分析一下
首先我们用一个键值对ret来接受插入的返回值,假设插入10,(红黑树里面没有10),然后再插入一个value()的一个缺省值,相当于一个默认构造,int的就是0,我把这个插入的默认值理解成一个占位符 插入成功返回true,然后我们取这个返回值的first,也就是一个迭代器,然后这个迭代器也是pair类型的,我们再返回他的second,也就是返回里面存放的数据,然后就可以插入了,也就相当于it->second = “123”
在这里插入图片描述
那是怎么支持查找和修改的呢
当我们再次插入10时,insert就会返回一个false,然后同样返回一个迭代器
在这里插入图片描述

然后去取他的first再取second就可以完成修改了
这样就实现了operator[]的功能
以上就是利用红黑树封装map,和set,实现主要功能,有什么问题欢迎指正

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

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

相关文章

汽车网络安全 -- IDPS如何帮助OEM保证车辆全生命周期的信息安全

目录 1.强标的另一层解读 2.什么是IDPS 2.1 IDPS技术要点 2.2 车辆IDPS系统示例 3.车辆纵深防御架构 4.小结 1.强标的另一层解读 在最近发布的国家汽车安全强标《GB 44495》,在7.2节明确提出了12条关于通信安全的要求,分别涉及到车辆与车辆制造商云平台通信、车辆与车辆…

R语言机器学习论文(二):数据准备

禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍加载R包数据下载导入数据一、数据描述二、数据预处理(一)修改元素名称(二)剔除无关变量(三)缺失值检查(四)重复值检查(五)异常值检查三、描述性统计(一)连续变量数据情…

java基础语法光速入门

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文整理Java的基础语法部分 适合有编程基础的人快点掌握语法使用 没学过一两门语言的话。。还是不建议看了 极致的浓缩没有一点解释 注释 单行注释 // 多行注释 /**/ 数据类型 布尔型:true false 整型:int,lon…

【Linux】常用命令二

声明&#xff1a;以下内容均学习自《Linux就该这么学》一书。 1、cat 用于查看内容较少的纯文本文件。 参数-n可以显示行号。 2、more 用于查看内容较多的纯文本文件。 它会在最下面使用百分比的形式来提示你已经月读了多少内容&#xff0c;你可以使用空格键或回车键向下翻…

springboot利用easypoi实现简单导出Excel

vue springboot利用easypoi实现简单导出 前言一、easypoi是什么&#xff1f;二、使用步骤 1.传送门2.前端vue3.后端springboot 3.1编写实体类&#xff08;我这里是dto,也一样&#xff09;3.2控制层结尾 前言 今天玩了一下springboot利用easypoi实现excel的导出&#xff0c;以前…

合规性要求对漏洞管理策略的影响

讨论漏洞管理中持续面临的挑战&#xff0c;包括确定漏洞的优先级和解决修补延迟问题。 介绍合规性要求以及自动化如何简化漏洞管理流程。 您认为为什么尽管技术不断进步&#xff0c;但优先考虑漏洞和修补延迟等挑战仍然存在&#xff1f; 企业基础设施日益复杂&#xff0c;攻…

【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势?

【NLP高频面题 - LLM架构篇】旋转位置编码RoPE相对正弦位置编码有哪些优势&#xff1f; 重要性&#xff1a;⭐⭐⭐ &#x1f4af; NLP Github 项目&#xff1a; NLP 项目实践&#xff1a;fasterai/nlp-project-practice 介绍&#xff1a;该仓库围绕着 NLP 任务模型的设计、训练…

STM32 PWM波形详细图解

目录 前言 一 PWM介绍 1.1 PWM简介 1.2 STM32F103 PWM介绍 1.3 时钟周期与占空比 二.引脚映像关系 2.1引脚映像与寄存器 2.2 复用功能映像 三. PWM 配置步骤 3.1相关原理图 3.2配置流程 3.2.1 步骤一二&#xff1a; 3.2.2 步骤三&#xff1a; 3.2.3 步骤四五六七&#xff1a; …

洛谷 B2029:大象喝水 ← 圆柱体体积

【题目来源】https://www.luogu.com.cn/problem/B2029【题目描述】 一只大象口渴了&#xff0c;要喝 20 升水才能解渴&#xff0c;但现在只有一个深 h 厘米&#xff0c;底面半径为 r 厘米的小圆桶 &#xff08;h 和 r 都是整数&#xff09;。问大象至少要喝多少桶水才会解渴。 …

使用docker部署GBase8s数据库(jdk安装,docker安装,GBase部署)

jdk安装步骤 1.将压缩包上传到/opt/software 2.解压到/opt/module tar -zxvf jdk-8u212-linux-x64.tar.gz -C /opt/module 3.配置环境变量 3.1 在/etc/profile.d目录下创建my_env.sh sudo touch my_env.sh 3.2在文件中添加内容 sudo vim my_env…

嵌入式 C 编程:const 关键字 —— 打造稳定的常量空间

目录 一、const关键字的基本含义与用法 1.1. 修饰基本数据类型 1.2. 修饰指针 1.3. 修饰数组 1.4. 修饰结构体 二、const关键字在嵌入式编程中的优势 2.1. 提升代码可读性 2.2. 增强代码安全性 2.3. 优化内存使用 2.4. 促进模块化设计 2.5. 支持静态分析和测试 三、…

【k8s】kubelet 的相关证书

在 Kubernetes 集群中&#xff0c;kubelet 使用的证书通常存放在节点上的特定目录。这些证书用于 kubelet 与 API 服务器之间的安全通信。具体的位置可能会根据你的 Kubernetes 安装方式和配置有所不同&#xff0c;下图是我自己环境【通过 kubeadm 安装的集群】中的kubelet的证…

USB 声卡全解析:提升音频体验的得力助手

在当今数字化的时代&#xff0c;音频领域的追求愈发多元。无论是热衷聆听高品质音乐的爱好者&#xff0c;还是在专业音频工作中精雕细琢的人士&#xff0c;亦或是在游戏世界里渴望极致音效沉浸的玩家&#xff0c;都始终在寻觅能让音频体验更上一层楼的妙法。而 USB 声卡&#x…

git查看本地库对应的远端库的地址

git查看本地库对应的远端库的地址 git remote -v 如果想要查看特定的远端库的url地址&#xff0c;可以使用如下命令&#xff0c;其中origin是默认的远端库的名称&#xff0c;可以使用其他远端库的名称 get remote get-url origin

深入解析级联操作与SQL完整性约束异常的解决方法

目录 前言1. 外键约束与级联操作概述1.1 什么是外键约束1.2 级联操作的实际应用场景 2. 错误分析&#xff1a;SQLIntegrityConstraintViolationException2.1 错误场景描述2.2 触发错误的根本原因 3. 解决方法及优化建议3.1 数据库级别的解决方案3.2 应用层的解决方案 4. 友好提…

社区团购中 2+1 链动模式商城小程序的创新融合与发展策略研究

摘要&#xff1a;本文聚焦于社区团购这一新兴零售模式的发展态势&#xff0c;深入探讨 21 链动模式商城小程序与之融合的创新机制与应用策略。通过剖析社区团购的运营模式、优势特点以及发展现状&#xff0c;结合 21 链动模式商城小程序的功能特性&#xff0c;研究二者协同作用…

qt QGraphicsScale详解

1、概述 QGraphicsScale是Qt框架中提供的一个类&#xff0c;它提供了一种简单而灵活的方式在QGraphicsView框架中实现缩放变换。通过设置水平和垂直缩放因子、缩放中心点&#xff0c;可以创建各种缩放效果&#xff0c;提升用户界面的交互性和视觉吸引力。结合QPropertyAnimati…

Narya.ai正在寻找iOS工程师!#Mixlab内推

如果你对AI技术和iOS开发充满热情&#xff0c;这里有一个绝佳的机会加入一家专注于AI应用创新的初创公司。Narya.ai正在招聘iOS工程师&#xff0c;帮助他们开发下一代效率工具&#xff0c;旨在提升用户的日常生活效率与幸福感。 关于Narya.ai&#xff1a; 专注于AI应用层创新&a…

CSS学习记录03

CSS背景 CSS 背景属性用于定义元素的背景效果。 CSS background-color background-color属性指定元素的背景色。 页面的背景色设置如下&#xff1a; body {background-color: lightblue; } 通过CSS&#xff0c;颜色通常由以下方式指定&#xff1a; 有效的颜色名称-比如“…

基于 MVC 架构的 SpringBoot 高校行政事务管理系统:设计优化与实现验证

摘 要 身处网络时代&#xff0c;随着网络系统体系发展的不断成熟和完善&#xff0c;人们的生活也随之发生了很大的变化&#xff0c;人们在追求较高物质生活的同时&#xff0c;也在想着如何使自身的精神内涵得到提升&#xff0c;而读书就是人们获得精神享受非常重要的途径。为了…