C++|set、map模拟实现<——红黑树

目录

一、红黑树的迭代器

1.1红黑树迭代器框架

1.2operator*() && operator->()

1.3operator++()

1.4operator--()

1.5operator==()  && operator!=() 

1.6begin() && end()

二、如何用红黑树搭配map和set(仿函数)

 三、红黑树封装map和set(简易版)

3.1红黑树的构造(RBTree.h) 

3.2map的模拟实现(MyMap.h)

3.3set的模拟实现(MySet.h)

3.4测试(test.cpp)


 一、红黑树的迭代器

前一篇章,有了红黑树的了解,但那只实现了红黑树的插入部分,那么现在要用红黑树封装set、map容器,那有一个功能,就必须得实现,即迭代器,对于红黑树的迭代器该如何实现呢?参考前面篇章,list容器的迭代器的实现,同样的,红黑树将迭代器要实现的功能封装成了一个类,那么接下来进行一步步实现。

1.1红黑树迭代器框架

由于迭代器的遍历,实际就是遍历节点,在实现具体步骤之前,先带上节点,再把迭代器的框架搭好。 

	enum Color//对于红黑节点,用枚举结构来表示{RED,BLACK};template<class T>struct RBTreeNode{RBTreeNode(const T& data, Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_color(color){}RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Color _color;//默认给红色才符合规则,若默认给黑色的话,则插入的每个节点都是黑色节点,//那么就不能保证每条路径的黑色节点相同,违反了第4条性质。而给红色,就可以根据规则调整。};template<class T, class Ref, class Ptr>struct RBTreeIterator{typedef RBTreeNode<T> Node;typedef Node* PNode;typedef RBTreeIterator<T, Ref, Ptr> Self;PNode _node;RBTreeIterator(const PNode node):_node(node){}//.....};

