【C++篇】STL的关联容器:map和set(下篇):用一颗红黑树同时封装出map和set
文章目录
- 一、红黑树源码
- 二、控制红黑树的模板参数
- 三、提取Key,仿函数的添加
- 四、普通迭代器的实现
- operator++实现
- 五、const迭代器的实现
- 六、设置Key值不可修改
- 七、修改insert的返回值 + operator[]的实现
- 1. 修改insert返回值
- 2. operator[]
- 八、封装后的源代码
- 1. 红黑树
- 2. 迭代器
- 3. set
- 4. map
一、红黑树源码
本文用于封装的红黑树代码(无erase接口):
enum Color
{BLACK,RED
};template<class K, class V>
struct BRTreeNode
{BRTreeNode<K, V>* _left;BRTreeNode<K, V>* _right;BRTreeNode<K, V>* _parent;pair<K, V> _kv;Color _col;BRTreeNode(const pair<K, V>& kv):_left(nullptr),_right(nullptr),_parent(nullptr),_kv(kv),_col(RED){ }
};template<class K, class V>
class BRTree
{typedef BRTreeNode<K, V> Node;
public:// 在红黑树中插入值为kv的节点bool Insert(const pair<K, V>& kv){if (_root == nullptr){_root = new Node(kv);_root->_col = BLACK;return true;}Node* cur = _root;Node* parent = nullptr;while (cur){if (cur->_kv.first < kv.first){parent = cur;cur = cur->_right;}else if (cur->_kv.first > kv.first){parent = cur;cur = cur->_left;}else{return false;}}cur = new Node(kv);//新增在左if (parent->_kv.first > kv.first){parent->_left = cur;}else//新增在右{parent->_right = cur;}cur->_parent = parent;//更新颜色//检查是否需要更新颜色,若parent为黑则无需更新while (parent && parent->_col == RED){Node* grandfather = parent->_parent;Node* uncle = nullptr;if (grandfather->_left == parent){uncle = grandfather->_right;}else{uncle = grandfather->_left;}if (uncle && uncle->_col == RED)//uncle存在且为红——变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;if (grandfather->_parent){if (grandfather->_col == BLACK){break;}else{cur = grandfather;parent = cur->_parent;}}else{grandfather->_col = BLACK;break;}}else//uncle不存在或uncle存在且为黑——旋转+变色{//判断何种情况,该用何种旋转if (grandfather->_right == parent){if (cur == parent->_right){// g// p// cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}else{if (cur == parent->_left){// g// p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}break;}}_root->_col = BLACK;return true;}
二、控制红黑树的模板参数
我们知道,map
是KV模型,键值是pair<Key, Value>
;而set
是K模型,键值是Key
。
那如何使得一颗KV模型的红黑树可以同时适配这两种模型呢?
首先我们控制map和set传入底层红黑树的模板参数,为了与原红黑树的模板参数进行区分,我们将红黑树第二个模板参数的名字改为T,意为通用参数。
template<class K, class T>
class BRTree
对于set,我们传入Key:
template<class K>
class set
{
public://...
private:BRTree<K, K> _t;
};
对于map,我们传入键值对:
template<class K, class V>
class map
{
public://...
private:BRTree<K, pair<K, V>> _t;
};
细心的朋友可以发现,在红黑树中,第一个模板参数K似乎多余了,因为模板参数T中也含有Key。
那么,可以省略第一个模板参数K吗?
看似无伤淡雅,实则不然。
对于set来说,确实没啥影响,因为set传入红黑树的两个模板参数是一样的。
但对于map来说,部分接口的函数参数是要求直接给出Key值的,例如find和erase。
既然我们改了红黑树的参数了,那节点类也得改改了。
对于模板参数,我们只需T即可,并没有任何用到K的地方。
成员我们依次更改一下类型即可:
template<class T>
struct BRTreeNode
{BRTreeNode<T>* _left;BRTreeNode<T>* _right;BRTreeNode<T>* _parent;T _data;Color _col;BRTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){ }
};
三、提取Key,仿函数的添加
在红黑树的find、insert等的接口中,需要对T类型的数据进行比较操作。
对于set来说,没啥问题;
但对于map,其类型为pair<K, V>,我们且来看看pair的比较运算符的重载是否符合我们的要求(只比较Key(first)的值)。
可以看到,pair的比较方式是:先比较first,若不为真,就再比较second
显然,这是不符合map的要求的。
既然如此,我们就需要利用仿函数来手动解决了。
目标仿函数功能:提取出pair<Key,Val>中的Key
设置红黑树仿函数模板参数为KeyOfT:
template<class K, class T, class KeyOfT>
class BRTree
在map中:
template<class K, class V>
class map
{//作为内部类//提取keystruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public://...
private:BRTree<K, pair<K, V>, MapKeyOfT> _t;
虽然对于set无需这般操作,但红黑树必须添加这个模板参数,set也只能无奈“陪跑”了。
template<class K>//仿函数提取keystruct SetKeyOfT{const K& operator()(const K& Key){return Key;}};
class set
{
public://...
private:BRTree<K, K, SetKeyOfT> _t;
};
四、普通迭代器的实现
迭代器的本质就是对指针进行封装,然后重载一些操作符,以至于我们可以像普通指针一样地对数据结构进行操作。
在红黑树中,迭代器必然封装的是节点指针,因此节点指针就是我们迭代器的成员。
template<class T, class T*, class T&>
struct TreeIterator
{typedef BRTreeNode<T> Node;//节点类型typedef TreeIterator<T, Ptr, Ref> Self;//迭代器类型Node* _node;//迭代器所封装的指针
};
接下来我们来一一实现他重要的运算符的重载:
当对迭代器进行解引用操作时,直接返回其对应结点数据的引用即可
T& operator*()
{return _node->_data; //返回结点数据的引用
}
当对迭代器进行->
操作时,直接返回其对应结点数据的指针即可
T* operator->()
{return &_node->_data; //返回结点数据的指针
}
当对两迭代器进行比较时,我们还需要==
和!=
运算符
//判断两个正向迭代器是否不同
bool operator!=(const Self& s) const
{return _node != s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
//判断两个正向迭代器是否相同
bool operator==(const Self& s) const
{return _node == s._node; //判断两个正向迭代器所封装的结点是否是同一个
}
上面几个都是“开胃小菜”,真正的大餐来了😏
operator++实现
迭代器进行++
操作后,应该根据红黑树中序遍历的序列找到当前结点的下一个结点。
【核心逻辑】
- 如果当前节点的右子树不为空时,则下一个访问节点应该在右子树的最左节点(最小节点)
- 如果当前节点的右子树为空时,则下一个访问节点应该在该节点的的祖先节点中,找到上一个孩子是父亲的左孩子的那个祖先。
代码:
// 迭代器的++操作,让迭代器可以移动
Self& operator++()
{//右子树存在,下一个访问节点是右子树的最左节点if (_node->_right){Node* LeftMax = _node->_right;while (LeftMax->_left){LeftMax = LeftMax->_left;}_node = LeftMax;}else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_left)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;
}
还有operator--
,是同理的,左右反一下就好了,笔者这里留下发挥空间。
迭代器的实现到这基本告一段落了,我们现在来给红黑树、map、set配置迭代器:
红黑树:
template<class K, class T, class KeyOfT>
class BRTree
{typedef BRTreeNode<T> Node;
public://注意这里要typedef在public内,否则就是私有成员了,map和set就无法访问了typedef TreeIterator<T, T*, T&> Iterator;//返回最左节点Iterator _begin(){Node* LeftMax = _root;while (LeftMax->_left){LeftMax = LeftMax->_left;}return Iterator(LeftMax);}Iterator _end(){return Iterator(nullptr);}//…………
};
map:
template<class K, class V>
class map
{//提取keystruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};
public:typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;iterator begin(){return _t._begin();}iterator end(){return _t._end();}
private:BRTree<K, pair<const K, V>, MapKeyOfT> _t;
};
set:
template<class K>
class set
{//仿函数提取keystruct SetKeyOfT{const K& operator()(const K& Key){return Key;}};
public:typedef typename BRTree<K, K, SetKeyOfT>::Iterator iterator;iterator begin() {return _t._begin();}iterator end() {return _t._end();}private:BRTree<K, K, SetKeyOfT> _t;
};
五、const迭代器的实现
const迭代器的实现并不难,难的是后续会引发一系列复杂的问题,后文基本上都是在解决这些问题。
将红黑树传入迭代器的T*
、T&
参数用const
修饰
typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;
此时,迭代器的模板参数就不能用具体的参数了,将T*
换成Ptr
,T&
换成Ref
,以便const T*
和const T&
的传入。
template<class T, class Ptr, class Ref>
struct TreeIterator
然后在map和set中也需要和普通迭代器一样去定义
最后在红黑树、map、set中实现const_iterator的begin()、end()
接口
六、设置Key值不可修改
我们知道,map和set中的Key是不可修改的,如何做到呢?
对于set,我们使用“障眼法”(记住这个障眼法,后文要考):
typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator iterator;
typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;
对于map,我们巧妙地将传入底层红黑树的pair<K, V>
改为pair<const K, V>
,方可实现Key值无法修改。
七、修改insert的返回值 + operator[]的实现
我们知道,operator[]
的本质是插入,它是调用insert
的接口实现的,且insert
的返回值被设置为pair<iterator, bool>
。(上篇有详细介绍)
1. 修改insert返回值
非常简单,我们将原本的返回值和当前插入节点的迭代器make_pair即可。
然后我们修改map和set的接口:
对于map,直接修改insert返回值即可
pair<iterator, bool> insert(const pair<K, V>& kv)
{return _t._Insert(kv);
}
然而对于set,出大事了!
如果和map一样处理的话,是会报错的,为什么呢?
//错误写法
pair<iterator, bool> insert(const K& key)
{return _t._Insert(key);
}
还记得我们之前使用的妙计“障眼法”吗?
没错,我们现在要来付出代价了🤣
在set中,iterator
是“假“”的,它本质上是const_iterator
,而我们insert
返回值中的iterator
是“货真价实”的iterator
。
错误点:用pair<const_iterator, bool>
类型作为返回值类型去返回pair<iterator, bool>
类型的值。
别看它们长得像,它们可是两个不同的类型!
【解决方案】
用一个pair<iterator, bool>
类型的变量ret
去接收insert
的返回值,然后再用iterator
去构造成const_iterator
,再返回。
//正确写法
pair<iterator, bool> insert(const K& key)
{pair<typename BRTree<K, K, SetKeyOfT>::Iterator, bool> Ret = _t._Insert(key);//用iterator去构造成const_iterator,再返回return pair<iterator, bool>(Ret.first, Ret.second);
}
还没完,我们的迭代器还不支持用普通迭代器去构造const迭代器,我们需要去写一个构造函数:
typedef TreeIterator<T, T*, T&> iterator;TreeIterator(const iterator& it):_node(it._node)
{}
别看这个函数普普通通,其实别有洞天:
- 当这个迭代器类被实例化为const迭代器,这个函数的作用是一个构造函数。可以用普通迭代器去构造一个const迭代器。
- 当这个迭代器类被实例化为普通迭代器,这个函数就是一个拷贝构造函数。
2. operator[]
直接返回insert返回的迭代器的value即可
V& operator[](const K& key)
{pair<iterator, bool> Ret = insert(make_pair(key, V()));return Ret.first->second;
}
八、封装后的源代码
虽然封装过程已经阐述完毕了,但在代码更改过程中还是有许多细节的,下面给出完整封装后的代码。
1. 红黑树
enum Color
{BLACK,RED
};template<class T>
struct BRTreeNode
{BRTreeNode<T>* _left;BRTreeNode<T>* _right;BRTreeNode<T>* _parent;Color _col;T _data;BRTreeNode(const T& data):_left(nullptr),_right(nullptr),_parent(nullptr),_data(data),_col(RED){ }
};template<class K, class T, class KeyOfT>
class BRTree
{typedef BRTreeNode<T> Node;
public:typedef TreeIterator<T, T*, T&> Iterator;typedef TreeIterator<T, const T*, const T&> const_Iterator;Iterator _begin(){Node* LeftMax = _root;while (LeftMax->_left){LeftMax = LeftMax->_left;}return Iterator(LeftMax);}Iterator _end(){return Iterator(nullptr);}const_Iterator _begin() const{Node* LeftMax = _root;while (LeftMax->_left){LeftMax = LeftMax->_left;}return const_Iterator(LeftMax);}const_Iterator _end() const{return const_Iterator(nullptr);}//查找Node* _find(const K& key){if (_root == nullptr){return nullptr;}Node* cur = _root;while (cur){if (kot(cur->_data) < key){cur = cur->_right;}else if (kot(cur->_data) > key){cur = cur->_left;}else{return cur;}}return nullptr;}// 在红黑树中插入值为val的节点pair<Iterator, bool> _Insert(const T& data){if (_root == nullptr){_root = new Node(data);_root->_col = BLACK;return make_pair(_root, true);}KeyOfT kot;Node* cur = _root;Node* parent = nullptr;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 make_pair(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;//更新颜色//检查是否需要更新颜色,若parent为黑则无需更新while (parent && parent->_col == RED){Node* grandfather = parent->_parent;Node* uncle = nullptr;if (grandfather->_left == parent){uncle = grandfather->_right;}else{uncle = grandfather->_left;}if (uncle && uncle->_col == RED)//uncle存在且为红——变色{parent->_col = uncle->_col = BLACK;grandfather->_col = RED;if (grandfather->_parent){if (grandfather->_col == BLACK){break;}else{cur = grandfather;parent = cur->_parent;}}else{grandfather->_col = BLACK;break;}}else//uncle不存在或uncle存在且为黑——旋转+变色{//判断何种情况,该用何种旋转if (grandfather->_right == parent){if (cur == parent->_right){// g// p// cRotateL(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p// cRotateR(parent);RotateL(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}else{if (cur == parent->_left){// g// p// cRotateR(grandfather);parent->_col = BLACK;grandfather->_col = RED;}else{// g// p// cRotateL(parent);RotateR(grandfather);cur->_col = BLACK;grandfather->_col = RED;}}break;}}_root->_col = BLACK;return make_pair(newnode, true);}
2. 迭代器
template<class T, class Ptr, class Ref>
struct TreeIterator
{typedef BRTreeNode<T> Node;typedef TreeIterator<T, Ptr, Ref> Self;typedef TreeIterator<T, T*, T&> iterator;Node* _node;TreeIterator(const iterator& it):_node(it._node){ }TreeIterator(Node* node):_node(node){ }Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}// 迭代器的++操作,让迭代器可以移动Self& operator++(){//右子树存在,下一个访问节点是右子树的最左节点if (_node->_right){Node* LeftMax = _node->_right;while (LeftMax->_left){LeftMax = LeftMax->_left;}_node = LeftMax;}else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_left)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}Self operator++(int){Self tmp(*this);//右子树存在,下一个访问节点是右子树的最左节点if (_node->_right){Node* LeftMax = _node->_right;while (LeftMax->_left){LeftMax = LeftMax->_left;}_node = LeftMax;}else//右子树不存在,沿着祖先路径往上寻找存在左孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_left)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return tmp;}Self& operator--(){//左子树存在,下一个访问节点是左子树的最右节点if (_node->_left){Node* RightMax = _node->_left;while (RightMax->_right){RightMax = RightMax->_right;}_node = RightMax;}else//左子树不存在,沿着祖先路径往上寻找存在右孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_right)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return *this;}Self operator--(int){Self tmp(*this);//左子树存在,下一个访问节点是左子树的最右节点if (_node->_left){Node* RightMax = _node->_left;while (RightMax->_right){RightMax = RightMax->_right;}_node = RightMax;}else//左子树不存在,沿着祖先路径往上寻找存在右孩子的那个祖先{Node* cur = _node;Node* parent = cur->_parent;;while (parent){if (cur == parent->_right)break;else{cur = parent;parent = parent->_parent;}}_node = parent;}return tmp;}// 让迭代器能够支持比较bool operator!=(const Self& s)const{return _node != s._node;}bool operator==(const Self& s)const{return _node == s._node;}
};
3. set
#pragma once#include"BRTree.h"namespace Surplus
{template<class K>class set{//仿函数提取keystruct SetKeyOfT{const K& operator()(const K& Key){return Key;}};public:typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator iterator;typedef typename BRTree<K, K, SetKeyOfT>::const_Iterator const_iterator;const_iterator begin() const{return _t._begin();}const_iterator end() const{return _t._end();}pair<iterator, bool> insert(const K& key){pair<typename BRTree<K, K, SetKeyOfT>::Iterator, bool> Ret = _t._Insert(key);return pair<iterator, bool>(Ret.first, Ret.second);}private:BRTree<K, K, SetKeyOfT> _t;};
}
4. map
#pragma once#include"BRTree.h"namespace Surplus
{template<class K, class V>class map{//提取keystruct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};public:typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::Iterator iterator;typedef typename BRTree<K, pair<const K, V>, MapKeyOfT>::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> Ret = insert(make_pair(key, V()));return Ret.first->second;}pair<iterator, bool> insert(const pair<K, V>& kv){return _t._Insert(kv);}private:BRTree<K, pair<const K, V>, MapKeyOfT> _t;};}