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

红黑树封装实现map set

STL中的map和set的底层实现均基于红黑树  通过对模板传参不同实例化复用同一红黑树容器 这种设计方式和之前我们实现过的list普通迭代器和const类型迭代器的方法类似

本文就用红黑树封装实现一下map和set 实现迭代器和map里面[]重载等功能 并分析一下在实现过程中遇到的各种问题如何解决


先简单分析下stl源码中使用到的方式

可以看到原码中实现的方式是很巧妙的 就是一些命名方式有些不好

接下来我们自己来实现一下

实现set和map要复用红黑树  所以我们先把上篇实现的红黑树代码拿过来然后在Mymap和Myset里面包上红黑树的头文件

源码中的名字经过了多次重命名 而且命名风格也有些乱    我们统一下 存key变量的模版参数为K value变量的模版参数为V  红黑树节点里面的数据类型就用T 

因为set和map的实现均借助了红黑树的代码,所以我们主要是要在红黑树中实现相应的功能 并能使得set和map同时可以使用

先改一下红黑树节点类里面的内容 模版参数为T 创建节点对象时候 对于set传的是K里面存的就是key  对于map传的是pair里面存的就是pair类型对象

template <class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};

插入

如下 在插入过程中一些地方需要比较插入的对象来决定插入的位置 而之前我们实现的红黑树是里面存的就是key和value这两个变量所以插入时候就是pair类型的kv 那么比较就是kv.first也就是key来比较的

但是这里的插入需要同时支持set和map来复用 所以当前需要设计一个通用的插入接口,使其能同时兼容set和map的数据插入操作。对于set的插入参数为单个key类型 而map需要插入pair<K,V>类型  我们需要找到一个方式对这两个同时支持

stl库中的解决方式 如下是在pair里面对小于的运算符重载

我们这里用仿函数的方式来解决

在Mymap和Myset中分别写一个仿函数   map的仿函数会返回kv.first也就是key set的仿函数返回key(是为了兼容map实现的)  

如下图  这样这个问题就可以解决了  对于set和map的insert直接复用树的insert就可以了 如上图

迭代器的实现

同样我们只需要在红黑树里面把迭代器的功能实现 然后set和map直接复用就可以  这里我们要支持普通迭代器和const类型的迭代器  我们使用和list那里一样的方式   

接下来还需要在红黑树的迭代器里面实现++的功能 

在之前我们使用map set迭代器时候我们知道迭代器是按照中序遍历的方式来遍历的  及左子树 根 右子树的顺序 所以迭代器的第一个位置就是中序遍历的第一个位置 ++就应该到中序遍历的下一个位置

实现分析

①判断当前节点存不存在右孩子节点 存在的话要到右树中的最小值 即先到当前节点右节点后然后一直到变为它的左节点 直到它的左节点为空

例如对下图18判断时候 18右节点存在先到30此时60左节点不为空 就变为25 此时25左节点为空了 那么此时的位置就是25

② 右节点不存在的话 说明此时作为根已经结束了 要到它的父节点的位置 但是此时可能还是处理过的节点如下图的15 15为空后回到它的父节点10 但是10已经处理过了 还要到10的父节点18  可以发现++的时候如果自己是父节点的左说明已经处理过了 所以还要向上找父节点 直到父节点为自己的右节点 

总结后 

需要先判断当前节点右节点是否存在 

①存在 到该节点右树中的最左节点位置

②不存在 判断一下此时是否是父节点的左孩子 是的话到父节点的位置就结束 

不是的话 一直到父节点的位置 直到此时是父节点的左孩子了  然后到父节点位置结束

另外 如果父节点为空的情况直接结束  

迭代器就按照list那里实现的方式一样 ++的逻辑就按照上面所分析的

代码实现

template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;RBTreeIterator(Node* node):_node(node){}Self operator++(){if (_node->_right){Node* curRmin = _node->_right;while (curRmin->_left){curRmin = curRmin->_left;}_node = curRmin;}else{Node* cur = _node;Node* curpa = _node->_parent;while (curpa && curpa->_right == cur){cur = curpa;curpa = curpa->_parent;}_node = curpa;}return *this;}bool operator!=(Self node){return _node != node._node;}bool operator==(Self node){return _node == node._node;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Node* _node;
};

 迭代器方式的打印和范围for的方式都可以使用了

