【C++】红黑树封装map—set

         1 .关联式容器

C++中的map是标准模板库(STL)中的一种关联容器,它存储的是键值对(key-value pairs),其中每个键都是唯一的。

键值对:

  • 用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。

创建键值对对象:

  1. pair<T1, T2>(x, y) 使用构造函数的方式构造一个匿名对象
  2. make_pair(x, y) 是一个函数模板,其中返回的是一个pair的匿名对象

         2 .红黑树模板参数

  • set是K模型的容器,而map是KV模型的容器,这里我们如何用一棵KV模型的红黑树同时实现map和set。
  • 这里我们就需要控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T。
template<class K, class T>
class RBTree

T模板参数可能只是键值Key,也可能是由Key和Value共同构成的上述键值对。

  • 如果是set容器,那么它传入底层红黑树的模板参数就是两个Key
template<class K>
class set
{
public://其他内容
private:RBTree<K, K> _t;
};

如果是map容器,那么它传入底层红黑树的模板参数就是Key以及Key和Value构成的键值对:

template<class K, class V>
class map
{
public://其他内容
private:RBTree<K, pair<K, V>> _t;
};

问题:能不能不要红黑树的第一个模板参数,只保留第二个模板参数?

  • 红黑树的第一个模板参数是不能省略的。
  • 对于set容器来说,省略红黑树的第一个参数没问题,因为set传入红黑树的第二个参数与第一个参数是一样的。
  • 但是对于map容器来说就不行了,因为map容器所提供的接口当中有些是只要求给出键值Key的,比如find和erase

结点当中存储的数据

  • 对于set容器来说,底层红黑树结点当中存储K和T都是一样的,
  • 但是对于map容器来说,底层红黑树就只能存储T了。
  • 由于底层红黑树并不知道上层容器到底是map还是set,因此红黑树的结点当中直接存储T就行了。
  • 因此,当上层容器是set的时候,结点当中存储的是键值Key;
  • 当上层容器是map的时候,结点当中存储的就是<K, V>键值对。
//红黑树节点
template<class T>
struct RBTreeNode
{//三叉链RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;//存储的数据T _data;//结点的颜色int _col; //红/黑//构造函数RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};

         3 . 模板参数中增加仿函数

  • 由于结点当中存储的是T,这个T可能是Key,也可能是<Key, Value>键值对。那么当我们需要进行结点的键值比较时。就需要知道用什么数据比较大小。
  • 当上层容器是set的时候T就是键值Key,直接用T进行比较即可,
  • 当上层容器是map的时候,我们需要从<Key, Value>键值对当中取出键值Key后,再用Key值进行比较。或者按照其他方法
  • 上层容器map需要向底层红黑树提供一个仿函数,用于获取T当中的键值Key。

所以:当底层红黑树当中需要比较两个结点的键值时,就可以通过这个仿函数来获取T当中的键值了。

template<class K, class V>
class map
{//仿函数——重载()struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key{return kv.first;}};
private:RBTree<K, pair<K, V>, MapKeyOfT> _t;
};
  • 由于我们的map和set都是用的同一颗树,所以对于底层红黑树来说,它不知道上层容器是map还是set。
  • 因此当需要进行两个结点键值的比较时,底层红黑树都会通过传入的仿函数获取键值Key,进而进行两个结点键值的比较。
  • 所以,set容器也需要向底层红黑树传入一个仿函数并且是必不可少的。因为与map用的同一颗树。
template<class K>
class set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值Key{return key;}};
private:RBTree<K, K, SetKeyOfT> _t;
};
  • 此时,set容器传入底层红黑树的就是set的仿函数,map容器传入底层红黑树的就是map的仿函数。
  • 所以当底层红黑树需要进行两个结点之间键值的比较时,都会通过传入的仿函数来获取相应结点的键值,然后再进行比较。

