当前位置: 首页 > news >正文

利用红黑树封装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,实现主要功能,有什么问题欢迎指正

相关文章:

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

如果不知道红黑树是什么的时候可以去看看这个红黑树 思路 首先我们可以把封装分为两个层面理解&#xff0c;上层代码就是set,和map&#xff0c;底层就是红黑树 就相当于根据红黑树上面套了两个map,set的壳子&#xff0c;像下面这张图一样 对于map和set&#xff0c;map里面存…...

网络(TCP)

目录 TCP socket API 详解 套接字有哪些类型&#xff1f;socket有哪些类型&#xff1f; 图解TCP四次握手断开连接 图解TCP数据报结构以及三次握手&#xff08;非常详细&#xff09; socket缓冲区以及阻塞模式详解 再谈UDP和TCP bind(): 我们的程序中对myaddr参数是这样…...

CSS 选择器的优先级

一、基本概念 CSS 选择器的优先级决定了在样式冲突时&#xff0c;哪个样式规则将被应用到 HTML 元素上。通过理解 CSS 选择器的优先级&#xff0c;可以更好地控制网页元素的样式&#xff0c;避免样式冲突。 二、优先级计算规则 1. 内联样式 内联样式具有最高的优先级。 &l…...

留学生数学辅导作业随机过程高等线性代数概率论微积分优化统计

针对留学生数学辅导作业中的随机过程、高等线性代数、概率论、微积分、优化以及统计等科目&#xff0c;以下是一些详细的辅导建议和资源概述&#xff1a; 一、随机过程 概念理解&#xff1a; 随机过程是研究随机现象随时间演变的数学分支。它涉及概率论和数理统计的知识&#…...

移动机器人课程建图实验-ROSbug汇总

问题1描述 $ rosrun robot_state_publisher robot_state_publisher [ERROR] [1733131886.474757207]: [registerPublisher] Failed to contact master at [localhost:11311]. Retrying...解决方案 这个错误信息表明 robot_state_publisher 节点无法联系到 ROS master。通常&…...

小家电出海,沃丰科技助力保障售后服务的及时性与高效性

随着全球化步伐的加快&#xff0c;小家电行业也逐渐迈向国际市场&#xff0c;面向全球消费者提供服务。然而&#xff0c;跨国界的销售和服务挑战也随之而来&#xff0c;尤其是售后服务的及时性与高效性成为了企业亟需解决的问题。沃丰科技凭借其全渠道在线客服、工单系统和视频…...

vscode 如何支持点击跳转函数,以C++为例,Python等其它编程语言同理,Visual Studio Code。

VScode(Visual Studio Code)按住Ctrl鼠标左键&#xff0c;没法跳转到对应的函数怎么办。 如下图所示 1、点击有四个小方块的图标 2、输入C&#xff08;如果你的编程语言是C&#xff0c;其它的就输其它的&#xff09; 3、找到C Extension&#xff08;其它编程语言&#xff0…...

创建子类对象时,会创建父类对象吗

一、查询网上的结论&#xff1a; 创建子类对象时&#xff0c; 会先调用子类构造方法对子类对象进行初始化&#xff0c;子类构造方法的第一行又会调用父类构造方法对父类进行初始化(不会创建父类对象&#xff0c; 但是会在子类对象的内存空间中开辟一块被包含的内存空间存储父类…...

华为、华三交换机纯Web下如何创关键VLANIF、操作STP参数

华为交换机WEB操作 使用的是真机S5735&#xff0c;目前主流的版本都适用&#xff08;V1R5~V2R1的就不在列了&#xff0c;版本太老了&#xff0c;界面完全不一样&#xff0c;这里调试线接的console口&#xff0c;电脑的网络接在ETH口&#xff09; 「模拟器、工具合集」复制整段内…...

MongoDB分片集群架构实战

分片集群架构 分片简介 分片&#xff08;shard&#xff09;是指在将数据进行水平切分之后&#xff0c;将其存储到多个不同的服务器节点上的一种扩展方式。分片在概念上非常类似于应用开发中的“水平分表”。不同的点在于&#xff0c;MongoDB本身就自带了分片管理的能力&#…...

