赞
踩
之前我们简单学习了一下搜索树和平衡搜索树,今天我们来学习一下红黑树
红黑树,是一种二叉搜索树,但在每个结点上增加一个存储位表示结点的颜色,可以是Red或Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树确保没有一条路径会比其他路径长出俩倍,因而是接近平衡的。
我们默认新插入的节点为红色,因为根据第四条规则,黑色节点不能轻易添加
template<class K, class V> struct RBTreeNode { using Node = RBTreeNode<K, V>; Node* _left; Node* _right; Node* _parent; pair<K, V> _kv; Color _col; RBTreeNode(const pair<K, V>& kv) :_left(nullptr) , _right(nullptr) , _parent(nullptr) , _kv(kv) ,_col(RED) {} };
插入节点最终的颜色的关键是看叔叔节点
原因:
我的父节点是红的,那我的爷爷节点一定是黑的,并且爷爷一定存在(因为根节点不为红色),所以就是看叔叔节点的颜色来判断插入结点的颜色
修改原则:
a. 叔叔存在且为红,则将父节点和叔叔节点变黑再将爷爷节点变红,
b. 叔叔为黑或叔叔不存在
针对第二点,我们需要考虑会不会最长路径超出最短路径的二倍或者面临下面这种情况(出现连续的红色节点且无法修改),那么这里我们就必须要进行旋转+变色。
(与AVL的旋转相同)
(好像有个小规律:parent始终为黑色)
旋转的规则:
p为g的左孩子,cur为p的左孩子,则进行右单旋转;相反,
p为g的右孩子,cur为p的右孩子,则进行左单旋转
p变黑,g变红
c. cur为红,p为红,g为黑,u不存在/u为黑
更新规则:
bool Insert(const pair<K, V>& kv) { //1.按照搜索树规则插入:先找到合适的位置,然后链接 if (_root == nullptr) { _root = new Node(kv); _root->_col = BLACK; return true; }//如果树为空,特殊判断 Node* parent = nullptr; Node* cur = _root; while (cur != nullptr) { if (cur->kv.first > kv.first) { parent = cur; cur = cur->_left; } else if (cur->kv.first < kv.first) { parent = cur; cur = cur->_right; } else { return false; } } cur = new Node(kv); //默认新节点是红色 if (parent->kv.first > kv.first) { parent->_right = cur; } else { parent->_right = cur; } cur->_parent = parent; //持续更新parent,直到更新到根节点 while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; //根据父亲,找叔叔 if (parent == grandfather->_left) { Node* uncle = grandfather->_right; //情况一:叔叔存在且为红 if (uncle != nullptr && uncle->_col == RED) { //变色 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; //更新节点 cur = grandfather; parent = cur->_parent; } //情况二:叔叔不存在或者为黑 else { 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 { Node* uncle = grandfather->_left; if (uncle != nullptr && uncle->_col == RED) { //变色 parent->_col = uncle->_col = BLACK; grandfather->_col = RED; //更新节点 cur = grandfather; parent = cur->_parent; } else { //结构图 // g // u p // c if (cur == parent->_right) { 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 true; }
要验证这棵树是不是红黑树,要根据他的规则去判断
所以我们可以根据这三点去判断
bool Check(Node* cur) { if (cur == nullptr) return true; if (cur->_col == RED && cur->_parent->_col == RED) //第二点 //根据孩子找父亲 { cout << cur->_kv.first << "存在连续的红色节点" << endl; return false; } return Check(cur->_left) && Check(cur->_right); } bool IsBalance() { if (_root && _root->_col == RED) return false; //第一点 return Check(_root); }
以上是对于第一点和第二点的验证,对于第三点,我们要求每两条路径的黑色节点数量,判断是否相等,
我们可以用老方法:增加参数个数
bool Check(Node* cur, int blackNum, int refBlackNum) { if (cur == nullptr) { if (refBlackNum != blackNum) { throw("黑色节点的数量不相等"); } return true; } if (cur->_col == RED && cur->_parent->_col == RED) { cout << cur->_kv.first << endl; throw("存在连续的红色节点"); } if (cur->_col == BLACK) ++blackNum; return Check(cur->_left, blackNum, refBlackNum) && Check(cur->_right, blackNum, refBlackNum); } bool IsBalance() { if (_root && _root->_col == RED) return false; int refBlackNum = 0; Node* cur = _root; while (cur) { if(cur->_col == BLACK) refBlackNum++; cur = cur->_left; } return Check(_root, 0, refBlackNum); }
红黑树的实现还没写完,下一篇文章会介绍红黑树的迭代器的简单实现和map和set是如何封装的 ,下次见~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。