​ 封装RBTree(红黑树)实现myset和mymap

​ 封装RBTree(红黑树)实现myset和mymap
 Hello大家好! 很高兴与大家见面! 给生活添点快乐,开始今天的编程之路。



我的博客:<但愿.

我的专栏:C语言题目精讲算法与数据结构C++

欢迎点赞,关注

目录

  一 前言

 二  对红黑树进行封装模拟实现set和map

       2.1 整体步骤(这是个建议,因为对于初学者想一下实现会出现很多问题)

       2.2封装过程中遇到问题极其解决办法的总结

       2.3 对红黑树进行封装实现set和map

                 2.3.1实现出复⽤红⿊树的框架。

                          2.3.1.1怎么封装红黑树实现储存不同数据类型的set和map

                          2.3.1.2数据类型是一个不确定的高度泛型怎么获得数据中的K

                                    2.3.1.2.1为什么要获得K

                                    2.3.1.2.2解决底层要的K进行比较问题

                2.3.2底层迭代器的实现

                         2.3.2.1迭代器中的难点++ 和 --;

                        2.3.2.2对于--中end--要回到树的最后一个节点怎么解决

                        2.3.2.3怎么区分普通迭代器和conat迭代器

               2.3.3怎么解决set和map中key不能改变

               2.3.4map中实现[ ]

   三  myset和mymap的整体实现

        3.1在实现myset和mymap中有一个细节:为什么加typename(并且为必须加)

        3.2二义性问题

        3.3typename 的作用

 四  整体代码

一 前言

set是K模型的容器,而map是(K/V)模型的的容器,这里我们对红黑树继续封装实现set和map。红黑树作为关联式容器的底层支撑,要想从数据结构向标准式容器的跨越并不是只是进行简单的接口封装,而是要解决一系列结构设计中出现的问题。

二  对红黑树进行封装模拟实现set和map

2.1 整体步骤(这是个建议,因为对于初学者想一下实现会出现很多问题)

 实现步骤: 1、实现红⿊树 2、封装map和set框架,解决KeyOfT 3、iterator 4、const_iterator 5、key不⽀持修改的问题 6、operator[]

2.2封装过程中遇到问题极其解决办法的总结

2.3 对红黑树进行封装实现set和map

2.3.1实现出复⽤红⿊树的框架。

2.3.1.1怎么封装红黑树实现储存不同数据类型的set和map

由于set和map中储存的数据类型不同,那怎么通过封装红黑树分别实现set和map呢?我们来看一下STL中源码给出的解决方法。

从源码中可以看到,其在定义节点时并没有给出明确数据类型,而是通过高度的泛型,将数据类型通过模板参数,在通过上层的set和map来确定数据类型。这种方法很不错,所以这里我们也所以这种方法。这里还有一个问题:从源码中可以看出在定义红黑树不仅仅有储存的数据类型,红黑树中的第一个模板参数是K,那为什么在有数据模板参数的情况下还要有K呢?因为在查找(Find)删除(Erase)等操作需要使用,所以这里必须要有第一个参数

定义红黑树的节点解决set和map储存不同的数据类型:对红黑树的节点进行改造,实现高度的泛型。

// 这里set和map中数据的类型不同,是写两种红黑树吗? // 从源码中可以看出是通过高度的泛型来实现。 // 这里我们可以给一个模板用来储存数据,但是数据的类型是不确定的由上层的set和map中模板的参数决定 // red black template<class T> struct RBTreeNode { // 需要parent指针,后续更新平衡因子可以看到 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) , _col(RED) { } };

2.3.1.2数据类型是一个不确定的高度泛型怎么获得数据中的K
2.3.1.2.1 为什么要获得K

在红黑树的insert插入和find查找的工作中都要使用储存的数据中的K进行比较,但是我们为了实现封装红黑树实现set和map已经将储存的数据类型定义成一个不确定的高度泛型,set中由于储存的数据类型就是K,所以在set中像获取K是很简单的,但是在map中储存的数据类型是pair<K,V>类型,从库中可以看出pair类型的数据是支持比较大小的,但是其比较思路是K、V都会参加所以无法满足我们实际需求,所以这里我们要自己实现。

