利用红黑树封装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.xdnf.cn/news/33499.html

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

相关文章

自动化测试之等待方式详解

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 在自动化测试中&#xff0c;等待是一个重要的技术&#xff0c;用于处理页面加载、元素定位、元素状态改变等延迟问题。 等待能够确保在条件满足后再进行后续操…

重学SpringBoot3-WebClient配置与使用详解

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-WebClient配置与使用详解 1. 简介2. 环境准备2.1 依赖配置 3. WebClient配置3.1 基础配置3.2 高级配置3.3 retrieve()和exchange()区别 4. 使用示例4.1 …

8.16DEBUG——DOCKER相关,DOCKER启动异常

DOCKER启动异常 问题一 WSL启动出现异常&#xff0c;导致DOCKER都无法运行 首先执行wsl --shutdown&#xff0c;再重启 但是重启时依然出现如上问题 首先按照网上教程&#xff0c;尝试去掉环境变量中冗余错误的变量定义 但是并没有解决&#xff0c;执行如下命令&#xff0c…

如何利用内链策略提升网站的整体权重?

内链是谷歌SEO中常常被低估的部分&#xff0c;实际上&#xff0c;合理的内链策略不仅能帮助提升页面间的关联性&#xff0c;还可以增强网站的整体权重。通过正确的内链布局&#xff0c;用户可以更流畅地浏览你的网站&#xff0c;谷歌爬虫也能更快地抓取到更多页面&#xff0c;有…

2021数学分析【南昌大学】

