红黑树封装实现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;};
}