2.3.1.2.2 解决底层要的K进行比较问题

这里我们可以在定义封装set和map时定义一个仿函数,仿函数的作用是获得数据中的K,在通过模板参数传回到红黑树中即可。那怎么为什么不直接实现一个比较的仿函数呢?问题是在于在不同操作中比较类型的不同所以无法实现一个仿函数。例如在比较操作比的是两个data中的key,而在Find操作中比较的是给的key和data中的key,所以这里是无法实现一个比较函数直接实现比较,所以只能实现获取data中的key在红黑树中进行比较。

总结:从2.3.1.1中可知红黑树的模板参数有K,和数据类型T,这里又又一个获取K的仿函数模板参数,所以红黑树又三个模板参数:

仅仅支持insert插入的红黑树框架:

2.3.2底层迭代器的实现

2.3.2.1迭代器中的难点++ 和 --;

这里我只将++的操作,--操作只反过来就行。

迭代器++整体步骤思路:核⼼逻辑就是不看全局,只看局部,只考虑当前中序局部要访问的下⼀个结点



(1)如果it指向的结点的右⼦树不为空,代表当前结点已经访问完了,要访问下⼀个结点是右⼦树的中序第⼀个,⼀棵树中序第⼀个是最左结点,所以直接找右⼦树的最左结点即可。(2)如果it指向的结点的右⼦树空,代表当前结点已经访问完了且当前结点所在的⼦树也访问完了,要访问的下⼀个结点在当前结点的祖先⾥⾯,所以要沿着当前结点到根的祖先路径向上找。如果当前结点是⽗亲的左,根据中序左⼦树->根结点->右⼦树,那么下⼀个访问的结点就是当前结点的⽗亲;如果当前结点是⽗亲的右,根据中序左⼦树->根结点->右⼦树,当前当前结点所在的⼦树访问完了,当前结点所在⽗亲的⼦树也访问完了,那么下⼀个访问的需要继续往根的祖先中去找,直到找到孩⼦是⽗亲左的那个祖先就是中序要问题的下⼀个结点。


2.3.2.2对于--中end--要回到树的最后一个节点怎么解决

这里我只将++的操作,--操作只反过来就行。由于end是指向树中最后一个节点,这里我用空表示,所以在--操作中对于end(nullptr)的--要特殊处理(nullptr不能解引用)。我们来看一看STL中源码怎么解决这个问题:


 

从上面两图中可以看出源码中给树增加了一个哨兵位就像正确的序列式容器一样,虽然哨兵位在迭代器--的操作给我们带来了便利,但是从另一方面像由于哨兵位的存在所以我们要维护哨兵位,在插入查找中可能因为插入一个节点、旋转而哨兵位的左右指向要改变,这大大增加了难度。所以这里我们不才用这种方法。所以这里我们增加用空表示end只是在迭代器中在--查找时对于end--(nullptr--)要特殊处理,返回树中最后一个节点,所以迭代器的成员变量应该有两个即当前节点和根节点(方便再end--时找到树中最后一个节点)

2.3.2.3怎么区分普通迭代器和conat迭代器

由于普通迭代器和const的迭代器只有再(*)和(->)操作上的返回值不同,所以这里我们可以将(*)和(->)的重载函数的返回值作为模板参数进行区分。所以迭代器有三个模板参数:一个数据模板参数方便后续定义成员变量:两个重载运算符的返回值方便区分普通迭代器和const迭代器。

【迭代器整体代码】

