C++红黑树模拟实现map和set

点击蓝字

9e661044eb091b2666c7664efd7b40de.png

关注我们

一、红黑树及其节点的设计

对于底层都是红黑树的map和set来说,他们之间存在的最大的区别就是:对于set是K模型的容器,而map是KV模型的容器。为了更好的灵活兼容实现map和set,就需要在红黑树以及树节点上进行特别的设计

1、树节点的设计

对于红黑树的节点我们需要节点对于set来说储存key,对于map来说储存key-value键值对,所以这里我们就直接让节点类型的阈值类型为T,用来控制储存类型

实现代码:

template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;//T可以是key也可以是pair<K,V>Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};

解释:

对于set实现我们传入key,对于map实现我们传入pair<key,value>,由此满足set和map各自的需求

2、红黑树的设计

想要兼容map和set,我们依旧需要红黑树的模板有两个类型来控制和满足上层对下层的需求

实现代码:

template<class K, class T>
class RBTree
{typedef RBTreeNode<T> Node;
public://.......
private:Node* _root;
};

注:这里的Node我们不想让外部直接获取使用,我们typedef在private域中

解释:

  • 为了兼容set和map,对于第一个参数我们是用来比较的key类型,第二个参数是用来储存数据的类型

  • 这里我们对红黑树第二个模板参数进行灵活传参,可能是键值key,也可能是pair<key,value>

  • 对于set传入底层红黑树的模板参数就是key和key;对于map传入底层红黑树的模板参数就是key和pair<key,value>

注:对于set来说第二个参数有点多余,但是对于map来说,因为map的接口当中有些是只要求给出键值key用来比较的(如find()和erase()),如果只有一个参数传入pair<key,value>类型,但是只能得到第一个key类型的值,无法获得key的类型(不能实现模板函数)

3、取值仿函数的使用

我们在设计树节点之后达到了对于不同容器存入不同类型的效果,但是真实在实现时在插入或者删除时,我们需要要拿出节点中储存类型的数据进行比较

分析:

  • 对于set的T本身就是键值key,直接用T进行比较即可,但是对于map的储存类型是pair<Key, Value>我们需要取出pair的first(key值)进行比较

  • 这两种的都是取值比较,但是需要的行为是不一样的,由此我们还需要一个仿函数用来灵活取值比较

  • 对于不同容器我们需要不同的仿函数类型,由此在红黑树的模板列表中还需要一个模板类型参数,灵活控制传入的仿函数类型

注:仿函数,就是使一个类的使用看上去像一个函数,其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了

红黑树框架:

template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public://...
private:Node* _root;
};

map实现框架:

namespace cole
{template<class K, class V>class map{struct MapOfKey{const K& operator()(const pair<K, V>& kv)
{return kv.first;}};public://...private:RBTree<K, pair<const K, V>, MapOfKey> _t;};
}

set实现框架:

namespace cole
{template<class K>class set{struct SetOfKey{const K& operator()(const K& key)
{return key;}};public://...private:RBTree<K, K, SetOfKey> _t;};
}

仿函数使用示例:

Node* Find(const K& key)
{KeyOfT kot;Node* cur = _root;while (cur){if (kot(cur->_kv.first) > key){cur = cur->_left;}else if (kot(cur->_kv.first) < key){cur = cur->_right;}else{return cur;}}return nullptr;
}

二、红黑树的迭代器

迭代器本质上是指针的一个封装的类,其底层就是指针;好处是可以方便遍历,是数据结构的底层实现与用户透明

对于string,vector,list等容器,其本身的结构上是比较简单的,迭代器的实现也很简单;但是对于二叉树结构的红黑树来说需要考虑很多的问题

1、begin()与end()

STL明确规定:begin()与end()代表的是一段前闭后开的区间

对红黑树进行中序遍历后,可以得到一个有序的序列,因此begin()可以放在红黑树中最小节点(即最左侧节点)的位置,end()放在最大节点(最右侧节点)的下一个位置即nullptr

示图:

520ec81fbc60e82eb4a8ddcc173ca370.png

2、operator++()与operator–()

找到begin()和end()之后,要遍历红黑树最主要的还是能找到要遍历的下一个节点

红黑树的中序遍历是有序的,也就是说:当一个结点的正向迭代器进行++操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点

实现++逻辑:

  • 对于中遍历到当前节点来说,该节点的左子树应该是已经被遍历过了,所以只需要考虑右子树

  • 如果当前结点的右子树不为空,则++操作后应该找到其右子树当中的最左结点

