【C++】封装红黑树实现 mymap 和 myset
文章目录
- 模拟实现 map 和 set
- 1. 如何复用前面写过的红黑树
- 2. 支持 Insert
- 3. 迭代器的实现
- 3.1 RBTree 的 iterator
- 3.2 map、set 的迭代器实现
- 3.3 map 支持 []
前面我们实现了模拟实现了红黑树,今天就通过复用之前的代码来封装 map 和 set。
模拟实现 map 和 set
1. 如何复用前面写过的红黑树
map、set的底层都是红黑树,但是set在使用时需要传一个 K 模板参数就可以了,但是 map 则需要传一个 pair<K, V> 的模板参数,这就导致了虽然 set、map的底层都是红黑树,但是我们需要写 Key 和 pair<K, V> 两种结构的红黑树代码,这显然过于冗杂,那该怎么办呢?
是的,我们可以使用模板让编译器自己生成,虽说在编译器生成后还是两棵独立的红黑树代码,但是我们手写部分明显少了很多,也更方便后续的维护。
前面我实现红黑树的时候是使用 K,V 模板实现的,但是我们这里可以对其修改一下,变成 K,T 模板,T指的是这棵树存储的是 K 类型还是 pair<K, V> 类型。
还有一个问题,我们这里使用的是 K,T 模板参数,既然 T 参数可以表示底层存储类型,为什么还要一个 K 模板参数呢?这样会不会很冗余?
你可以想想 Insert、find、erase 的场景,在 Insert 的时候确实是可以直接用 T 没错。但是如果你是 map ,你在 find、erase 的时候,是使用 pair<K, V> 的吗?显然不是吧,使用的是 K,所以这里需要额外使用一个 K 模板参数
所以我们的 RBTreeNode 直接用 T 模板参数表示它的存储的类型。
这里又引申出来个问题,我们之前使用 K,V 的时候可以直接使用它的 first 进行比较,但是这里我们并不知道它的类型到底是 K 还是 pair ,所以这里我们需要引入一个KeyOfT 仿函数来帮我们支持比较。
// 修改前
template<class K, class V>
struct RBTreeNode
{pair<K, V> _kv;RBTreeNode<K, V>* _left;RBTreeNode<K, V>* _right;RBTreeNode<K, V>* _parent;Colour _col;RBTreeNode(const pair<K, V>& kv):_kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr){}
};template<class K, class V>
class RBTree {}// 修改后
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 K, class T, class KeyOfT>
class RBTree {}
在 map 里面实现 MapKeyOfT,在 set 里面实现 SetKeyOfT,用于各自支持比较
template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;}template<class K>class set{struct SetKeyOfT{const K& operator()(const K& key){return key;}};private:RBTree<K, const K, SetKeyOfT> _t;};
我们前面说过在红黑树类前有一个 KeyOfT 模板参数,等需要使用的时候,就在要使用的地方通过 KeyOfT 实例化一个对象,再把要比较的对象传过去再进行比较。
// 类似于这样的使用方式
KeyOfT kot;
if (kot(cur->_data) < kot(data))
2. 支持 Insert
因为我们在 map、set 类中分别存在着一个对应的红黑树对象,所以我们想的应该是直接通过调用底层红黑树的 Insert 函数来实现,我们外层的 map、set 仅仅只是一层壳。
template<class K>class set{bool insert(const K& key){return _t.Insert(key);}};template<class K, class V>class map{pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}}
3. 迭代器的实现
要想支持 map、set 的迭代器,得现在底层支持 RBTree 的迭代器
3.1 RBTree 的 iterator
还是老规矩,迭代器模板参数数量为三个,分别为 T、Ref、Ptr,以便于在使用时通过控制参数是否为 const 类型来实例化为 普通迭代器 和 const 迭代器。
RBTree 的迭代器和链表的迭代器比较相似,因为都是链式结果。最大的区别在于它们++、-- 的方式不同。因为RBTree 的底层是一棵树,所以相对而言会复杂许多。
operator++
因为红黑树是一棵自平衡的二叉搜索树,所以我们对它 ++ 的设计就是通过模仿中序的方式 来实现的。
那么就来讨论一下中序。
中序代表着这一节点已经使用,而下一节点是右子树的最左(最小)节点,所以:
- 如果当前节点存在右孩子的话,就先取它的右节点,再取这个右孩子的最左节点
- 如果当前节点不存在右孩子的话:
- 如果当前节点是父节点的左,根据中序左->根->右,那么下一节点就是当前节点的父节点
- 如果当前节点是父节点的右, 当前节点访问完了,就表示父节点的子树访问完了。那我们就模拟回溯的行为,往根的祖先中去找,直到找到一个节点,它是父亲的左节点,那么那个节点的父亲节点就是下一个要访问的节点
如何表示 begin 呢?
按照二叉搜索树的性质,它第一个访问位置为整棵树的最左节点,那么就是它
如何表示 end 呢?
按照上面++的思路,当我们找完最后一个有效节点之后,你再按照上面的思路进行一次++,你会发现你到了 _root 节点的父节点的位置。_root 节点的父节点为空,所以我们将 end 设置为 nullptr
operator–
迭代器 – 的实现刚好和 ++ 反过来,它的访问顺序是 右->根->左。
- 因为 end 位置是 nullptr,所以需要特判一下,直接返回整棵树的最右节点即可
- 如果左子树不为空,按照中序,我们可以确定下一个访问位置是左子树的最后一个节点(最右节点)
- 如果左子树为空,下一个节点是 孩子是父节点的左节点 的那个父节点
其他的和之前实现其他迭代器的方式是一样的
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{typedef RBTreeNode<T> Node;typedef RBTreeIterator<T, Ref, Ptr> Self;Node* _node;Node* _root;RBTreeIterator(Node* node, Node* root):_node(node), _root(root){}Self operator++(){if (_node->_right){// 右不为空,中序下一个访问节点是右子树的最左(最小)节点Node* min = _node->_right;while (min->_left){min = min->_left;}_node = min;}else{// 右为空,祖先里面孩子是父亲左的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_right){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Self operator--(){if (_node == nullptr) // --end(){// --end(),特殊处理,走到中序最后一个节点,整棵树的最右节点Node* rightMost = _root;while (rightMost && rightMost->_right)rightMost = rightMost->_right;_node = rightMost;}else if (_node->_left){// 左子树不为空,中序左子树最后一个Node* rightMost = _root->_left;while (rightMost->_right)rightMost = rightMost->_right;_node = rightMost;}else{// 孩子是父亲右的那个祖先Node* cur = _node;Node* parent = cur->_parent;while (parent && cur == parent->_left){cur = parent;parent = cur->_parent;}_node = parent;}return *this;}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}
};
3.2 map、set 的迭代器实现
都是通过调用底层红黑树的迭代器来实现的。
// map:
namespace zkp
{template<class K, class V>class map{struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:// 这里 typename 是为了声明这是类型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();}private:RBTree<K, pair<const K, V>, MapKeyOfT> _t;};};// set中:
namespace zkp
{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();}private:// 反正 set 的key不允许修改,直接加 constRBTree<K, const K, SetKeyOfT> _t;};
}
3.3 map 支持 []
我们在使用库中的 map 时,是可以直接使用 [] 进行插入和修改的,而要支持这个,则需要修改底层红黑树 Insert 的返回值,和 map 中 insert 的返回值
// map 中:pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}// set 中:// 这里只是为了维护代码堆统一性,并无实际作用pair<iterator, bool> insert(const K& key){return _t.Insert(key);}// 底层红黑树的插入pair<Iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;//return pair<Iterator, bool>(Iterator(_root, _root), true);return { Iterator(_root, _root), true }; // 直接用 C++11 的方式写吧}// 插入部分KeyOfT kot;Node* parent = nullptr;Node* cur = _root;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){// g// p uNode* uncle = grandfather->_right;// 如果叔叔节点也是红色,直接变色并继续向上调整就可以了,不需要旋转if (uncle && uncle->_col == RED){// 变色uncle->_col = parent->_col = BLACK;grandfather->_col = RED;// 将 g 作为新的 c 继续向上调整cur = grandfather;parent = cur->_parent;}else{// 到这就该考虑 p、c 同侧还是异侧的情况了// p、c同侧单旋,异侧双旋if (cur == parent->_left){// 同侧单旋// g// p u// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// 异侧双旋// g// p u// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}// 单旋双旋都是直接跳出循环break;}}else{// g// u pNode* uncle = grandfather->_left;// 叔叔为红,变色if (uncle && uncle->_col == RED){parent->_col = uncle->_col = BLACK;grandfather->_col = RED;// 向上处理cur = grandfather;parent = cur->_parent;}else{// 还是考虑同侧异侧的问题,代码和上面刚好对称if (cur == parent->_right){RotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{RotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}break;}}}// 保证根节点为黑色节点_root->_col = BLACK;return { Iterator(newnode, _root), true };}