示例:

  • //查找函数
    iterator Find(const K& key)
    {KeyOfT kot;Node* cur = _root;while (cur){
    //kot(x)就是获取x中比较大小的一部分if (key < kot(cur->_data)){cur = cur->_left; //找右孩子}else if (key > kot(cur->_data)){cur = cur->_right; //找左孩子}else //找到了目标结点就返回该节点{return iterator(cur); }}return end(); //查找失败
    }
    

当然,所有进行结点键值比较的地方,均需要通过仿函数获取对应结点的键值后再进行键值的比较。 

         4 . 正向迭代器

  • map的迭代器是一种指针,指向map中的元素。map的迭代器有两种类型:const_iterator和iterator。const_iterator用于只读访问,而iterator可以读写。
  • 红黑树的正向迭代器实际上就是对结点指针进行了封装,因此在正向迭代器当中实际上就只有一个成员变量,那就是正向迭代器所封装结点的指针。

迭代器的特性

  • map的迭代器遵循双向迭代器的要求,可以向前和向后遍历

  • 迭代器失效:迭代器在map被修改时(插入或删除)可能会失效。

  • map的迭代器可以通过解引用操作符(*)来访问指向的元素,或者使用箭头操作符(->)来访问元素的成员。

//正向迭代器
template<class T, class Ref, class Ptr>
struct __TreeIterator
{
//取别名 方便后面写typedef RBTreeNode<T> Node; typedef __TreeIterator<T, Ref, Ptr> Self; Node* _node; //正向迭代器所封装结点的指针//通过一个结点的指针便可以构造出一个正向迭代器。
//构造函数
__TreeIterator(Node* node):_node(node) //根据所给结点指针构造一个正向迭代器
{}};

对正向迭代器进行解引用操作时,我们直接返回对应结点数据的引用:

Ref operator*()
{return _node->_data; //返回结点数据的引用
}

对正向迭代器进行->操作时,我们直接返回对应结点数据的指针:

Ptr operator->()
{return &_node->_data; //返回结点数据的指针
}

重载==和!=运算符时,直接判断两个迭代器所封装的结点,这个节点Node里面实现过:

//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}

++和–运算符的重载:

  • 实现红黑树的正向迭代器时,一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。

实现思路如下:

  • 如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点。
  • 如果当前结点的右子树为空,则++操作后应该在该结点的祖先结点中,找到孩子不在父亲右的祖先。
//前置++
Self operator++()
{if (_node->_right) //结点的右子树不为空{//寻找该结点右子树当中的最左结点Node* left = _node->_right;while (left->_left){left = left->_left;}_node = left; //++后变为该结点}else //结点的右子树为空{//寻找孩子不在父亲右的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent&&cur == parent->_right){cur = parent;parent = parent->_parent;}_node = parent; //++后变为该结点}return *this;
}
  • 实现红黑树的正向迭代器时,一个结点的正向迭代器进行 - - 操作后,应该根据红黑树中序遍历的序列找到当前结点的前一个结点。

实现思路:

  • 如果当前结点的左子树不为空,则–操作后应该找到其左子树当中的最右结点。
  • 如果当前结点的左子树为空,则–操作后应该在该结点的祖先结点中,找到孩子不在父亲左的祖先。
//前置--
Self operator--()
{if (_node->_left) //结点的左子树不为空{//寻找该结点左子树当中的最右结点Node* right = _node->_left;while (right->_right){right = right->_right;}_node = right; //--后变为该结点}else //结点的左子树为空{//寻找孩子不在父亲左的祖先Node* cur = _node;Node* parent = cur->_parent;while (parent&&cur == parent->_left){cur = parent;parent = parent->_parent;}_node = parent; //--后变为该结点}return *this;
}

 正向迭代器实现后,我们需要在红黑树的实现当中进行迭代器类型的typedef。

注意:这棵树是要被外层的map和set封装的,他们要看见树中的iterator才能使用。

  • 因此,为了让外部能够使用typedef后的正向迭代器类型iterator,我们需要在public区域进行typedef。

 实现成员函数begin和end:

  • begin函数返回中序序列当中第一个结点的正向迭代器,即最左结点。
  • end函数返回中序序列当中最后一个结点下一个位置的正向迭代器,这里直接用空指针构造一个正向迭代器。
template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node; //结点的类型
public:typedef __TreeIterator<T, T&, T*> iterator; //正向迭代器iterator begin(){//寻找最左结点Node* left = _root;while (left&&left->_left){left = left->_left;}//返回最左结点的正向迭代器return iterator(left);}iterator end(){//返回由nullptr构造得到的正向迭代器return iterator(nullptr);}
private:Node* _root; //红黑树的根结点
};