  • 如果当前结点的右子树为空,则该子树已经被遍历完了,则++操作后应该在该结点的祖先结点中找到孩子不在父亲右的祖先

说明:

  • 如果是孩子节点在父亲的左,说明父亲的右节点没有遍历

  • 如果孩子在父亲的右,说明以父亲节点为根的子树已经遍历完了,还需要继续向上找

  • 如果一直向上遍历到nullptr说明整颗树已经遍历完了(end()返回的就是nullptr)

注:怎么看该节点的右节点遍历过没有,这里需要一个指针记录上一次经过的节点地址,进行比较地址就行了

实现++代码:

Self& operator++()
{if (_node->_right)//右子节点存在{//找到右子树中最左节点Node* cur = _node->_right;while (cur->_left){cur = cur->_left;}_node = cur;}else//右子节点不存在,向上找{Node* cur = _node;//记录走过的节点Node* parent = _node->_parent;while (parent && parent->_right == cur){cur = parent;parent = parent->_parent;}_node = parent;}return *this;
}

注:对于–来说可以参考++的实现,逻辑是完全相反的

实现–代码:

Self& operator--()
{
if (_node->_left)//左子节点存在{
//找左子树中的最右节点Node* cur = _node->_left;
while (cur->_right){cur = cur->_right;}_node = cur;}
else//左子节点不存在{Node* cur = _node;Node* parent = _node->_parent;
while (parent && parent->_left == cur){cur = parent;
parent = parent->_parent;}_node = parent;}
return *this;
}

3、正反迭代器的实现

对于正向迭代器与反向迭代器的区别就是:begin()指向的位置不一样;迭代器的++和–的实现相反,但本质上还是差不多的

所以实现正向迭代器后,我们可以直接使用适配器,在正向迭代器的基础上,对其接口进行封装达到反向迭代器的效果

正向迭代器实现代码:

template<class T, class Ref, class Ptr>
struct _TreeIterator
{
//声明类型,便于反向迭代器对类型的提取typedef Ref reference;typedef Ptr pointer;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;}bool operator==(const Self& it)const{
return _node == it._node;}bool operator!= (const Self& it)const{
return _node != it._node;}Self& operator++(){
if (_node->_right){Node* cur = _node->_right;
while (cur->_left){cur = cur->_left;}_node = cur;}
else{Node* cur = _node;Node* parent = _node->_parent;
while (parent && parent->_right == cur){cur = parent;
parent = parent->_parent;}_node = parent;}
return *this;}Self& operator--(){
if (_node->_left){Node* cur = _node->_left;
while (cur->_right){cur = cur->_right;}_node = cur;}
else{Node* cur = _node;Node* parent = _node->_parent;
while (parent && parent->_left == cur){cur = parent;
parent = parent->_parent;}_node = parent;}return *this;}
};

反向迭代器实现代码:

//适配器构造反向迭代器
template<class Iterator>
struct ReverseIterator
{
//类型未实例化,无法取出里面的类型,此时需要使用typename:告诉编译器等实例化后再到类里面找对应的类型
typedef typename Iterator::reference Ref;
typedef typename Iterator::pointer Ptr;
typedef ReverseIterator<Iterator> Self;Iterator _it;ReverseIterator(Iterator it):_it(it){}//在正向迭代器接口上进行封装复用   Ref operator*(){
return *_it;}Ptr operator->(){
return _it.operator->();}bool operator==(const Self& it)const{
return it._it==_it;}bool operator!= (const Self& it)const//两个const{
return _it != it._it;}Self& operator++(){--_it;
return *this;}Self& operator--(){++_it;
return *this;}
};

三、map和set的实现


1、红黑树的实现

具体实现代码:

//颜色
enum Colour
{RED,BLACK,
};template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;T _data;//T可以是key也可以是pair<K,V>Colour _col;RBTreeNode(const T& data):_left(nullptr), _right(nullptr), _parent(nullptr), _data(data), _col(RED){}
};template<class K, class T, class KeyOfT>
class RBTree
{typedef RBTreeNode<T> Node;
public:typedef _TreeIterator<T, T&, T*> iterator;typedef _TreeIterator<T,const T&, const T*> const_iterator;typedef ReverseIterator<iterator> reverse_iterator;typedef ReverseIterator<const_iterator> reverse_const_iterator;RBTree():_root(nullptr){}~RBTree(){_Destory(_root);}iterator begin(){Node* cur = _root;
while (cur&&cur->_left){cur = cur->_left;}
return iterator(cur);}reverse_iterator rbegin(){Node* cur = _root;
while (cur&&cur->_right){cur = cur->_right;}
return reverse_iterator(iterator(cur));}reverse_iterator rend(){
return reverse_iterator(iterator(nullptr));}iterator end(){
return iterator(nullptr);}Node* Find(const K& key){KeyOfT kot;Node* cur = _root;
while (cur){
if (kot(cur->_kv.first) > key){cur = cur->_left;}
else if (kot(cur->_kv.first) < key){cur = cur->_right;}
else{
return cur;}}
return nullptr;}pair<iterator, bool> Insert(const T& data){
//空树的情况
if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;
return make_pair(iterator(_root), true);}KeyOfT kot;
//查找位置插入节点Node* cur = _root, * parent = _root;
while (cur){
if (kot(cur->_data) > kot(data)){
parent = cur;cur = cur->_left;}
else if (kot(cur->_data) < kot(data)){
parent = cur;cur = cur->_right;}
else{
return make_pair(iterator(cur), false);}}//创建链接节点cur = new Node(data);Node* newnode = cur;
if (kot(parent->_data) > kot(data)){
parent->_left = cur;}
else{
parent->_right = cur;}cur->_parent = parent;//父节点存在且为红,则需要调整(不能存在连续的红色节点)
while (parent && parent->_col == RED){
//此时当前节点一定有祖父节点Node* granparent = parent->_parent;
//具体调整情况主要看叔叔节点
//分左右讨论
if (parent == granparent->_left){Node* uncle = granparent->_right;
//情况1:叔叔节点存在且为红
if (uncle && uncle->_col == RED){
//修改颜色,继续向上检查granparent->_col = RED;
parent->_col = uncle->_col = BLACK;cur = granparent;
parent = cur->_parent;}
else//情况2和3:叔叔节点不存在 或者存在且为黑{
//单旋(三代节点为斜线)+变色
if (cur == parent->_left){RotateR(granparent);granparent->_col = RED;
parent->_col = BLACK;}
else//双旋(三代节点为折线)+变色{RotateL(parent);RotateR(granparent);cur->_col = BLACK;granparent->_col = RED;}
//旋转后不需再向上调整了
break;}}
else//parent=grandparent->right{Node* uncle = granparent->_left;
if (uncle && uncle->_col == RED){
parent->_col = uncle->_col = BLACK;granparent->_col = RED;cur = granparent;
parent = cur->_parent;}
else{
if (cur == parent->_right){RotateL(granparent);parent->_col = BLACK;granparent->_col = RED;}
else{RotateR(parent);RotateL(granparent);cur->_col = BLACK;granparent->_col = RED;}
break;}}}//确保根节点为黑_root->_col = BLACK;
return make_pair(iterator(newnode), true);}bool IsRBTree(){
if (_root == nullptr){
return true;}if (_root->_col == RED){cout << "根节点为红色" << endl;
return false;}int Blacknum = 0;Node* cur = _root;
while (cur){
if (cur->_col == BLACK)Blacknum++;cur = cur->_left;}int i = 0;
return _IsRBTree(_root, Blacknum, i);}private:void _Destory(Node*& root){
if (root == nullptr)
return;_Destory(root->_left);_Destory(root->_right);delete root;root = nullptr;}bool _IsRBTree(Node* root, int blacknum, int count){
if (root == nullptr){
if (blacknum == count)
return true;cout << "各路径上黑色节点个数不同" << endl;
return false;}if (root->_col == RED && root->_parent->_col == RED){cout << "存在连续红色节点" << endl;
return false;}if (root->_col == BLACK)count++;return _IsRBTree(root->_left, blacknum, count) && _IsRBTree(root->_right, blacknum, count);}void RotateL(Node* parent){Node* subR = parent->_right;Node* subRL = subR->_left;Node* parentP = parent->_parent;parent->_right = subRL;
if (subRL){subRL->_parent = parent;}subR->_left = parent;
parent->_parent = subR;if (parent == _root){_root = subR;subR->_parent = nullptr;}
else{subR->_parent = parentP;
if (parentP->_left == parent){parentP->_left = subR;}
else{parentP->_right = subR;}}}void RotateR(Node* parent){Node* subL = parent->_left;Node* subLR = subL->_right;Node* parentP = parent->_parent;parent->_left = subLR;
if (subLR){subLR->_parent = parent;}subL->_right = parent;
parent->_parent = subL;if (parent == _root){_root = subL;subL->_parent = nullptr;}
else{subL->_parent = parentP;
if (parentP->_left == parent){parentP->_left = subL;}
else{parentP->_right = subL;}}}private:Node* _root;
};

2、map的封装

具体实现代码:

namespace cole
{
template<class K, class V>
class map{
struct MapOfKey{
const K& operator()(const pair<K, V>& kv)
{
return kv.first;}};
public:
typedef typename RBTree<K, pair<const K, V>, MapOfKey>::iterator iterator;
typedef typename RBTree<K, pair<const K, V>, MapOfKey>::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()));
return ret.first->second;}iterator find(const K& key)
{
return _t.Find(key);}private:RBTree<K, pair<const K, V>, MapOfKey> _t;};
}

3、set的封装

具体实现代码:

namespace cole
{
template<class K>
class set{
struct SetOfKey{
const K& operator()(const K& key)
{
return key;}};
public:
typedef typename RBTree<K,K, SetOfKey>::iterator iterator;
typedef typename RBTree<K,K, SetOfKey>::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);}iterator find(const K& key)
{
return _t.Find(key);}private:RBTree<K, K, SetOfKey> _t;};
}

*声明:本文于网络整理,版权归原作者所有,如来源信息有误或侵犯权益,请联系我们删除或授权事宜。

b30b5d166193e408ce194ea25987e9d5.png

982193946bdcfd625aa3c484de98bd54.gif

戳“阅读原文”我们一起进步

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

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

相关文章

c语言连接mysql_聊聊数据库MySQL、SqlServer、Oracle的区别,哪个更适合你?

一、MySQL优点&#xff1a;体积小、速度快、总体拥有成本低&#xff0c;开源&#xff1b;支持多种操作系统&#xff1b;是开源数据库&#xff0c;提供的接口支持多种语言连接操作 &#xff1b;MySQL的核心程序采用完全的多线程编程。线程是轻量级的进程&#xff0c;它可以灵活地…

绩效工作流_流绩效–您的想法

绩效工作流上周&#xff0c;我介绍了一些有关Java 8流性能的基准测试结果。 你们和gal足够有兴趣留下一些想法&#xff0c;还有哪些可以介绍。 这就是我所做的&#xff0c;这里是结果。 总览 最后一篇文章的序言也适用于此。 阅读它&#xff0c;以找出所有数字为何撒谎&#…

C语言访问MCU寄存器的两种方式

点击蓝字关注我们单片机的特殊功能寄存器SFR&#xff0c;是SRAM地址已经确定的SRAM单元&#xff0c;在C语言环境下对其访问归纳起来有两种方法。1、采用标准C的强制类型转换和指针来实现采用标准C的强制转换和指针的概念来实现访问MCU的寄存器&#xff0c;例如:#define DDRB (*…

08_优先队列

08_优先队列 一、优先队列最大优先队列最大优先队列API设计 最小优先队列最小优先队列API设计最小优先队列代码实现 索引优先队列索引优先队列实现思路索引优先队列API设计索引优先队列代码实现 一、优先队列 :::info 普通的队列是一种先进先出的数据结构&#xff0c;元素在队…

Python3实现红黑树[下篇]

Python3实现红黑树[下篇]我写的红黑树的上篇在这里&#xff1a;https://blog.csdn.net/qq_18138105/article/details/105190887 这是我近期看的文章 https://www.cnblogs.com/gcheeze/p/11186806.html 我看了很多关于红黑树删除的文章和博客&#xff0c;介绍得是相当相当的复…

C语言内存泄露很严重,如何应对?

点击蓝字关注我们1. 前言最近部门不同产品接连出现内存泄漏导致的网上问题&#xff0c;具体表现为单板在现网运行数月以后&#xff0c;因为内存耗尽而导致单板复位现象。**一方面&#xff0c;内存泄漏问题属于低级错误&#xff0c;此类问题遗漏到现网&#xff0c;影响很坏&…

python发送邮件outlook_通过Python发送Outlook电子邮件?

I am using Outlook 2003. What is the best way to send email (through Outlook 2003) using Python? 解决方案 For a solution that uses outlook see TheoretiCALs answer below. Otherwise, use the smtplib that comes with python. Note that this will require your e…

C++队列queue用法详解(超详细)

点击蓝字关注我们一、定义queue是一种容器转换器模板&#xff0c;调用#include< queue>即可使用队列类。二、queue初始化queue<Type, Container> (<数据类型&#xff0c;容器类型>&#xff09;初始化时必须要有数据类型&#xff0c;容器可省略&#xff0c;省…

python随机抽取人名_python实现艾宾浩斯背单词功能,实现自动提取单词、邮件发送,再也不用担心背单词啦...