1.2operator*() && operator->()

        T& operator*(){return _node->_data;//访问节点数据}T* operator->(){return &(operator*());//operator*() == _node->_data}

1.3operator++()

 对红黑树的遍历是一个中序遍历,遍历完后,得到的是一个有序序列。每++一次,跳到的位置是中序序列中的下一个位置。我们知道中序的访问顺序是,左根右,那么如何在树上进行操作而达到中序遍历呢?

大致可以分为两步:

1.当左子树与根访问完,要符合中序,得去右子树进行访问,同理右子树得满足中序遍历,首先就得找到右子树的最小节点,即最左节点。

抽象示意图:

2.当左子树未访问完,++时,就指向父节点,

抽象示意图:

或者当右子树访问完了,则说明一颗节点的整个左子树访问完了。那么++就是要找到这个节点

抽象示意图: 

        Self& operator++(){if (_node->_right)//左子树访问完,去访问右子树{_node = _node->_right;while (_node && _node->_left){_node = _node->_left;}}else//左子树未访问完,或者右子树访问完{PNode cur = _node;PNode parent = cur->_parent;while (parent && cur != parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}

1.4operator--()

那么,对于--操作,它与++操作是反过来的,是从大到小的一个遍历。

1. 当右子树与根访问完,要符合大到小的遍历,得去左子树进行访问,同理左子树得满足大到小的遍历,首先就得找到左子树的最大节点,即最右节点。

2.当右子树未访问完,或者左子树已访问完

Self& operator--(){PNode cur = _node;PNode parent = cur->_parent;if (_node->_left)//右子树访问完,去左子树访问{_node = _node->_left;while (_node->_right){_node = _node->_right;}}else//右子树未访问完,或者左子树访问完{PNode cur = _node;PNode parent = cur->_parent;if (parent && cur != parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}

1.5operator==()  && operator!=() 

        bool operator==(const Self& x) const{return _node == x._node;}bool operator!=(const Self& x) const{return _node != x._node;}

 1.6begin() && end()

搭建好了迭代器,那么如何在红黑树中定义begin和end(),按正常的理解, begin返回指向中序序列第一个元素的迭代器,end()返回指向中序序列最后一个元素下一个位置的迭代器。

        iterator begin(){PNode cur = _Root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){		return iterator(nullptr);}

begin的返回值是没有问题,但是end就不一样了,end指向的是nullptr,当要实行end()--操作时,迭代器就会指向最后一个元素的位置,但是end已经指向空了呀,而--的操作是通过更改指针指向,那么更改end指向,就是要对空指针进行解引用,就会报错。

那么正确的做法就是将end()放在头结点的位置。即构建一个头结点用来存放begin和end,该头结点与树的头结点互相指向,对于这种做法,这里并不会去实现,还是按照原来的做法进行实现。

二、如何用红黑树搭配map和set(仿函数)

我们可以用两颗红黑树分别封装一份map和一份set,但是这样做的效果就带来了代码冗余。为了减少代码冗余,模拟跟库保持用一颗红黑树封装map和set,但是该如何做到套用一颗树呢,我们来进一步分析。

 首先对于map而言,其存放的节点值是pair,而对于set存放的是key,这对于红黑树节点的实现到是没啥问题,但是对于红黑树内部的构造,是需要查询插入的位置,就需要进行比较,若将比较实现成key的比较,那么对于pair类型又该如何比较,虽然知道比较的也是pair中的key,但是如何做到既满足set中的key类型比较,又满足pair类型中的key比较,总不能干两份代码吧。这个时候,我们的仿函数又派上用场了,对于set和map中都构造一个仿函数,分别表示取到set的key,和map中pair中的key,那么红黑树中的比较,就可以换成仿函数的比较,当往set中插入元素进行比较,调用的就是set的仿函数,当往map中插入元素进行比较,调用的就是map的仿函数从而达到回调。用一张图来进行表示,如图:

 三、红黑树封装map和set(简易版)

3.1红黑树的构造(RBTree.h) 

#pragma once
#include <iostream>
#include <assert.h>
using namespace std;namespace bit
{enum Color//对于红黑节点,用枚举结构来表示{RED,BLACK};template<class T>struct RBTreeNode{RBTreeNode(const T& data, Color color = RED):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_color(color){}RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;Color _color;//默认给红色才符合规则,若默认给黑色的话,则插入的每个节点都是黑色节点,//那么就不能保证每条路径的黑色节点相同,违反了第4条性质。而给红色,就可以根据规则调整。};template<class T, class Ref, class Ptr>struct RBTreeIterator{typedef RBTreeNode<T> Node;typedef Node* PNode;typedef RBTreeIterator<T,Ref,Ptr> Self;PNode _node;RBTreeIterator(const PNode node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &(operator*());}Self& operator++(){if (_node->_right)//左子树访问完,去访问右子树{_node = _node->_right;while (_node && _node->_left){_node = _node->_left;}}else//左子树未访问完,或者右子树访问完{PNode cur = _node;PNode parent = cur->_parent;while (parent && cur != parent->_left){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}Self& operator--(){PNode cur = _node;PNode parent = cur->_parent;if (_node->_left)//右子树访问完,去左子树访问{_node = _node->_left;while (_node->_right){_node = _node->_right;}}else//右子树未访问完,或者左子树访问完{PNode cur = _node;PNode parent = cur->_parent;if (parent && cur != parent->_right){cur = parent;parent = parent->_parent;}_node = parent;}return *this;}bool operator==(const Self& x) const{return _node == x._node;}bool operator!=(const Self& x) const{return _node != x._node;}};template<class K, class T,class KeyOfT>class RBTree{typedef RBTreeNode<T> Node;typedef Node* PNode;public:typedef RBTreeIterator<T,T&,T*> iterator;typedef RBTreeIterator<const T, const T&, const T*> const_iterator;RBTree():_Root(nullptr){}iterator begin(){PNode cur = _Root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end(){		return iterator(nullptr);}const_iterator begin() const{PNode cur = _Root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}const_iterator end() const{return iterator(nullptr);}pair<iterator,bool> Insert(const T& data){if (_Root == nullptr){_Root = new Node(data, BLACK);_Root->_parent = nullptr;return make_pair(iterator(_Root), true);}//寻找插入位置KeyOfT kot;//定义仿函数对象PNode cur = _Root;PNode parent = nullptr;while (cur){if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else if (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}elsereturn make_pair(iterator(cur),false);}//插入cur = new Node(data);if (kot(data) < kot(parent->_data)){parent->_left = cur;}else if (kot(data) > kot(parent->_data)){parent->_right = cur;}cur->_parent = parent;//调整while (parent && parent->_color == RED)//只要停留在情况一就继续判断{PNode grandparent = parent->_parent;PNode uncle = nullptr;//先定好uncle的位置,不管uncle是否存在if (parent == grandparent->_left){uncle = grandparent->_right;}else{uncle = grandparent->_left;}if (uncle && uncle->_color == RED)//p为红、u存在且为红{//    g//  p   u// curparent->_color = BLACK;uncle->_color = BLACK;grandparent->_color = RED;//根节点,更新结束if (grandparent == _Root){grandparent->_color = BLACK;break;}//往上更新cur = grandparent;parent = cur->_parent;}else if (cur == parent->_left && parent == grandparent->_left )//cur为p的左孩子,p为g的左孩子,p为红{//     g//  p     u//curRotateR(grandparent);parent->_color = BLACK;grandparent->_color = RED;break;}else if (cur == parent->_right &&  parent == grandparent->_right )//cur为p的右孩子,p为g的右孩子,p为红{//     g// u      p//          curRotateL(grandparent);parent->_color = BLACK;grandparent->_color = RED;break;}else if (cur == parent->_right && parent == grandparent->_left )//p为g的左孩子,cur为p的右孩子,p为红{//   g//p     u//  curRotateL(parent);RotateR(grandparent);cur->_color = BLACK;grandparent->_color = RED;break;}else if (cur == parent->_left && parent == grandparent->_right)//p为g的右孩子,cur为p的左孩子,p为红{//   g//u     p//   curRotateR(parent);RotateL(grandparent);cur->_color = BLACK;grandparent->_color = RED;break;}else{assert(false);}}return make_pair(iterator(cur),true);}iterator Find(const T& data){if (_Root == nullptr)return end();PNode cur = _Root;KeyOfT kot;while (cur){if (kot(data) < kot(cur->_data)){cur = cur->_right;}else if (kot(data) > kot(cur->_data)){cur = cur->_left;}else return iterator(cur);}return end();}size_t _Size(PNode Root,int k){if (Root == nullptr)return 0;int leftsize = _Size(Root->_left, k);int rightsize = _Size(Root->_right, k);return leftsize + rightsize + 1;}size_t Size() {int k = 0;return _Size(_Root, k);}bool Empty(){return _Root == nullptr;}void RotateL(PNode parent){PNode subR = parent->_right;PNode subRL = subR->_left;PNode pparent = parent->_parent;if (parent == _Root)//更新根节点{_Root = subR;subR->_parent = nullptr;}else{//更新parent的父节点指向if (parent == pparent->_left){pparent->_left = subR;}else{pparent->_right = subR;}subR->_parent = pparent;}//parent的右指针指向subRL,subRL的父节点指向parentparent->_right = subR->_left;if (subRL)//subR的左节点可能不存在subRL->_parent = parent;//subR的左指针指向parent,parent的父节点指向subRsubR->_left = parent;parent->_parent = subR;}//右单旋void RotateR(PNode parent){PNode subL = parent->_left;PNode subLR = subL->_right;PNode pparent = parent->_parent;if (_Root == parent){_Root = subL;subL->_parent = nullptr;}else{//更新parent的父节点指向if (pparent->_left == parent){pparent->_left = subL;}else{pparent->_right = subL;}subL->_parent = pparent;}//parent的左指针指向subLR,subLR的父节点指向parentparent->_left = subLR;if (subLR)//subR的右节点可能不存在subLR->_parent = parent;//subL的右指针指向parent,parent的父节点指向subLsubL->_right = parent;parent->_parent = subL;}private:PNode _Root;};}

3.2map的模拟实现(MyMap.h)

#pragma once
#include "RBTree.h"
namespace bit
{template<class K,class V>class Map{struct MapKeyOfT{const K& operator()(const pair<K,V>& kv){return kv.first;}};public:typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<K, V>, MapKeyOfT>::const_iterator const_iterator;iterator begin(){return _rbt.begin();}iterator end(){return _rbt.end();}const_iterator begin() const{return _rbt.begin();}const_iterator end() const{return _rbt.end();}pair<iterator,bool> insert(const pair<K,V>& kv){return _rbt.Insert(kv);}V& operator[](const K& data){pair<iterator, bool> p = _rbt.Insert(make_pair(data, V()));//插入失败,说明data已经存在,返回指向data的迭代器return p.first->second;}iterator find(const K& data){return _rbt.Find(data);}size_t size(){return _rbt.Size();}bool empty(){return _rbt.Empty();}private:RBTree<K, pair<K,V>, MapKeyOfT> _rbt;};}

 3.3set的模拟实现(MySet.h)

#pragma once
#include "RBTree.h"
namespace bit
{template<class K>class Set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};public:typedef typename RBTree<K, const K, SetKeyOfT>::iterator iterator;//set的迭代器使用的就是红黑树的迭代器typedef typename RBTree<K, const K, SetKeyOfT>::const_iterator const_iterator;//set的迭代器使用的就是红黑树的迭代器iterator begin()//获取set的首元素位置的迭代器,即获取红黑树的最小元素的迭代器{return _rbt.begin();}iterator end(){return _rbt.end();}const_iterator begin() const{return _rbt.begin();}const_iterator end() const{return _rbt.end();}pair<iterator,bool> insert(const K& key)//插入元素实际就是插入到红黑树节点中去{return _rbt.Insert(key);}iterator find(const K& data){return _rbt.Find(data);}size_t size(){return _rbt.Size();}bool empty(){return _rbt.Empty();}private:RBTree<K,const K,SetKeyOfT> _rbt;//对set的操作,就是对红黑树的操作,定义一个红黑树对象};
}

3.4测试(test.cpp)

#define _CRT_SECURE_NO_WARNINGS 1
#pragma warning(disable:6031)#include "MyMap.h"
#include "MySet.h"void TestMapRBTree()
{int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };//int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };bit::Map<int, int> t;for (auto e : a){t.insert(make_pair(e, e));}bit::Map<int, int>::iterator it = t.begin();while (it != t.end()){cout << it->first << ":" << it->second << endl;++it;}cout << endl;
}void TestSetRBTree()
{//int a[] = { 16, 3, 7, 11, 9, 26, 18, 14, 15 };int a[] = { 4, 2, 6, 1, 3, 5, 15, 7, 16, 14 };bit::Set<int> t;for (auto e : a){t.insert(e);}bit::Set<int>::iterator it = t.begin();while (it != t.end()){cout << *it << endl;++it;}cout << t.size() << endl;cout << boolalpha << t.empty() << endl;
}int main()
{TestMapRBTree();TestSetRBTree();return 0;
}

输出结果:

以上实现的是一个红黑树简易版,虽然功能并不齐全,但目标是为了进一步学习对红黑树、map和set的掌握理解。end~

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

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

相关文章

springboot + Vue前后端项目(第十三记)

项目实战第十三记 写在前面1.建立角色表2. 后端代码生成2.1 RoleController 3. 前端页面的搭建3.1 Role.vue3.2 路由3.3 Aside.vue3.4 页面效果 4.建立菜单表5.后端代码编写5.1 Menu5.2 MenuController 6.前端页面的搭建6.1 Menu.vue6.2 路由6.3 Aside.vue6.4 页面效果 总结写在…

keepalived安装文档

目录 1、安装环境 2、安装keepalived 2.1 上传keepalived安装文件 2.2 解压 2.3 安装keepalived 2.4 加入开机启动&#xff1a; 2.5 配置日志文件 2.6 打开防火墙的通讯地址 1、安装环境 su - root yum -y install kernel-devel* yum -y install openssl-* yum -y …

vx小程序初学

小程序初学 在我还没接触到微信小程序之前&#xff0c;通常使用轮播要么手写或使用swiper插件去实现&#xff0c;当我接触到微信小程序之后&#xff0c;我看到了微信小程序的强大之处&#xff0c;让我为大家介绍一下吧&#xff01; swiper与swiper-item一起使用可以做轮播图 …

把自己的服务器添加到presearch节点

Presearch is a scam. Before, judging by the price of the token you should have been able to get between $150-$200 after 12-13 months of regular searches. "If you use this service for the next 11 years you will have earned $30!" Presearch大约需要…

Easy RoCE:在SONiC交换机上一键启用无损以太网

RDMA&#xff08;远程直接内存访问&#xff09;技术是一种绕过 CPU 或操作系统&#xff0c;在计算机之间直接传输内存数据的技术。它释放了内存带宽和 CPU&#xff0c;使节点之间的通信具有更低的延迟和更高的吞吐量。目前&#xff0c;RDMA 技术已广泛应用于高性能计算、人工智…

车流量监控系统

1.项目介绍 本文档是对于“车流量检测平台”的应用技术进行汇总&#xff0c;适用于此系统所有开发&#xff0c;测试以及使用人员&#xff0c;其中包括设计背景&#xff0c;应用场景&#xff0c;系统架构&#xff0c;技术分析&#xff0c;系统调度&#xff0c;环境依赖&#xf…

MongoDB~存储引擎了解

存储引擎 存储引擎是一个数据库的核心&#xff0c;主要负责内存、磁盘里数据的管理和维护。 MongoBD的优势&#xff0c;在于其数据模型定义的灵活性、以及可拓展性。但不要忽略&#xff0c;其存储引擎也是插件式的存在&#xff0c;支持不同类型的存储引擎&#xff0c;使用不同…

导线防碰撞警示灯:高压线路安全保障

导线防碰撞警示灯&#xff1a;高压线路安全保障 在广袤的大地上&#xff0c;高压线路如同血脉般纵横交错&#xff0c;然而&#xff0c;在这看似平静的电力输送背后&#xff0c;却隐藏着不容忽视的安全隐患。特别是在那些输电线路跨越道路、施工等区域的路段&#xff0c;线下超…

顶点着色技术在AI去衣中的作用

在当今的数字时代&#xff0c;人工智能&#xff08;AI&#xff09;已经渗透到我们生活的方方面面&#xff0c;从智能家居到自动驾驶汽车&#xff0c;再到在线购物推荐。然而&#xff0c;AI的影响远不止于此。近年来&#xff0c;AI在图像处理和计算机视觉领域的应用取得了显著进…

c++字符串相关接口

c字符串相关接口 1.str2wstr(str转换wstr)2.wstr2str(str转换wstr)3.Utf8ToAsi(Utf8转换ANSI)4.AsiToUtf8(ANSI转换Utf8)5.stringformatA/stringformatW(按照指定的格式格式化字符串)6.GetStringBetween(获取cStart cEnd之间的字符串)7.Char2Int(char转int)8.Str2Bin(字符串转换…

视觉语言大模型llava学习

1. 拉取 https://github.com/haotian-liu/LLaVA 视觉语言大模型是人工智能领域一种重要的多模态模型&#xff0c;它结合了计算机视觉&#xff08;CV&#xff09;和自然语言处理&#xff08;NLP&#xff09;的技术&#xff0c;使得模型能够同时理解图像和文本信息。这类模型在多…

hadoop部署

需要3台机子&#xff0c;Linux为centos7 分别设置静态ip&#xff0c;设置主机名,配置主机名映射&#xff0c;配置ssh免密登入 hadoop1 192.168.1.7 hadoop2 192.168.1.8 hadoop3 192.168.1.9 vi /etc/sysconfig/network-scripts/ifcfg-ens33TYPE"Ethernet" PROX…

Kotlin 泛型

文章目录 定义泛型属性泛型函数泛型类或接口 where 声明多个约束泛型具体化in、out 限制泛型输入输出 定义 有时候我们会有这样的需求&#xff1a;一个类可以操作某一类型的对象&#xff0c;并且限定只有该类型的参数才能执行相关的操作。 如果我们直接指定该类型Int&#xff…

机器人抓取检测(Robot Grasping Detection)

目录 前言 一、物体检测 二、抓取点生成 三、运动规划 四、控制 五、总结 前言 机器人抓取检测&#xff08;Robot Grasping Detection&#xff09;是指通过计算机视觉和机器学习技术&#xff0c;自动识别并确定机器人如何抓取物体的一种技术。这个过程涉及多个步骤和关键…

【Python系列】Python 中方法定义与方法调用详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

详细介绍运算符重载函数,清晰明了

祝各位六一快乐~ 前言 1.为什么要进行运算符重载&#xff1f; C中预定义的运算符的操作对象只能是基本数据类型。但实际上&#xff0c;对于许多用户自定义类型&#xff08;例如类&#xff09;&#xff0c;也需要类似的运算操作。这时就必须在C中重新定义这些运算符&#xff…

短信发送验证码及邮件发送验证码

发送短信验证码 阿里云发送验证码 public Integer sendTelCode(String tel) {String url "https://dfsns.market.alicloudapi.com/data/send_sms";String appcode "a3198282fbdf443d97aa9f3cfbe1232e";int code RandomUtil.randomInt(1000,10000);emai…

【DSP】xDAIS算法标准

1. 简介 在安装DSP开发支持包时&#xff0c;有名为 “xdais_7_21_01_07”文件夹。xDAIS全称: TMS320 DSP Algorithm Standard(算法标准)。39条规则&#xff0c;15条指南。参考文档。参考文章。 2. 三个层次 3.接口 XDAIS Digital Media。编解码引擎。VISA&#xff08;Video&…

LeetCode前端刷题指南:探索四大领域,精通五大技能,掌握六大题型,运用七大策略

LeetCode前端刷题指南&#xff1a;探索四大领域&#xff0c;精通五大技能&#xff0c;掌握六大题型&#xff0c;运用七大策略 在前端开发的广阔领域中&#xff0c;刷题是提高自身能力、深入理解算法和数据结构的重要途径。LeetCode作为知名的在线刷题平台&#xff0c;为前端开…

牛客小白月赛95VP

早上蓝桥杯大寄&#xff0c;算是交了300元买了件T恤qaq 1.签到&#xff1a;https://ac.nowcoder.com/acm/contest/83687/A 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int main() {int a,b;cin>>a>>b;if(ab) cout<<&quo…