//3迭代器 //很重要其中遇到的问题也很多 //问题3.1在于在实现++和--中,这里不像以前的线性结构很简单,这里是一个树型结构在得到前一个节点和后一个节点上有一定难度 //问题3.2在实现const迭代器和普通迭代器上怎么区分有一定难道 //问题3.3怎么实现迭代器end,并实现end使其--操作时可以得到树中的最后一个节点 //由于普通迭代器和const迭代器只有operator*和operator->的返回值不同 //所以为了区分普通迭代器和const迭代器我们这里增加两个模板参数。用于表示operator*和operator->的返回值 template<class T,class Ref,class Ptr> struct RBTreeIterator { typedef RBTreeNode<T> Node; typedef RBTreeIterator<T,Ref,Ptr> Self; Node* _root; Node* _node; RBTreeIterator(Node* node, Node* root) :_node(node) ,_root(root) {} Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } //前置++ Self& operator++() { //根据中序遍历(左根右) //情况1如果当前节点的右节点存在,其下一个(++)就是其右子树中最左节点 if (_node->_right) { Node* minLeft = _node->_right; while (minLeft->_left) { minLeft = minLeft->_left; } _node = minLeft; } else { //情况2如果当前节点的右节点不存在,此时要找 //下一个当前阶段的祖先,且孩子是父亲左的那个祖先。 Node* cur = _node; Node* parent = cur->_parent; while (parent && cur == parent->_right) { cur = parent; parent = parent->_parent; } _node = parent; } return *this; } //后置++ //这里我们返回当前位置,在复用前置++即可 Self& operator++(int) { Self tmp({*this,_root}); ++(*this, _root); } //前置-- //这里思路和++是想反的,只是这里有一种特殊情况即end--;由于end是最后一个节点的后一个节点(空) //所以end--要得到最后一个节点 Self& operator--() { //根据中序遍历(左根右),所以这里要反过来右根左 //情况1特殊情况:如果当前节点为end指向的节点(空) //此时--会指向树中最后一个节点 if (_node == nullptr) { //end-- Node* maxRight = _root; while (maxRight &&maxRight->_right) { maxRight = maxRight->_right; } _node = maxRight; } else if (_node->_left) { //情况2:当前节点的左子树不为空,此时--得到的是中序左子树最后一个 Node* rightMost = _node->_left; while (rightMost->_right) { rightMost = rightMost->_right; } _node = rightMost; } else { //情况3:如果当前节点的左节点为空, //此时要找孩子是是父亲右的那个祖先 Node* cur = _node; Node* parent = cur->_parent; while (parent && cur == parent->_left) { cur = parent; parent = cur->_parent; } _node = parent; } return *this; } //后置-- //这里我们返回当前位置,在复用前置--即可 Self& operator--(int) { Self tmp({ *thia,_root }); --({ *this,_root }); } bool operator==(const Self& s)const { return _node == s._node; } bool operator!=(const Self& s)const { return _node != s._node; } };

2.3.3怎么解决set和map中key不能改变

我们先看看STL中源码是怎么解决的:

由于set储存的类型是K,所以这里把普通迭代器重命名为const迭代器可行,是因为set只有K本身不支持修改;map中储存的类型是pair<K,V>,要像保证K不能修改,V和pair可以修改源码中给出的解决办法是在pair的第一个类型K加const以限制变成pair<const K,V>,这种方法很妙所以在数据中我们采用这种。所以在const的迭代器中对于set将第二个模板参数数据类型改成const K即可,对于map将第二个模板参数pair的K使用const加以限制即可。

2.3.4map中实现[ ]

由于[ ]的功能有多种,插入、插入+修改、查找。使用[ ]是以insert(插入为底层实现的)

【代码】

//实现operator[] 这里底层式insert实现 //其有插入,修改,插入+修改,查找功能 V& operator[](const K& key) { pair<iterator, bool> ret = _t.Insert({key,V()}); iterator it = ret.first; return it->second; } 

三 myset和mymap的整体实现

【myset】实现

#pragma once #include"RBTree.h" namespace wzy { 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; typedef typename RBTree<K, const K, SetKeyOft>::ConstIterator 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); } iterator find(const K& key) { return _t.Find(key); } private: RBTree<K, const K,SetKeyOft> _t;//key不⽀持修改的问题,这里增加一个const即可 }; 

【mymap】实现