&#xfeff;已经完成了利用python爬虫实现定时QQ邮箱推送英文文章&#xff0c;辅助学习英语的项目&#xff0c;索性就一口气利用python多做一些自动化辅助英语学习的项目&#xff0c;对自己的编程能力和英文水评也有一定的帮助&#xff0c;于是在两天的努力下&#xff0c;我完…

用不到125行C语言代码就可以编写一个简单的16位虚拟机?

点击蓝字关注我们一位国外的软件工程师分享了这么一篇博文&#xff1a;Writing a simple 16 bit VM in less than 125 lines of C&#xff08;用不到 125 行 C 语言编写一个简单的 16 位虚拟机&#xff09;。博文地址&#xff1a;https://www.andreinc.net/2021/12/01/writing-…

用一个程序生成另一个程序_还有另一个报告生成器?

用一个程序生成另一个程序如果您具有业务应用程序开发的经验&#xff0c;那么很可能会遇到要求该应用程序具有灵活的报告机制的需求。 我工作的公司主要专注于开发业务解决方案&#xff0c;而报告是必不可少的&#xff0c;实际上&#xff0c;它必须包含我们开发的所有企业系统的…

CocosCreator1.x实现水流动的效果

CocosCreator1.x实现水流动的效果Cocos Creator版本&#xff1a;1.10.2 运行结果&#xff1a;(H5和原生都支持) 场景: 脚本&#xff1a; HelloWorld.js&#xff1a; let shader require(shader);cc.Class({extends: cc.Component,properties: {water: cc.Node,waterNorm…

python爬虫xpath教程_使用 Xpath 进行爬虫开发

使用 Xpath 进行爬虫开发 Xpath( XML Path Language, XML路径语言)&#xff0c;是一种在 XML 数据中查找信息的语言&#xff0c;现在&#xff0c;我们也可以使用它在 HTML 中查找需要的信息。 既然谈到 Xpath 是一门语言&#xff0c;当然它就会有自己的一些特定的语法。我们这里…

用C语言写烟花,给心中的那个人看!

点击蓝字关注我们前言程序员不懂浪漫? 大错特错&#xff01;今天就让你们看看什么是程序员的浪漫&#xff01;你向窗外看烟花&#xff0c;我在窗边看你&#xff0c;这时&#xff0c;你比烟花好看的多&#xff0c;你的眼眸倒映满天的烟火&#xff0c;我的瞳孔倒映你温柔的脸庞…

手把手教你做一个线程池--C语言版

点击蓝字关注我们1、线程池原理我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a;如果并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间很短的任务就结束了&#xff0c;这样频繁创建线程就会大大降低…

oracle 48小时内_缺血性脑梗死后48小时内使用阿替普酶能够降低脑损伤程度

一项刊登在影响因子7.6杂志Neurology上题为“Effect of IV alteplase on the ischemic brain lesion at 24–48 hours after ischemic stroke”的研究报告中&#xff0c;来自爱丁堡大学的科学家们发现&#xff0c;alteplase与病变可视性的短期进展降低相关。在荟萃分析中&#…

MySQL夺命16问,你能坚持到第几问?

点击蓝字关注我们1、数据库三大范式是什么&#xff1f;第一范式&#xff1a;每个列都不可以再拆分。第二范式&#xff1a;在第一范式的基础上&#xff0c;非主键列完全依赖于主键&#xff0c;而不能是依赖于主键的一部分。第三范式&#xff1a;在第二范式的基础上&#xff0c;非…

美图手机投射功能在哪_在Java 8中进行投射(还有其他功能?)

美图手机投射功能在哪将实例转换为设计不良的类型。 尽管如此&#xff0c;在某些情况下没有其他选择。 从第一天开始&#xff0c;执行此功能就已成为Java的一部分。 我认为Java 8提出了对这种古老技术稍加改进的需求。 静态铸造 Java中最常见的转换方法如下&#xff1a; 静态…

js箭头函数和普通函数区别

js箭头函数和普通函数区别实验环境&#xff1a;nodejs v12.16.1 箭头函数不能作为构造函数&#xff0c;而普通函数可以 箭头函数没有原型&#xff0c;而普通函数有 箭头函数return可以省略语句块。(如果>右边不是语句块&#xff0c;则代表return右边的表达式或对象) 箭…

git 更新_[技术分享T.191212]GitLab使用方法及git命令常见问题(不断更新)

该文章用于记录一些GitLab的使用指南&#xff0c;以及在实际版本控制过程中遇到的问题及解决方法&#xff0c;会尽量及时的更新~GitLab简介&#xff1a;GitLab和GitHub很相似都属于仓库管理系统的开源项目&#xff0c;使用Git作为代码管理工具&#xff0c;并在此基础上搭建起来…