也可以给迭代器重载一下--  和++的思路类似

先看左节点是否存在

①存在 找左树中的最右节点的位置

②不存在 判断一下此时是否是父节点的右孩子 是的话到父节点的位置就结束 

不是的话 一直到父节点的位置 直到此时是父节点的右孩子了  然后到父节点位置结束

另外 如果父节点为空的情况直接结束

和++的区别在于 end刚开始为空 刚开始的--需要到中序遍历的最后一个位置即最大节点  所以在迭代器中需要用_root来找最右节点  

重载的--如下 因为需要用到root 所以在迭代器中除了node指针外 还需要root 所以在迭代器的构造函数和树中的begin end返回值调用到这个构造的都需要改造一下

Self operator--()
{if (_node == nullptr)  // --end(){//特殊处理,走到中序最后一个结点,整棵树的最右结点Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){Node* curLmax = _node->_left;while (curLmax->_right){curLmax = curLmax->_right;}_node= curLmax;}else{Node* cur = _node;Node* curpa = cur->_parent;while (curpa && curpa->_left == cur){cur = curpa;curpa = curpa->_parent;}_node = curpa;}return *this;
}

实现之后 用迭代器--来打印也可以了

 

[]重载的实现

在set map使用那里我们就了解过了 其实[]里面用到的就是insert  insert返回值为pair<iterator bool>类型

所以先把insert的一些内容给修改一下  让一下树insert的返回值为pair<Iterator,bool>类型Myset Mymap insert的返回值为pair<iteraotr,bool>  然后也需要把树中insert所有返回的内容都给修改一下

如下 在map中就可以实现重载[]了

map的[]就可以正常使用了

还有一个问题

此时first的key和second的value都可以改变 但是key应该是不能修改的

stl库中的解决方式是将树中的普通迭代器和const迭代器直接都是const迭代器 但是这样还会引发一些其他的问题

我们这里直接用下图的方式 在set中对第二个模版参数直接用const修饰 在map中对第二个模版参数pair中的第一个参数key用cosnt修饰

这样就不能将key改变了  改变就会直接报错 如下

完整代码

RBTree.h完整代码