架构 | 调优 - [zookeeper]

INDEX 0 实际使用的 zoo.cfg1 基础知识1.1 官网文档1.2 日志相关配置1.3 tick 时间 0 实际使用的 zoo.cfg ### 时间配置 ### 一个tick&#xff08;滴答&#xff09;的毫秒数&#xff0c;时间单位&#xff0c;可以认为是心跳时间 tickTime2000 ### follower 连接 leader 并与之…...

威联通-004 安装photoview相册应用Docker镜像

文章目录 前言准备MariaDB 10phpMyAdminphotoview 安装步骤1.安装MariaDB 10和phpMyAdmin2.初始安装MariaDB 103.进入phpMyAdmin添加账户4.手动下载photoview的Docker库注意&#xff1a;安装 phpMyAdmin 报错5.配置photoview6.容器安装成功之后进入photoview注意&#xff1a;这…...

Github clone 的时候出现Error in the HTTP2 framing layer错误

解决方案 github鉴权认证&#xff0c;打开gitbash&#xff0c;并输入 ssh-keygen -t rsa -C "emailicjs.cc" 执行后会在 .ssh 目录生产两个文件&#xff1a;id_rsa&#xff08;私有密钥&#xff09;和id_rsa.pub&#xff08;公开密钥&#xff09; 直接默认回车执行…...

SpringBoot中@Import和@ImportResource和@PropertySource

1. Import Import注解是引入java类&#xff1a; 导入Configuration注解的配置类&#xff08;4.2版本之前只可以导入配置类&#xff0c;4.2版本之后也可以导入普通类&#xff09;导入ImportSelector的实现类导入ImportBeanDefinitionRegistrar的实现类 SpringBootApplication…...

OpenCV 简介与安装方法

大家好啊&#xff0c;我是董董灿。 如果你在做计算机视觉相关的工作&#xff0c;肯定少不了使用 OpenCV 库。 在《计算机视觉专栏》的传统计算机视觉部分&#xff0c;我曾经使用 OpenCV 进行了很多图像的处理&#xff0c;比如边缘检测。 刚好最近在整理一份文稿&#xff0c;…...

pycharm基本库安装的几种方法

1、pycharm基本库安装的几种方法 1&#xff09;一次性设置下载源 cmd窗口&#xff08;管理员方式&#xff09;.输入以下命令&#xff1a; pip config set global.index-url http://pypi.tuna.tsinghua.edu.cn/simple pip config set global.trusted-host pypi.tuna.tsinghu…...

安装更新upgrade导致ubuntu崩溃

安装更新导致ubuntu崩溃 前言uuid编不过&#xff0c;导致的崩溃 记录一些ubuntu崩溃的过程。 目前只有一个&#xff0c;以后遇到都放在这里&#xff0c;以提醒自己。 前言 如果从10000年看现在的linux&#xff0c;不是说不完美&#xff0c;而是糟透了。 linux的版本号&#xf…...

数学建模选MATLAB还是Python?

选择MATLAB还是Python进行数学建模&#xff0c;取决于多个因素&#xff0c;包括你的具体需求、个人偏好、项目要求以及你已有的技能。以下是一些考虑因素&#xff1a; 1. 易用性&#xff1a; • MATLAB&#xff1a;对于数学和工程问题&#xff0c;MATLAB提供了一个非常直观和…...

python数组增加元素

append、appext、insert&#xff0c;在某位置插入insert最在行。 (笔记模板由python脚本于2024年12月04日 19:41:46创建&#xff0c;本篇笔记适合python基础编程的coder翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖…...

【笔记】离散数学 1-3 章

1. 数理逻辑 1.1 命题逻辑的基本概念 1.1.1 命题的概念 命题&#xff08;Proposition&#xff09;&#xff1a;是一个陈述句&#xff0c;它要么是真的&#xff08;true&#xff09;&#xff0c;要么是假的&#xff08;false&#xff09;&#xff0c;但不能同时为真和假。例如…...

递归提示策略:构建高效可靠的自然语言转SQL系统

