STL之map&set续|红黑树篇
- 红黑树
- 红黑树的规则
- 红黑树的模拟实现
- map&set的模拟实现
- 封装map/set
- 关于红黑树的复用
- 红黑树模板参数
- set的const迭代器问题
红黑树
红黑树也是一种搜索二叉树,它通过颜色和规则控制树上没有一条路径会比其他路径长两倍,实现接近平衡。它比AVL常用的一个原因就是它的控制条件不那么严苛,即不需要频繁旋转而效率较高。
红黑树的规则
规则:
- 每个结点不是黑色就是红色
- 根结点是黑色
- 红结点的两个孩子结点都是黑的
- 对于每个结点,从该结点到其后代所有结点的路径上,黑色结点数相同
- 叶子结点是黑色的(指空结点)
可以看出,首先不存在连续的红结点。又因为每条路上黑结点数同,所以最短路径就是全黑,最长也是黑红相间,确实保证了树上没有一条路径会比其他路径长两倍。
再看看查找效率:假设黑结点N个,最长路径就是2log2N ,树的总结点数在N ~ 2N之间,这里按2N算,那么AVL树的最长路径是log2(2N+1),约等于log2N。所以红黑树最坏查找时间约是AVL树的两倍。尽管如此,差别不大。
红黑树的模拟实现
先讨论一下红黑树的插入:
首先插入结点我们默认应该给红,因为如果给黑会影响黑结点数量,导致可能需要大范围调整才能让规则4满足。
- 假设插入位置的父亲是黑,那就正正好了,没违反任何规则,插入结束😜(这也是默认红色的好处)
- 如果插入位置的父亲是红,违反规则3,下面根据叔叔的情况分类讨论:
- 情况一:叔叔(uncle)存在且为黑。这是最简单的情况。只需要父亲(parent)和叔叔变黑,祖父(grandparent)变红,这样祖父以下就没问题了,但因为祖父变色了,需要继续向上调整。
- 情况二:uncle不存在。先左旋/左右双旋,再变色。注意,尽量保证顶部结点为黑,这样不会违反规则三,从而不再继续向上调整。
- 情况三:uncle存在且为黑。先右旋/右左双旋,再变色。也注意一下顶部颜色。
注意下图中只是最简单的情况,比如一些父亲的右结点未画出,它们依然需要跟随旋转,只是不影响情况的分类判断,故未画出。
细节都在代码里:
enum COLOR {RED,BLACK
};
template<class T>
struct RBTreeNode
{RBTreeNode<T>* _left = nullptr;RBTreeNode<T>* _right = nullptr;RBTreeNode<T>* _parent = nullptr;//方便起见,采取三叉链T _data;COLOR _col = RED;//优先给红RBTreeNode(const T& data) :_data(data) {}
};
template<class T, class Ref, class Ptr>
class __RBTreeIterator
{
public:typedef RBTreeNode<T> node;typedef __RBTreeIterator<T, Ref, Ptr> Self;typedef __RBTreeIterator<T, T&, T*>normal_iterator;__RBTreeIterator(node* node) :_node(node) {}//下面是高手操作,const迭代器实例化类模板时,这里是在用普通迭代器来构造const迭代器,太妙了//而如果是普通迭代器实例化类模板时,这就是普通迭代器的正常拷贝构造__RBTreeIterator(const normal_iterator& it) :_node(it._node) {}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}bool operator!=(const Self& s){return _node != s._node;}bool operator==(const Self& s){return _node == s._node;}Self& operator++(){if (_node->_right)//右不为空,则右的最左节点{_node = _node->_right;while (_node->_left)_node = _node->_left;}else//右为空,沿着到根的路径,找孩子是父亲的左的那个节点{while (_node->_parent && _node != _node->_parent->_left){_node = _node->_parent;}if (!_node->_parent)_node = nullptr;else _node = _node->_parent;}return *this;}Self& operator--()//与++逻辑相反{if (_node->_left){_node = _node->_left;}else{while (_node->_parent && _node != _node->_parent->_right)_node = _node->_parent;if (!_node->_parent)_node = nullptr;else _node = _node->_parent;}}node* _node;};
template<class K, class T, class KeyOfT>
class RBTree
{
public:typedef RBTreeNode<T> node;//红黑树传的第二个参数就是节点里存的数据typedef __RBTreeIterator<T, T&, T*>iterator;typedef __RBTreeIterator<T, const T&, const T*>const_iterator;
public:iterator begin()const{node* cur = _root;while (cur && cur->_left){cur = cur->_left;}return iterator(cur);}iterator end()const{return iterator(nullptr);}pair<iterator, bool> Insert(const T& data){if (_root == nullptr){_root = new node(data);_root->_col = BLACK;return make_pair(iterator(_root), true);}node* parent = nullptr;node* cur = _root;KeyOfT kot;while (cur){//这里是不知道data里面的key在哪的,可能data就是K,也可能K是data的first,但是上层的map和set知道,所以需要传一个keyoftif (kot(data) > kot(cur->_data)){parent = cur;cur = cur->_right;}else if (kot(data) < kot(cur->_data)){parent = cur;cur = cur->_left;}else return make_pair(iterator(cur), false);}cur = new node(data);node* res = cur;cur->_parent = parent;if (kot(parent->_data) > kot(data))parent->_left = cur;elseparent->_right = cur;while (parent && parent->_col == RED){node* grandparent = parent->_parent;if (parent == grandparent->_left){node* uncle = grandparent->_right;if (uncle && uncle->_col == RED)//情况1{parent->_col = BLACK;grandparent->_col = RED;uncle->_col = BLACK;cur = grandparent;parent = cur->_parent;}else//情况2+3,uncle的颜色不变 if (uncle == nullptr|| uncle->_col == BLACK){if (cur == parent->_left){RotateR(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{RotateL(parent);RotateR(grandparent);grandparent->_col = RED;//parent->_col =还是红cur->_col = BLACK;}break;}}else{node* uncle = grandparent->_left;if (uncle && uncle->_col == RED)//与上一致{parent->_col = BLACK;grandparent->_col = RED;uncle->_col = BLACK;cur = grandparent;parent = cur->_parent;}else//情况2+3,uncle的颜色不变 if (uncle == nullptr|| uncle->_col == BLACK){if (cur == parent->_right){RotateL(grandparent);parent->_col = BLACK;grandparent->_col = RED;}else{RotateR(parent);RotateL(grandparent);grandparent->_col = RED;//parent->_col =还是红cur->_col = BLACK;}break;}}}_root->_col = BLACK;return make_pair(iterator(res), true);}iterator Find(const K& key){node* cur = _root;while (cur){if (key > KeyOfT()(cur->_data))cur = cur->_right;else if (key < KeyOfT()(cur->_data))cur = cur->_left;else return iterator(cur);}return end();}~RBTree(){Destroy(_root);_root = nullptr;}
private:void Destroy(node* root){if (!root)return;Destroy(root->_left);Destroy(root->_right);delete root;}void RotateL(node* parent){node* subR = parent->_right;node* subRL = subR->_left;node* pparent = parent->_parent;parent->_right = subRL;subR->_left = parent;subR->_parent = pparent;if (subRL)subRL->_parent = parent;parent->_parent = subR;if (pparent){if (pparent->_left == parent)pparent->_left = subR;else pparent->_right = subR;}else _root = subR;}void RotateR(node* parent){node* subL = parent->_left;node* subLR = subL->_right;node* pparent = parent->_parent;parent->_left = subLR;subL->_right = parent;subL->_parent = pparent;if (subLR)subLR->_parent = parent;parent->_parent = subL;if (pparent){if (parent == pparent->_left)pparent->_left = subL;else pparent->_right = subL;}else _root = subL;}
private:node* _root = nullptr;
};
红黑树这里和AVL树的模拟实现一样都不实现erase,较为复杂,而我的目的也不是造轮子。
下面提供一些测试代码:
bool isRBTree()
{if (_root && _root->_col == RED){cout << "root is red" << endl;return false;}int benchMark = -1;return _isRBTree(_root, 1, benchMark);
}
void Inorder()
{_Inorder(_root);
}
int Height()
{return _Height(_root);
}
int _Height(node* root)
{if (!root)return 0;int LeftH = _Height(root->_left) + 1;int RightH = _Height(root->_right) + 1;return LeftH > RightH ? LeftH : RightH;
}
bool _isRBTree(node* root, int blackNum, int& benchMark)
{if (root == nullptr){if (benchMark == -1){benchMark = blackNum;}if (benchMark != blackNum){cout << "黑色节点数量不等" << endl;return false;}return true;}if (root->_col == BLACK)blackNum++;if (root->_col == RED && root->_parent->_col == RED){cout << root << ":parent is red" << endl;return false;}return _isRBTree(root->_left, blackNum, benchMark)&& _isRBTree(root->_right, blackNum, benchMark);
}
void _Inorder(node* root)
{if (!root)return;_Inorder(root->_left);cout << root->_kv.first << " ";_Inorder(root->_right);
}int main()
{//RBTree<int, int>t;int arr[] = { 16,3,7,11,9,26,18,14,15 };//int arr[] = { 4,2,6,1,3,5,15,7,16,14 };//for (int i = 0; i < 9; i++)//{// t.Insert(make_pair(arr[i], i));// //cout << "第" << i << ":";//调试// //cout << t.isRBTree() << endl;// //cout << endl;//}//t.Inorder();/*srand(time(0));const size_t N = 10000000;AVLTree<int, int>t;RBTree<int, int>rbt;for (size_t i = 0; i < N; i++){size_t x = rand() + i;t.Insert(make_pair(x, x));rbt.Insert(make_pair(x, x));}cout << t.isAVLTree() << endl;cout << rbt.isRBTree() << endl;cout << t.Height() << endl;cout << rbt.Height() << endl;for (size_t i = 0; i < N; i++){size_t x = rand();}*/
}
map&set的模拟实现
封装map/set
接下来封装红黑树来实现map/set
namespace lky
{template<class K>class set{public:struct SetKeyOfT{const K& operator()(const K& key){return key;}};typedef typename RBTree<K, K, SetKeyOfT>::const_iterator iterator;//typename告诉编译器是类型名,而非类内静态成员typedef typename RBTree<K, K, SetKeyOfT>::const_iterator const_iterator;//typename告诉编译器是类型名,而非类内静态成员pair<iterator, bool> insert(const K& key){return _t.Insert(key);//这里iterator转为const版发生了特殊拷贝构造,见上文高手操作}iterator begin(){return _t.begin();}iterator end(){return _t.end();}private:RBTree<K, K, SetKeyOfT>_t;//第二个参数传K,红黑树节点里存的就是key了};template<class K, class V>class map{public:struct MapKeyOfT{const K& operator()(const pair<K, V>& kv){return kv.first;}};typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::iterator iterator;typedef typename RBTree<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;pair<iterator, bool> insert(const pair<K, V>& kv){return _t.Insert(kv);}V& operator[](const K& key){return _t.Insert(make_pair(key, V())).first->second;}iterator find(const K& key){return _t.Find(key);}iterator begin(){return _t.begin();}iterator end(){return _t.end();}private:RBTree<K, pair<const K, V>, MapKeyOfT>_t;//防修改pair里的k};
}
关于红黑树的复用
写stl库的大佬很喜欢用模板参数来实现复用,比如list那块的const迭代器和超级无敌万能反向迭代器。还有下面map和set去复用rbtree。
红黑树模板参数
- 库中为了复用红黑树,map和set传给RBTree的第二个参数才是结点中实际存的数据类型,set通过K和T都传K类型实现复用,而map则传pair。注意,map传的pair里面可是const K哦,防止通过迭代器等等来修改pair里的key。
- 而第一个参数是单独的K类型,因为红黑树的一些接口如find/erase需要知道K类型。
- 模板里的KeyOfT是用来取出pair里的key,因为insert里虽然你知道map里pair的第一个参数是key,结构体取成员还不简单,但是set可是直接传的key啊,所以需要一个仿函数保证insert能取得key。
- alloc开内存池不再多言。
- 还有一个compare是为了支持比较key的大小,万一你插了一个自定义类型,对吧。
set的const迭代器问题
库里面set的普通迭代器和const迭代器都是红黑树的const迭代器。在没写迭代器里的拷贝构造之前,set里的insert是编不过的,因为红黑树的insert返回的是普通迭代器,而没有类型转换的话,set里面的insert返回的pair里的迭代器可是const迭代器哦,所以会报错无法从普通迭代器转化成const迭代器。库里是怎么解决的呢?
没错,它写了迭代器的拷贝构造,而且巧妙利用了模板
解析一下,这里的iterator就是永远的普通迭代器,因为他写死了(因为这里Value无论哪种迭代器都传的是T,而非const T,所以typedef的模板里面三个参数固定是T,T&,T*),所以当你传T时,这是个正常的拷贝构造;当你传const T时,这就是个从普通迭代器到const迭代器的特殊拷贝构造,支持了从普通到const的类型转换。
很妙吧,高手啊
本篇博客也就到此为止了,map/set完结撒花。但是来不及悼念了,接下来登场的是哈希大哥。C++的学业快望到头了😘😜