using namespace std;enum Colour
{RED,BLACK
};template <class T>
struct RBTreeNode
{T _data;RBTreeNode<T>* _left;RBTreeNode<T>* _right;RBTreeNode<T>* _parent;Colour _col;RBTreeNode(const T& data):_data(data), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;RBTreeIterator(Node* node,Node* root):_node(node),_root(root){}Self operator++(){if (_node->_right){Node* curRmin = _node->_right;while (curRmin->_left){curRmin = curRmin->_left;}_node = curRmin;}else{Node* cur = _node;Node* curpa = _node->_parent;while (curpa && curpa->_right == cur){cur = curpa;curpa = curpa->_parent;}_node = curpa;}return *this;}Self operator--(){if (_node == nullptr)  // --end(){//特殊处理,走到中序最后一个结点,整棵树的最右结点Node* rightMost = _root;while (rightMost && rightMost->_right){rightMost = rightMost->_right;}_node = rightMost;}else if (_node->_left){Node* curLmax = _node->_left;while (curLmax->_right){curLmax = curLmax->_right;}_node = curLmax;}else{Node* cur = _node;Node* curpa = cur->_parent;while (curpa && curpa->_left == cur){cur = curpa;curpa = curpa->_parent;}_node = curpa;}return *this;}bool operator!=(Self node){return _node != node._node;}bool operator==(Self node){return _node == node._node;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Node* _root;Node* _node;
};template<class k, class T, class REkey>
class RBTree
{
public:typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, T&, T*> Iterator;typedef RBTreeIterator<T,const T&,const T*> const_Iterator;Iterator begin(){Node* cur = _root;while (cur->_left){cur = cur->_left;}return Iterator(cur,_root);}Iterator end(){return Iterator(nullptr, _root);}const_Iterator begin() const{Node* cur = _root;while (cur->_left){cur = cur->_left;}return const_Iterator(cur, _root);}const_Iterator end() const{return const_Iterator(nullptr, _root);}REkey rekey;pair<Iterator,bool> insert(const T data){if (_root == nullptr)  //对空树的处理{_root = new Node(data);_root->_col = BLACK;    //根节点一定是黑色return { Iterator(_root,_root),true};} Node* cur = _root;Node* curparent = cur;while (cur != nullptr)              //直到为空了  就是要插入的位置{if (rekey(data) > rekey(cur->_data))             //要插入的数大于根的值就往右{curparent = cur;          //cur里面存的是cur的上一个位置 cur改变之前先把它的位置存到curparent中cur = cur->_right;}else if (rekey(data) < rekey(cur->_data))       //小于根的值就往左{curparent = cur;cur = cur->_left;}elsereturn { Iterator(cur,_root),false};        //不支持插入重复的元素 插入失败 返回false}//如果正常出了循环 那么此时cur的位置就是新插入节点的位置 那么此时为新节点开空间 然后让它的父节点指向它cur = new Node(data);Node* retunode = cur;if (rekey(curparent->_data) < rekey(data))curparent->_right = cur;          //此时还需要判断 cur位置的节点是父节点的右孩子还是左孩子elsecurparent->_left = cur;cur->_parent = curparent;           //处理节点的parent指针cur->_col = RED;       //新插入的非根节点一定是红色的while (curparent&&curparent->_col == RED)         //这样用while是针对u存在且为红的情况{                                       //通过处理这种情况 当grandparent为根节点再更新cur后此时的curparent为空所以需要判空Node* grandparent = curparent->_parent;  //gNode* uncle = nullptr;if (rekey(curparent->_data)< rekey(grandparent->_data))         //uncle为g的另一个孩子节点{uncle = grandparent->_right;}else{uncle = grandparent->_left;}//开始根据不同的情况处理if(uncle && uncle->_col == RED)      //叔叔节点存在且为红的处理{uncle->_col = curparent->_col = BLACK;       //u和p变黑  g变红grandparent->_col = RED;_root->_col = BLACK;        //可能grandparent就是根节点 在变色之后根节点变红了 此时要变回去 cur = grandparent;         //让此时的grandparent为cur 并确立它的父节点 为了进行下一次的判断curparent = cur->_parent;}else //如果是第一次进循环 这里就是uncle节点不存在的情况  如果是第二次或更多次进的循环 这里就是uncle节点存在且为黑的情况{                                            //这两种情况处理的方法一样if (curparent == grandparent->_left)   //p为g左的情况{if (cur==curparent->_right)  // 先对curparent进行左单旋   // g          g{                                                   //  p          p     uRotateL(curparent);                                  //c          cNode* m = cur;          //curparent单旋之后 cur和curparent的位置交换了cur = curparent;curparent = m;}                                                 //      if里面的情况在处理后变为下面RotateR(grandparent);                            //   g            g}                                                   //  p             p    uelse    // p为g右的情况                                 c             c{if (cur == curparent->_left) //先对curparent进行左单旋{RotateR(curparent);Node* m = cur;cur = curparent;curparent = m;}RotateL(grandparent);}curparent->_col = BLACK;    //最后统一变色grandparent->_col = RED;return { Iterator(retunode,_root),true};}}  //如果父节点不是红色 不需要处理 直接返回return { Iterator(retunode,_root),true};   //插入成功 返回true}void RotateR(Node* parent){Node* subl = parent->_left;Node* sublR = subl->_right;parent->_left = sublR;   //先将subl的右给了parent的左 subl->_right = parent;   //然后parent变为subl的右if (sublR){sublR->_parent = parent;}Node* pparent = parent->_parent;       //在改变parent的parent指针之前先存一下parent->_parent = subl;  //还需要改变parent的parent指针//处理pparent的指向问题if (pparent)              //pparent不为空就让pparent指向subl{if (pparent->_left == parent){pparent->_left = subl;}else{pparent->_right = subl;}}else   //如果pparent为空 说明parent就是根节点 那么直接更新根节点为parent{_root = subl;}subl->_parent = pparent;}void RotateL(Node* parent){Node* subl = parent->_right;Node* sublL = subl->_left;parent->_right = sublL;subl->_left = parent;if (sublL){sublL->_parent = parent;}Node* pparent = parent->_parent;parent->_parent = subl;if (pparent){if (pparent->_left == parent){pparent->_left = subl;}elsepparent->_right = subl;}else{_root = subl;}subl->_parent = pparent;}void Midbl()    //中序遍历的形参类型需要为节点  但是我们创建的对象是BStree类型 且里面的root根节点为私有{                         //所以 我们可以提供一个返回根节点的函数 或者像之前实现归并非递归那样做一层封装Midbl1(_root);}void Midbl1(Node* root)   //中序遍历  先左再中再右 对搜索二叉树来说也就是从小到大的顺序打印{if (root == nullptr){return;}Midbl1(root->_left);cout << root->_kv.first << " ";Midbl1(root->_right);}
private:Node* _root = nullptr;
};

Mymap.h完整代码

# include "RBTree.h"namespace xx
{template<class K, class V>class Mymap{public:struct MymapREK{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef typename RBTree<K,pair<const K,V>,MymapREK>::Iterator iterator;typedef typename RBTree<K, pair<const K, V>, MymapREK>::const_Iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}V& operator[](const K& key){pair<iterator, bool> pp = insert({ key,V() });return pp.first->second;}pair<iterator,bool> insert(const pair<K, V>& kv){return _t.insert(kv);}private:RBTree<K, pair<const K, V>, MymapREK> _t;};}

Myset.h完整代码

# include "RBTree.h"namespace xx
{template<class K>class Myset{public:struct MysetREK{const K& operator()(const K& key){return key;}};bool <iterator, bool> insert(K key){return _t.insert(key);}typedef typename RBTree<K, const K, MysetREK>::Iterator iterator;             //访问模版类里面的东西 需要用typename指定告诉编译器访问的是类型typedef typename RBTree<K, const K, MysetREK>::const_Iterator const_iterator;iterator begin(){return _t.begin();}iterator end(){return _t.end();}const_iterator begin() const{return _t.begin();}const_iterator end() const{return _t.end();}pair<iterator,bool> insert(const K key){return _t.insert(key);}private:RBTree<K,const K,MysetREK> _t;};
}

http://www.dtcms.com/a/392030.html

相关文章:

  • EMQX和MQTTX的安装
  • AI 大模型入门 四:检索增强生成(RAG),自动生成精准用例!
  • EDR与MITRE ATTCK 详解
  • 特征值和特征向量
  • Gridview:让 HPC 作业管理真正“看得见、点得着、跑得快”
  • C++/初识
  • 进一步理解自适应卡尔曼滤波(AKF)
  • 反馈循环的跨领域智慧:从控制工程到Pix2Pix
  • AI智能体如何开发工作流及注意事项
  • 电视插座工程量计算-图形识别秒计量
  • 1.1 进程与内存知识点总结
  • 深度学习-神经网络(下篇)
  • 检查 Nginx 是否启动的几种方法
  • CSS 创建漂亮的文字肖像
  • 37、RAG系统架构与实现:知识增强型AI的完整构建
  • 当贝桌面 4.1.6 | 支持文件快传(电脑传到TV),内存清理,海量4K壁纸,自定义应用和功能
  • 30-39、大模型实战构建完整技术手册:从0到1的工程化实现体系
  • 【Python】Tkinter库实现GUI界面计算器
  • 茶叶在线销售与文化交流平台的设计与实现(java)
  • 中电金信 :MCP在智能体应用中的挑战与对策
  • AI智能体开发目前主要基于哪些方面?
  • 8.2-spring 事务-声明式事务(@Transactional原理)
  • 数据分类分级:数据安全与治理的核心框架
  • STM32---看门狗
  • 标签肽V5 tag,V5 Tag Peptide
  • Hello Robot Stretch 3 技术解析(上):极简结构与全身力感知如何加速科研?
  • FPGA学习
  • 栈序列:合法与非法判定法则
  • Postgresql17数据库中的扩展插件说明
  • pwn知识点——字节流