#pragma once #include"RBTree.h" namespace wzy { template<class K, class V> class map { //获得数据(data)中的key struct MapKeyOft { const K& operator()(const pair<K, V>& kv) { return kv.first; } }; public: typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::Iterator iterator; typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::ConstIterator 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 pair<K, V>& kv) { return _t.Insert(kv); } iterator find(const K& key) { return _t.Find(key); } //实现operator[] 这里底层式insert实现 //其有插入,修改,插入+修改,查找功能 V& operator[](const K& key) { pair<iterator, bool> ret = _t.Insert({key,V()}); iterator it = ret.first; return it->second; } private: //这里要保证插入的数据类型pair中的pair可以修改,而pair中的key不能修改;key可以修改 // 所以pair的K类型用const修饰,保证key不能被修改 RBTree < K, pair<const K, V>, MapKeyOft> _t;//将获得数据(data)中的key的仿函数作为模板参数传回 };

3.1在实现myset和mymap中有一个细节:为什么加typename(并且为必须加)

在C++的模板编程中,typename用于声名依赖类型名称。

在上述代码中由于iterator是定义在RBTree<..>红黑树内部的一个类型,RBTree本身就是一个模板,它的具体内容取决于你map和set中传入的模板参数类型。这种依赖于模板参数而定义的名称,在C++中被称为嵌套从属名称。

3.2二义性问题

当编译器第一次解析 MyMap 模板时,它还没有实例化 RBTree。此时,编译器看到 RBTree<...>::iterator 时会陷入困惑:

1它可能是一个类型(比如我们定义的 struct iterator)



2它也可能是一个静态成员变量(比如 static int iterator)



如果 iterator 是个变量,那么 typedef 就会报错;如果是类型,则正常。在默认情况下,编译器为了保守起见,会假设这个名称不是一个类型

3.3typename 的作用

为了打破上面这种僵局,我们必须显式地加上 typename。它其实是在告诉编译器:

无论 RBTree 如何实例化,这个 iterator 始终是一个确定的类型,可以按照类型来处理它

在编写泛型代码时,只要满足以下两个条件,就必须加上 typename:

正在使用一个模板内部定义的名称(嵌套)



这个名称取决于模板参数(从属)

四  整体代码

【RBTree.h】