2021 数学分析 求极限 lim ⁡ n → ∞ 1 n ( n + 1 ) ( n + 2 ) ⋯ ( n + n ) n \lim_{n \to \infty} \frac{1}{n} \sqrt [n]{(n+1)(n+2) \cdots (n+n)} n→∞lim​n1​n(n+1)(n+2)⋯(n+n) ​ lim ⁡ n → ∞ 1 n ( n + 1 ) ( n + 2 ) ⋯ ( n + n ) n = lim ⁡ n → ∞ ( n + …

【金猿CIO展】复旦大学附属中山医院计算机网络中心副主任张俊钦:推进数据安全风险评估,防范化解数据安全风险,筑牢医疗数据安全防线...

‍ 张俊钦 本文由复旦大学附属中山医院计算机网络中心副主任张俊钦撰写并投递参与“数据猿年度金猿策划活动——2024大数据产业年度优秀CIO榜单及奖项”评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 数据要素时代&#xff0c;医疗数据已成为医院运营与决策的重要基石…

Cocos Json

类定义&#xff1a; export class PersonalInformation {public name: string;public age: number;public nationality: string;public gender: string;public height: number;public constructor(name: string, age: number, nationality: string, gender: string, height: n…

Qt开发技巧(二十四)滚动部件的滑动问题,Qt设置时区问题,自定义窗体样式不生效问题,编码格式问题,给按钮左边加个图,最小化后的卡死假象

继续记录一些Qt开发中的技巧操作&#xff1a; 1.滚动部件的滑动问题 再Linux嵌入式设备上&#xff0c;有时候一个页面的子部件太多&#xff0c;一屏放不下是需要做页面滑动&#xff0c;可以使用“QScrollArea”控件&#xff0c;拖来一个“QScrollArea”控件&#xff0c;将子部件…

Prime1_解法一:cms渗透 内核漏洞提权

Prime1_解法一&#xff1a;cms渗透 & 内核漏洞提权 文章目录 Prime1_解法一&#xff1a;cms渗透 & 内核漏洞提权信息收集主机发现nmap扫描tcp扫描tcp详细扫描22&#xff0c;80端口udp扫描漏洞脚本扫描 目录爆破dirsearch Web渗透wfuzz常见的 wfuzz 过滤器&#xff1a; …

保护数字资产:iOS 加固在当前安全环境中的重要性

随着互联网和手机的发展&#xff0c;APP在我们的日常生活中已经变得无处不在&#xff0c;各大平台的应用程序成为了黑客攻击的主要目标。尤其在 2024 年&#xff0c;随着数据泄露和隐私侵犯事件的频发&#xff0c;手机应用的安全问题再次成为公众关注的焦点。近期&#xff0c;多…

Qt Designer Ui设计 功能增加

效果展示 输入密码&#xff0c;密码错误&#xff0c;弹出提示 密码正确&#xff0c;弹出提示并且关闭原窗口 代码&#xff08;只提供重要关键主代码&#xff09;lxh_log.py代码&#xff1a; import sysfrom PySide6.QtWidgets import QApplication, QWidget, QPushButtonfrom …

基于Transformer的编码器-解码器图像描述模型在AMD GPU上的应用

Transformer based Encoder-Decoder models for image-captioning on AMD GPUs — ROCm Blogs 图像描述&#xff0c;即基于生成式人工智能&#xff08;GenAI&#xff09;自动生成简洁的图像文本描述&#xff0c;在现实世界中有着非常重要的应用。例如&#xff0c;图像描述可以为…

AI技术在电商行业中的应用与发展

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

项目-02-数学学院后台项目开发过程中的问题总结

目录 一、后台&#xff08;pc端&#xff0c;vue2&#xff09;1. dialog对话框被黑色蒙层盖住2. 将前端表格导出为word文档3. 在线查看、下载 .docx、.doc、.pdf文档 一、后台&#xff08;pc端&#xff0c;vue2&#xff09; 1. dialog对话框被黑色蒙层盖住 问题&#xff1a; d…

大语言模型技术相关知识-笔记整理

系列文章目录 这个系列攒了很久。主要是前段之间面试大语言模型方面的实习&#xff08;被拷打太多次了&#xff09;&#xff0c;然后每天根据面试官的问题进行扩展和补充的这个笔记。内容来源主要来自视频、个人理解以及官方文档中的记录。方便后面的回顾。 文章目录 系列文章…

【计算机网络】实验11:边界网关协议BGP

实验11 边界网关协议BGP 一、实验目的 本次实验旨在验证边界网关协议&#xff08;BGP&#xff09;的实际作用&#xff0c;并深入学习在路由器上配置和使用BGP协议的方法。通过实验&#xff0c;我将探索BGP在不同自治系统之间的路由选择和信息交换的功能&#xff0c;理解其在互…

HTTP协议图--HTTP 报文实体

1. HTTP 报文实体概述 HTTP 报文结构 大家请仔细看看上面示例中&#xff0c;各个组成部分对应的内容。 接着&#xff0c;我们来看看报文和实体的概念。如果把 HTTP 报文想象成因特网货运系统中的箱子&#xff0c;那么 HTTP 实体就是报文中实际的货物。 报文&#xff1a;是网络…

PCL DipG-Seg 地面分割实现

DipG-Seg采用基于像素的图像方法,将点云投影到两个图像面,经过投影图像生成,图像预分割、图像精细分割、标签投票等步骤,完成对于地面的分割。验证后其分割效果优于patchwork++等传统算法,16线激光雷达可以达到200hz的速度。代码可以由单模态激光雷达数据扩展到多模态点云…

MySQL大小写敏感、MySQL设置字段大小写敏感

文章目录 一、MySQL大小写敏感规则二、设置数据库及表名大小写敏感 2.1、查询库名及表名是否大小写敏感2.2、修改库名及表名大小写敏感 三、MySQL列名大小写不敏感四、lower_case_table_name与校对规则 4.1、验证校对规则影响大小写敏感4.1、验证校对规则影响排序 五、设置字段…

4.5 TCP 报文段的首部格式

欢迎大家订阅【计算机网络】学习专栏&#xff0c;开启你的计算机网络学习之旅&#xff01; 文章目录 前言1 TCP 报文段的基本结构2 固定部分2.1 源端口与目的端口2.2 序号2.3 确认号2.4 数据偏移2.5 保留字段2.6 控制位2.7 窗口2.8 检验和2.9 紧急指针 3 可变部分3.1 选项3.2 填…