C++标准库中的实现: 

C++STL库当中实现红黑树时,在红黑树的根结点处增加了一个头结点

头结点的左指针指向红黑树当中的最左结点右指针指向红黑树当中的最右结点父指针指向红黑树的根结点

在该结构下:

  • 实现begin()时,直接用头结点的左孩子构造一个正向迭代器即可。
  • 实现rbegin()时,直接用头结点的右孩子构造一个反向迭代器即可(实际是先用该结点构造一个正向迭代器,再用正向迭代器构造出反向迭代器)。
  • 实现end()和rend()时,直接用头结点构造出正向和反向迭代器即可。
  • 此后,通过对逻辑的控制,就可以实现end()进行–操作后得到最后一个结点的正向迭代器。

实现该结构需要更改当前很多函数的逻辑,例如:

  • 插入结点时,若插入到了红黑树最左结点的左边,或最右结点的右边,此时需要更新头结点左右指针的指向

         5 .反向迭代器

  • 红黑树的反向迭代器实际上就是正向迭代器的一个封装,前面我们在list的时候讲过过。红黑树的反向迭代器就是一个迭代器适配器。
  • 在反向迭代器当中只有一个成员变量,那就是反向迭代器封装的正向迭代器。反向迭代器中的成员函数,都是通过调用正向迭代器对应的函数来完成相应功能的。
//反向迭代器---迭代器适配器
template<class Iterator, class Ref, class Ptr>
struct ReverseIterator
{typedef ReverseIterator<Iterator, Ref, Ptr> Self;//反向迭代器所封装的正向迭代器Iterator _it;
正向迭代器构造一个反向迭代器ReverseIterator(Iterator it):_it(it){}Ref operator*(){Iterator tmp = _it;return *(--tmp);}Ptr operator->(){//return _it->;//return _it.operator->();return &(operator*());}Self& operator++(){--_it;return *this;}Self& operator--(){++_it;return *this;}bool operator!=(const Self& s){return _it != s._it;}
};

有了反向迭代器后,我们需要在红黑树的实现当中进行迭代器类型的typedef,并在红黑树当中实现成员函数rbegin和rend:

template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node; 
public:typedef ReverseIterator<iterator> reverse_iterator; //反向迭代器reverse_iterator rbegin(){//寻找最右结点Node* right = _root;while (right&&right->_right){right = right->_right;}//返回最右结点的反向迭代器return reverse_iterator(iterator(right));}reverse_iterator rend(){//返回由nullptr构造得到的反向迭代器return reverse_iterator(iterator(nullptr));}
private:Node* _root; 
};

         6 .set的模拟实现

注意:我们需要将键值对的第一个Key加上const,在map和set中我们不能取修改键,值可以修改。因为修改了键后,他的位置就可能需要重新调整了。

template<class K>
class set
{//仿函数struct SetKeyOfT{const K& operator()(const K& key) //返回键值Key{return key;}};
public:typedef typename RBTree<K, K, SetKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, K, SetKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}//插入函数pair<iterator, bool> insert(const K& key){return _t.Insert(key);}//删除函数void erase(const K& key){_t.Erase(key);}//查找函数iterator find(const K& key){return _t.Find(key);}
private:RBTree<K, const K, SetKeyOfT> _t;
};

         7 .map的模拟实现

template<class K, class V>
class map
{//仿函数struct MapKeyOfT{const K& operator()(const pair<K, V>& kv) //返回键值对当中的键值Key{return kv.first;}};
public:typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator; //正向迭代器typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::reverse_iterator reverse_iterator; //反向迭代器iterator begin(){return _t.begin();}iterator end(){return _t.end();}reverse_iterator rbegin(){return _t.rbegin();}reverse_iterator rend(){return _t.rend();}//插入函数pair<iterator, bool> insert(const pair<const K, V>& kv){return _t.Insert(kv);}//[]运算符重载函数V& operator[](const K& key){pair<iterator, bool> ret = insert(make_pair(key, V()));iterator it = ret.first;return it->second;}//删除函数void erase(const K& key){_t.Erase(key);}//查找函数iterator find(const K& key){return _t.Find(key);}
private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;
};

         8 .map—set总体代码​​​​​​​