#pragma once enum Colour { RED, BLACK }; //问题1: // 这里set和map中数据的类型不同,是写两种红黑树吗? // 从源码中可以看出是通过高度的泛型来实现。 // 这里我们可以给一个模板用来储存数据,但是数据的类型是不确定的由上层的set和map中模板的参数决定 // red black template<class T> struct RBTreeNode { // 需要parent指针,后续更新平衡因子可以看到 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) , _col(RED) { } }; //3迭代器 //很重要其中遇到的问题也很多 //问题3.1在于在实现++和--中,这里不像以前的线性结构很简单,这里是一个树型结构在得到前一个节点和后一个节点上有一定难度 //问题3.2在实现const迭代器和普通迭代器上怎么区分有一定难道 //问题3.3怎么保证key不能改变 //问题3.4怎么实现迭代器end,并实现end使其--操作时可以得到树中的最后一个节点 //由于普通迭代器和const迭代器只有operator*和operator->的返回值不同 //所以为了区分普通迭代器和const迭代器我们这里增加两个模板参数。用于表示operator*和operator->的返回值 template<class T,class Ref,class Ptr> struct RBTreeIterator { typedef RBTreeNode<T> Node; typedef RBTreeIterator<T,Ref,Ptr> Self; Node* _root; Node* _node; RBTreeIterator(Node* node, Node* root) :_node(node) ,_root(root) {} Ref operator*() { return _node->_data; } Ptr operator->() { return &_node->_data; } //前置++ Self& operator++() { //根据中序遍历(左根右) //情况1如果当前节点的右节点存在,其下一个(++)就是其右子树中最左节点 if (_node->_right) { Node* minLeft = _node->_right; while (minLeft->_left) { minLeft = minLeft->_left; } _node = minLeft; } else { //情况2如果当前节点的右节点不存在,此时要找 //下一个当前阶段的祖先,且孩子是父亲左的那个祖先。 Node* cur = _node; Node* parent = cur->_parent; while (parent && cur == parent->_right) { cur = parent; parent = parent->_parent; } _node = parent; } return *this; } //后置++ //这里我们返回当前位置,在复用前置++即可 Self& operator++(int) { Self tmp({*this,_root}); ++(*this, _root); } //前置-- //这里思路和++是想反的,只是这里有一种特殊情况即end--;由于end是最后一个节点的后一个节点(空) //所以end--要得到最后一个节点 Self& operator--() { //根据中序遍历(左根右),所以这里要反过来右根左 //情况1特殊情况:如果当前节点为end指向的节点(空) //此时--会指向树中最后一个节点 if (_node == nullptr) { //end-- Node* maxRight = _root; while (maxRight &&maxRight->_right) { maxRight = maxRight->_right; } _node = maxRight; } else if (_node->_left) { //情况2:当前节点的左子树不为空,此时--得到的是中序左子树最后一个 Node* rightMost = _node->_left; while (rightMost->_right) { rightMost = rightMost->_right; } _node = rightMost; } else { //情况3:如果当前节点的左节点为空, //此时要找孩子是是父亲右的那个祖先 Node* cur = _node; Node* parent = cur->_parent; while (parent && cur == parent->_left) { cur = parent; parent = cur->_parent; } _node = parent; } return *this; } //后置-- //这里我们返回当前位置,在复用前置--即可 Self& operator--(int) { Self tmp({ *thia,_root }); --({ *this,_root }); } bool operator==(const Self& s)const { return _node == s._node; } bool operator!=(const Self& s)const { return _node != s._node; } }; //问题2: //模板参数T是储存数据类型,在已经知道储存数据类型的情况下为什么还要第一个模板参数K(key) //因为在查找(Find)删除(Erase)等操作需要使用,所以这里必须要有第一个参数 //问题3: //由于set和map中储存的数据类型是不同的,而这里节点给的储存数据类型是一个泛型 //而在插入,查找等操作中要使用key进行比较,在set中储存的数据(data)就是key所以在set中可以直接进行比较 // 但是在map中储存数据类型是pair,这里我们可以判断pair<K,V>是否支持比较, // 在库中观察我们可以知道pair<K,V>是支持比较的,但是比较逻辑不符合我们的需要(库中会对K,V分别进行比较,而我们想比较的只是K) // 那怎么解决这个问题呢?即在RBTree中怎么获嘚key呢? //这里我们可以在上层set和map中进行取Key操作即(重载())在通过模板传入; //那这里为什么不直接实现一个比较key的仿函数呢?问题是在于在不同操作中比较类型不同无法实现一个仿函数 //例如在比较操作比的是两个data中的key,而在Find操作中比较的是给的key和data中的key,所以这里是无法实现一个比较函数直接实现比较 //只能实现获取data中的key在进行比较 //重要完成解决前两个问题,怎么知道实现成功呢?这里我们要调用insert继续插入数据,检查调用模板,看看是否正确 template<class K, class T,class KeyOft> class RBTree { typedef RBTreeNode<T> Node; public: typedef RBTreeIterator<T, T&, T*> Iterator; typedef RBTreeIterator<T, const T&, const T*> ConstIterator; //实现迭代器的底层 //普通迭代器 Iterator Begin() { Node* minLeft = _root; while (minLeft && minLeft->_left) { minLeft = minLeft->_left; } return Iterator(minLeft, _root); } //由于end是指向最后一个节点的后一个即为空,所以这里可以有空表示 //但是也给我们在迭代器中支持的操作增加难道,对于迭代器支持的--操作如果此时是end-- //此时就要指向树中最后一个节点,而这里end为空,所以此时就要遍历得到树中最后一个节点 Iterator End() { return Iterator(nullptr, _root); } //const迭代器 ConstIterator Begin() const { Node* minLeft = _root; while (minLeft && minLeft->_left) { minLeft = minLeft->_left; } return ConstIterator(minLeft, _root); } ConstIterator End() const { return ConstIterator(nullptr, _root); } //这里前面插入的返回值是bool类型 //这里由于要返回插入时的位置和是否插入成功,所以这里的返回值可以是一个pair<Iterator,bool>类型 pair<Iterator,bool> Insert(const T& data) { if (_root == nullptr) { _root = new Node(data); _root->_col = BLACK; return { Iterator(_root,_root),true };//这里使用隐式类型转换 } Node* parent = nullptr; Node* cur = _root; KeyOft kot; while (cur) { if (kot(cur->_data) < kot(data)) { parent = cur; cur = cur->_right; } else if (kot(cur->_data) > kot(data)) { parent = cur; cur = cur->_left; } else { return { Iterator(cur,_root), false }; } } // 新插入红色节点 cur = new Node(data); Node* newNode = cur;//这里有一个节点由于储存插入的节点,分别后续返回 cur->_col = RED; if (kot(parent->_data) < kot(data)) { parent->_right = cur; } else { parent->_left = cur; } cur->_parent = parent; while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; if (parent == grandfather->_left) { Node* uncle = grandfather->_right; // 1、u存在且为红 if (uncle && uncle->_col == RED) { // 变色 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; //继续往上处理 cur = grandfather; parent = cur->_parent; } else // 2、u不存在或者u存在且为黑 { if (cur == parent->_left) { // g // p u //c RotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { // g // p u // c RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } else // (parent == grandfather->_right) { Node* uncle = grandfather->_left; // 1、u存在且为红 if (uncle && uncle->_col == RED) { // 变色 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; //继续往上处理 cur = grandfather; parent = cur->_parent; } else // 2、u不存在或者u存在且为黑 { if (cur == parent->_right) { // g // u p // c RotateL(grandfather); parent->_col = BLACK; grandfather->_col = RED; } else { // g // u p // c RotateR(parent); RotateL(grandfather); cur->_col = BLACK; grandfather->_col = RED; } break; } } } _root->_col = BLACK; return { Iterator(newNode,_root), true }; } void InOrder() { _InOrder(_root); cout << endl; } bool Check(Node* root, int blackNum, const int refNum) { if (root == nullptr) { // 前序遍历走到空时,意味着一条路径走完了 if (blackNum != refNum) { cout << "存在黑色结点的数量不相等的路径" << endl; return false; } return true; } // 检查孩子不太方便,因为孩子有两个,且不一定存在,反过来检查父亲就方便多了 if (root->_col == RED && root->_parent->_col == RED) { cout << root->_kv.first << "存在连续的红色结点" << endl; return false; } if (root->_col == BLACK) { ++blackNum; } return Check(root->_left, blackNum, refNum) && Check(root->_right, blackNum, refNum); } bool IsBalanceTree() { if (_root == nullptr) return true; if (_root->_col == RED) return false; // 参考值 int refNum = 0; Node* cur = _root; while (cur) { if (cur->_col == BLACK) { ++refNum; } cur = cur->_left; } return Check(_root, 0, refNum); } int Height() { return _Height(_root); } int Size() { return _Size(_root); } //这里查找要返回查找到的位置 Iterator Find(const K& key) { Node* cur = _root; while (cur) { if (kot(cur->_data) < key) { cur = cur->_right; } else if (kot(cur->_data) > key) { cur = cur->_left; } else { return Iterator(cur, _root); } } return End(); } private: int _Size(Node* root) { return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1; } int _Height(Node* root) { if (root == nullptr) return 0; int leftHeight = _Height(root->_left); int rightHeight = _Height(root->_right); return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1; } void _InOrder(Node* root) { if (root == nullptr) { return; } _InOrder(root->_left); cout << root->_kv.first << " "; _InOrder(root->_right); } void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; if (subLR) subLR->_parent = parent; Node* ppnode = parent->_parent; subL->_right = parent; parent->_parent = subL; if (parent == _root) { _root = subL; subL->_parent = nullptr; } else { if (ppnode->_left == parent) { ppnode->_left = subL; } else { ppnode->_right = subL; } subL->_parent = ppnode; } } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* ppnode = parent->_parent; subR->_left = parent; parent->_parent = subR; if (parent == _root) { _root = subR; subR->_parent = nullptr; } else { if (ppnode->_left == parent) { ppnode->_left = subR; } else { ppnode->_right = subR; } subR->_parent = ppnode; } } private: Node* _root = nullptr; }; 