1. 引言&#xff1a;当自然语言撞上结构化查询作为一名和数据打了十几年交道的“老码农”&#xff0c;我见过太多业务同学对着数据库“望洋兴叹”的场景。他们能清晰地用中文描述需求&#xff1a;“帮我找出上个月华东地区销售额超过10万&#xff0c;但客户满意度低于平均值的所…...

原神祈愿数据分析终极方案:genshin-wish-export架构革命与效能倍增

原神祈愿数据分析终极方案&#xff1a;genshin-wish-export架构革命与效能倍增 【免费下载链接】genshin-wish-export Easily export the Genshin Impact wish record. 项目地址: https://gitcode.com/GitHub_Trending/ge/genshin-wish-export 你是否曾在多设备间苦苦同…...

gd32f303烧录提示Flash Timeout. Reset the Target and try it again.;

出现这个原因&#xff0c;是因为我在代码中使用了&#xff1a; ob_security_protection_config(FMC_USPC); // 开启保护 保护装置&#xff0c;导致烧录的时候出现 Flash Timeout. Reset the Target and try it again.&#xff1b; Error: Flash Download failed - "Cort…...

AI 时代的平台工程

两个月前&#xff0c;正是我 Aha moment 不断&#xff0c;多巴胺爆炸的时刻&#xff0c;每天都会记录下很多灵感和想法&#xff0c;准备在未来写成文章&#xff0c;或者开发成工具。其中有一条是这样的&#xff1a;AI 时代的平台工程&#xff08;CLISkillMCP&#xff0c;可访问…...

测试工程师如何进行测试计划制定?这5个步骤让你的计划更合理

对于软件测试从业者而言&#xff0c;一份合理可行的测试计划是项目测试工作的核心纲领&#xff0c;它不仅决定了测试活动的范围、方向与资源分配&#xff0c;更直接影响着项目的交付质量与进度管控。很多初级测试工程师常常将测试计划等同于测试时间列表&#xff0c;要么写得过…...

DeepSeek LeetCode 2561. 重排水果 Java实现

LeetCode 2561. 重排水果题目分析有两个长度为 n 的数组 basket1 和 basket2&#xff0c;每个数组包含若干水果。每次操作可以交换两个数组中的任意水果&#xff0c;花费为这两个水果中较小的那个值。目标是使两个数组中的水果种类和数量完全相同&#xff08;即两个数组重排后相…...

【Typescript】13-tsconfig与工程化实践

tsconfig 与工程化实践 很多人学 TypeScript 时&#xff0c;会把注意力几乎全部放在语法上&#xff1a;泛型会不会写、infer 看不看得懂、工具类型会不会用。可真正在工程里决定 TypeScript 上限的&#xff0c;往往不是这些&#xff0c;而是 tsconfig.json。因为它决定了编译器…...

我希望项目能像lisp那样只有少量而又足够的关键字,不希望后面再添加关键字,那样太繁琐了。 后面可以使用函数、宏等方式增加更多的功能和函数

补充一点设计需求&#xff0c;我希望项目能像lisp那样只有少量而又足够的关键字&#xff0c;不希望后面再添加关键字&#xff0c;那样太繁琐了。 后面可以使用函数、宏等方式增加更多的功能和函数关键在于‌将语法结构本身作为核心&#xff0c;而非定义大量特殊的关键字‌。这可…...

中小型企业服务器常见隐患 + 标准化运维维护方案总结

做运维多年&#xff0c;接触过大量中小企业服务器&#xff0c;总结几个最常见、最致命的问题&#xff1a;1、服务器常年不关机、不巡检&#xff0c;磁盘爆满无人察觉&#xff1b;2、对外开放端口过多&#xff0c;没有安全策略&#xff0c;极易被暴力破解&#xff1b;3、数据库无…...

vscode+stm32+embedded ide+cortex debug+gcc

用stm32cubemx生成项目。下载三个软件&#xff0c;设置环境变量 openocd是仿真用&#xff0c;gcc-arm-none-eabi-10.3是编译用&#xff0c;w64evkit只用其中的make.exe根据生成的makefile文件&#xff0c;添加c源文件&#xff0c;包含目录&#xff0c;startup文件&#…...