map_set · HYD/C++ - 码云 - 开源中国

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

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

相关文章

系统思考—结构影响行为

最近在和一些企业领导者交流时&#xff0c;发现一个共性——创始人都非常厉害&#xff01;战略清晰、点子多、方向准&#xff0c;简直就是企业的“定海神针”。但往往问题在了下一层级&#xff1a;如何把创始人的智慧传承下去&#xff0c;甚至复制到团队里&#xff0c;这成了一…

定时器简介

TIM(Timer定时器)简介 在第一部分,我们主要讲的是定时器基本定时的功能&#xff0c;也就是定一个时间&#xff0c;然后让定时器每隔这个时间产生一个中断&#xff0c;来实现每隔一个固定时间执行一段程序的目的&#xff0c;比如你要做个时钟、秒表&#xff0c;或者使用一些程序…

快排和归并

目录 前言 快速排序 相遇位置一定比key小的原理&#xff08;大&#xff09;&#xff1a; 避免效率降低方法&#xff08;快排优化&#xff09; 三数取中&#xff08;选key优化&#xff09; 小区间优化 hoare版本快排 挖坑法快排 前后指针快排 非递归快排 归并排序 非递…

代码段数据段的划分

DPL DPL存储在段描述符中&#xff0c;规定访问该段的权限级别(Descriptor Privilege Level) CPL CPL是当前进程的权限级别(Current Privilege Level)&#xff0c;是当前正在指向的代码段所在段的成绩&#xff0c;也就是CS段的DPL RPL RPL说明的是进程对段访问的请求权限(Re…

Essential Cell Biology--Fifth Edition--Chapter one (8)

1.1.4.6 The Cytoskeleton [细胞骨架] Is Responsible for Directed Cell Movements 细胞质基液不仅仅是一种无结构的化学物质和细胞器的混合物[soup]。在电子显微镜下&#xff0c;我们可以看到真核细胞的细胞质基液是由长而细的丝交叉而成的。通常[Frequently]&#xff0c;可…

开源科学工程技术软件介绍 – EDA工具KLayout

link 今天向各位知友介绍的 KLayout是一款由德国团队开发的开源EDA工具。 KLayout是使用C开发的&#xff0c;用户界面基于Qt。它支持Windows、MacOS和Linux操作系统。安装程序可以从下面的网址下载&#xff1a; https://www.klayout.de/build.html KLayout图形用户界面&…

【设计模式】行为型模式(五):解释器模式、访问者模式、依赖注入

《设计模式之行为型模式》系列&#xff0c;共包含以下文章&#xff1a; 行为型模式&#xff08;一&#xff09;&#xff1a;模板方法模式、观察者模式行为型模式&#xff08;二&#xff09;&#xff1a;策略模式、命令模式行为型模式&#xff08;三&#xff09;&#xff1a;责…

(长期更新)《零基础入门 ArcGIS(ArcMap) 》实验一(下)----空间数据的编辑与处理(超超超详细!!!)

续上篇博客&#xff08;长期更新&#xff09;《零基础入门 ArcGIS(ArcMap) 》实验一&#xff08;上&#xff09;----空间数据的编辑与处理&#xff08;超超超详细&#xff01;&#xff01;&#xff01;&#xff09;-CSDN博客 继续更新 本篇博客内容为道路拓扑检查与修正&#x…

Unity3D 完整直升机控制器(虚拟仿真级别)

采用了MVC框架&#xff0c;以四轴驱动的方式对直升机的启动、飞行做了仿真模拟&#xff0c;包括但不限于参数设置、启动发动机和旋翼、数据显示、HUD、UI、升降、水平移动、转弯等。 文末有完整的工程资源链接。 1.旋翼 直升机飞行过程中&#xff0c;有顶部的主旋翼和尾部的尾…