【myset.h】

#pragma once #include"RBTree.h" namespace wzy { 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; typedef typename RBTree<K, const K, SetKeyOft>::ConstIterator 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); } iterator find(const K& key) { return _t.Find(key); } private: RBTree<K, const K,SetKeyOft> _t;//key不⽀持修改的问题,这里增加一个const即可 }; void Print(const set<int>& s) { set<int>::const_iterator it = s.end(); while (it != s.begin()) { --it; //*it += 10; cout << *it << " "; } cout << endl; } //测试代码 void test_set() { set<int> s; s.insert(4); s.insert(14); s.insert(2); s.insert(12); s.insert(2); s.insert(222); s.insert(21); set<int>::iterator it = s.begin(); // 保证key不能修改 //*it += 100; while (it != s.end()) { cout << *it << " "; ++it; } cout << endl; Print(s); } }

【mymap.h】

#pragma once #include"RBTree.h" namespace wzy { template<class K, class V> class map { //获得数据(data)中的key struct MapKeyOft { const K& operator()(const pair<K, V>& kv) { return kv.first; } }; public: typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::Iterator iterator; typedef typename RBTree<K, pair<const K, V>, MapKeyOft>::ConstIterator 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 pair<K, V>& kv) { return _t.Insert(kv); } iterator find(const K& key) { return _t.Find(key); } //实现operator[] 这里底层式insert实现 //其有插入,修改,插入+修改,查找功能 V& operator[](const K& key) { pair<iterator, bool> ret = _t.Insert({key,V()}); iterator it = ret.first; return it->second; } private: //这里要保证插入的数据类型pair中的pair可以修改,而pair中的key不能修改;key可以修改 // 所以pair的K类型用const修饰,保证key不能被修改 RBTree < K, pair<const K, V>, MapKeyOft> _t;//将获得数据(data)中的key的仿函数作为模板参数传回 }; void Print(const map<string, string>& m) { for (auto& e : m) { cout << e.first << ":" << e.second << endl; } } //测试代码 void test_map() { map<string, string> m; m.insert({ "sort", "排序" }); m.insert({ "left", "左边" }); m.insert({ "right", "右边" }); // 插入 m["insert"]; for (auto& e : m) { // e.first += 'x'; //e.second += 'x'; cout << e.first << ":" << e.second << endl; } cout << endl; // 修改 m["insert"] = "插入"; for (auto& e : m) { // e.first += 'x'; //e.second += 'x'; cout << e.first << ":" << e.second << endl; } cout << endl; // 插入+修改 m["string"] = "字符串"; for (auto& e : m) { // e.first += 'x'; //e.second += 'x'; cout << e.first << ":" << e.second << endl; } cout << endl; //for (auto& e : m) //{ // // e.first += 'x'; // //e.second += 'x'; // cout << e.first << ":" << e.second << endl; //} //Print(m); string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜","苹果", "香蕉", "苹果", "香蕉" }; map<string, int> countMap; for (auto& str : arr) { countMap[str]++; } for (auto& e : countMap) { cout << e.first << ":" << e.second << endl; } cout << endl; } }

至此,红黑树及其在关联式容器中的应用已经完成。本系列的下一阶段,我们将从有序结构的另一种重要的数据结构 —— 哈希表(Hash Table),探索其在平均 O(1) 时间复杂度下实现高效查找的核心机制。本篇文章就到此结束,欢迎大家订阅我的专栏,欢迎大家指正,希望有所能帮到读者更好了解C++STL知识 ,觉得有帮助的还请三联支持一下~后续会不断更新算法与数据结构相关知识,我们下期再见。

Could not load content