一.红黑树的概念与规则
红黑树是一棵满二叉树,他的每个结点相较于满二叉树多增加一个储存位来表示结点的颜色,可以是红色或者黑色。通过对任何一条从根到叶子的路径上各个结点的颜色进行约束,确保没有一条路径会比其他路径长处两倍,因此是接近平衡的。
在红黑树下,每个结点不是红色就是黑色,其中根结点一定为黑色。在同一条路径中不会有连续的红色结点。对于任意的一个结点,从该结点到所有null结点的简单路径上均包含数量相同的黑色结点。
那么红黑树是如何保证其规则的实现的呢?
由上述规则可知,红黑树没条分支上的黑色结点数量都是一致的,如果我们将新添加进来的结点颜色设置为黑色,那么就要使得其他分支的结点数都要增加一个,这样处理起来难度很高;倘若我们将进入的结点设置为红色,只需要使得同一条路径下不连续出现两个红色结点即可,所以我们统一将插入的结点设置为红色。
二.红黑树的实现
1.红黑树的结构
红黑树的结构由枚举变量红色与黑色,存入结点的键值,左右父母结点构成。
cppenum 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 K,class T,class KeyOfT> class RBTree { typedef RBTreeNode<T> Node; public: private: Node* _root = nullptr; };
2.红黑树的插入
当我们插入一个数时,首先他要满足二叉树的规则,我们就按照小值在左大值在右安排,找到合适的空结点位置存放。第二步开始考虑红黑树的规则,若树为空,直接插入黑色结点,若不为空,由上述可知,我们的红黑树插入的结点需为红色结点,因为若为黑色结点,就会破坏了所有分支的平衡性,而插入红色结点只会破坏当前分支的平衡性,所以我们选择插入红色结点。
当我们插入了红色结点后,我们要考虑如何使得树达到平衡。我们将当前结点标识为cur,c的父亲为parent,parent的父亲为grandparent,parent的兄弟为uncle。
若此时parent为红,cur为红,那么grandparent一定为黑,uncle可能为黑或者空。此时我们就需要进行分类讨论进行相应的旋转和变色处理。
下面我们一步一步进行
首先根据二叉树规则插入结点
cppif (_root == nullptr) { _root = new Node(data); _root->_col = BLACK; return true; } Node* parent = nullptr; Node* cur = _root; KeyOfT kot; while (cur) { if (kot(cur->_data) < kot(data)) { parent = cur; cur = cur->_left; } else if (kot(cur->_data) > kot(data)) { parent = cur; cur = cur->_right; } else { return false; } } cur = new Node(data); cur->_col = RED; if (kot(parent->_data) > kot(data)) { parent->_left = cur; } else if (kot(parent->_data) < kot(data)) { parent->_right = cur; } cur->_parent = parent;
遍历整棵树,若data值相较于当前结点大就遍历到右边,小就遍历到左树,直到结点为空时停止。然后再找到cur结点的parent结点就完成满足了二叉树规则。
第二步我们要对红黑树进行变色和旋转处理,主要分为6种情况