yum工具的学习

Linux下安装软件的方法 1.源代码安装 2.rmp包安装 3.包管理器进行安装 --- yum/apt Linux下载软件的过程 操作系统的好坏评估 -- 生态问题 yum具体操作 Linux软件安装所有人都能用&#xff0c;是以other的身份去执行可执行程序 文件拷贝&#xff08;sudo&#xff09;-- &g…

Linux:进程的优先级 进程切换

文章目录 前言一、进程优先级1.1 基本概念1.2 查看系统进程1.3 PRI和NI1.4 调整优先级1.4.1 top命令1.4.2 nice命令1.4.3 renice命令 二、进程切换2.1 补充概念2.2 进程的运行和切换步骤&#xff08;重要&#xff09; 二、Linux2.6内核进程O(1)调度队列&#xff08;重要&#x…

MongoDB在现代Web开发中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 MongoDB在现代Web开发中的应用 引言 MongoDB 概述 定义与原理 发展…

爬取链家二手房房价数据存入mongodb并进行分析

感谢您的关注&#xff01;需要完整源码评论区获取~ 【实验目的】 1. 使用 python 将爬虫数据存入 mongodb&#xff1b; 2. 使用 python 读取 mongodb 数据并进行可视化分析。 【实验原理】 MongoDB 是文档数据库&#xff0c;采用 BSON 的结构来存储数据。在文档中可嵌套其…

Solana 区块链的技术解析及未来展望 #dapp开发#公链搭建

随着区块链技术的不断发展和应用场景的扩展&#xff0c;性能和可拓展性成为各大公链竞争的关键因素。Solana&#xff08;SOL&#xff09;因其高吞吐量、低延迟和低成本的技术特性&#xff0c;在众多区块链项目中脱颖而出&#xff0c;被誉为“以太坊杀手”之一。本文将从技术层面…

Vue通过file控件上传文件到Node服务器

功能&#xff1a; 多文件同步上传、拖动上传、实时上传进度条、上传前的删除文件、原生file控件的美化 搁置的功能: 取消上传(上传过程中取消,即取消网络请求abort)、上传文件夹、大文件切片、以及很多限制条件未处理(重复上传、文件格式。。。) bug: 文件总大小(。。。竟然从d…

Element-ui Select选择器自定义搜索方法

效果图 具体实现 <template><div class"home"><el-selectref"currencySelect"v-model"currency"filterable:spellcheck"false"placeholder"请选择":filter-method"handleCurrencyFilter"change&q…

JS的学习与使用

JS的学习与使用 一 什么是Javascript&#xff1f; Javascript是一门跨平台&#xff0c;面向对象的脚本语言&#xff0c;是用来控制网页行为的&#xff0c;它能使网页可以交互 java与Javascript是完全不同的语言&#xff0c;不论是概念还是设计&#xff0c;但是基础语法类似 E…

Docker:查看镜像里的文件

目录 背景步骤1、下载所需要的docker镜像2、创建并运行临时容器3、停止并删除临时容器 背景 在开发过程中&#xff0c;为了更好的理解和开发程序&#xff0c;有时需要确认镜像里的文件是否符合预期&#xff0c;这时就需要查看镜像内容 步骤 1、下载所需要的docker镜像 可以使…

【网络安全 | 漏洞挖掘】通过密码重置污染实现账户接管

未经许可,不得转载。 文章目录 密码重置污染攻击漏洞挖掘的过程目标选择与初步测试绕过 Cloudflare 的尝试发现两个域名利用 Origin 头部污染实现账户接管攻击流程总结在今天的文章中,我们将深入探讨一种 账户接管 漏洞,并详细分析如何绕过 Cloudflare 的保护机制,利用密码…

Redis 5 种基本数据类型详解

Redis 共有 5 种基本数据类型&#xff1a;String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、Set&#xff08;集合&#xff09;、Hash&#xff08;散列&#xff09;、Zset&#xff08;有序集合&#xff09;。 这 5 种数据类型是直接提供给用户使用的&…