其中parent在grandparents左与parent在 grandparents在右逻辑一致,所以这里我们只对一边进行讲解。
1.叔叔结点存在且为红
我们用白色来代表黑色结点,此时的G为grandparents结点且为黑。
此时我们只需要将P和U结点颜色变为黑色,G结点变为红色,即可满足当前子树的平衡。然后再将cur交给G进行下一次判断即可。
2.叔叔不存在或存在且为黑
(1)cur在parent的左边
由于此时两条路径上红色结点的数量相差2,所以我们要进行旋转操作。面对当前情况,我们可以对grandparents进行右旋操作,使parent结点为根。
(2)cur在parent的右边
此时两个红色结点不处于同一直线上,所第一步我们先对parent进行左旋操作,使得与grandparents和cur在同一直线,再对grandparents进行右旋操作,使红色结点变为根结点,最后进行变色处理即可,下面放上变化图
对parent结点左旋操作后
对grandparents结点右旋操作后
由于剩余的三种情况与上述三种情况类似,只是旋转方向相反,所以不在此进行讲解。
下面放上代码:
cppbool insert(const T& data) { if (_root == nullptr) { _root = new Node(data); _root->_col = BLACK; return true; } Node* parent = nullptr; Node* cur = _root; KeyOfT kot; while (cur) { if (kot(cur->_data) < kot(data)) { parent = cur; cur = cur->_left; } else if (kot(cur->_data) > kot(data)) { parent = cur; cur = cur->_right; } else { return false; } } cur = new Node(data); cur->_col = RED; if (kot(parent->_data) > kot(data)) { parent->_left = cur; } else if (kot(parent->_data) < kot(data)) { parent->_right = cur; } cur->_parent = parent; //变色旋转 while (parent && parent->_col == RED) { Node* grandfather = parent->_parent; if (parent == grandfather->_left) { // g // p u // c Node* uncle = grandfather->_right; if (uncle && uncle->_col == RED)//叔叔存在且为红 { parent->_col = BLACK; uncle->_col = BLACK; grandfather->_col = RED; cur = grandfather; parent = cur->_parent; } else //叔叔不存在或为黑 { if (cur == parent->_left)//cur在左 { // 调整前 // g(黑) // p(红) u(黑或空) // c(红) RotateR(grandfather); parent->_col = BLACK; grandfather->_col = RED; // 调整后 // p(黑) // c(红) g(红) // u(黑或空) } else//cur在右 { // 调整前 // g(黑) // p(红) u(黑或空) // c(红) RotateL(parent); RotateR(grandfather); cur->_col = BLACK; grandfather->_col = RED; // 调整后 // c(黑) // p(红) g(红) // u(黑或空) } break; } } else { Node* uncle = grandfather->_left; if (uncle && uncle->_col == RED) { grandfather->_col = RED; parent->_col = BLACK; uncle->_col = BLACK; 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 true; } void RotateR(Node* parent) { Node* subL = parent->_left; Node* subLR = subL->_right; parent->_left = subLR; subL->_right = parent; if (subLR) subLR->_parent = parent; Node* parentParent = parent->_parent; parent->_parent = subL; //if (parentParent == nullptr) if (parent == _root) { _root = subL; subL->_parent = nullptr; } else { if (parentParent->_left == parent) { parentParent->_left = subL; } else { parentParent->_right = subL; } subL->_parent = parentParent; } } void RotateL(Node* parent) { Node* subR = parent->_right; Node* subRL = subR->_left; parent->_right = subRL; if (subRL) subRL->_parent = parent; Node* parentParent = parent->_parent; subR->_left = parent; parent->_parent = subR; if (parentParent == nullptr) { _root = subR; subR->_parent = nullptr; } else { if (parent == parentParent->_left) { parentParent->_left = subR; } else { parentParent->_right = subR; } subR->_parent = parentParent; } }
3.红黑树的平衡检查
我们用递归计算红黑树的黑色结点数量,在各条路径进行判断即可。
cpp
bool check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
if (refNum != blackNum)
{
cout << "存在黑色结点数量不相同的路径" << endl;
return false;
}
return true;
}
if (root->_col == RED && root->_parent && root->_parent->_col == RED)
{
cout << root->_data << "存在连续的红色结点" << endl;
return false;
}
if (root->_col == blackNum)
{
blackNum++;
}
return check(root->_left, blackNum, refNum)
&& check(root->_right, blackNum, refNum);
}
bool IsBalanceTree()
{
if (_root == nullptr)
return true;
if (_root->_col == RED)
return false;
int refNum = 0;
Node* cur = _root;
while (cur)
{
if (cur->_col == BLACK)
{
++refNum;
}
cur = cur->_left;
}
return check(_root, 0, refNum);
}
三.总结
红黑树相较于前文的AVL树相比而言,时间复杂度都是logn级别的,红黑树对平衡条件管理更加宽松,使得其调整的次数大大减少,通过红黑规则的巧妙布局,保持高效的操作性的同时也降低了平衡维护的复杂度,适用场景广。