红黑树
1.概述
定义
由红、黑两色节点组成的二叉搜索树若满足以下条件,即为红黑树 (red-black tree):
(1) 树根始终为黑色
(2) 外部节点均为黑色
(3) 其余节点若为红色,则其孩子节点必为黑色
(4) 从任一外部节点到根节点的沿途,黑节点的数目相等
从以上条件可得:红节点均为内部节点,且其父节点及左、右孩子必然存在。红节点之父必为黑色,因此树中任一通路都不含相邻的红节点。
在从根节点通往任一节点的沿途,除去根节点本身,沿途所经黑节点的总数称作该节点的黑深度(black depth)——根节点的黑深度为0,其余依此类推。
在从任一节点通往其任一后代外部节点的沿途,沿途所经黑节点的总数称作该节点的黑高度(black height)。
如此,所有外部节点的黑高度均为0,其余依此类推。
(2,4)-树
自顶而下逐层考查红黑树各节点。每遇到一个红节点,都将对应的子树整体提升一层,从而与其父节点(必黑)水平对齐,二者之间的联边则相应地调整为横向。此时,若将原红黑树的节点视作关键码,沿水平方向相邻的每一组(父子至多三个)节点即恰好构成4阶B-树的一个节点。
以下将不再严格区分红黑树中的节点及其在(2,4)-树中对应的关键码。当然,照此理解,此时的关键码也被赋予了对应的颜色。对照红黑树的条件,(2,4)-树中的每个节点应包含且仅包含一个黑关键码,同时红关键码不得超过两个。而且,若某个节点果真包含两个红关键码,则黑关键码的位置必然居中。
2.红黑树接口定义
基于BST模板类,可派生出RedBlack模板类
#include "../BST/BST.h" //基于BST实现RedBlack
template <typename T> class RedBlack : public BST<T> { //RedBlack树模板类
protected:
void solveDoubleRed(BinNodePosi(T) x); //双红修正
void solveDoubleBlack(BinNodePosi(T) x); //双黑修正
int updateHeight(BinNodePosi(T) x); //更新节点x的高度
public:
BinNodePosi(T) insert(const T& e);
bool remove(const T& e);
// BST::search()等其余接口可直接沿用
};
代码8.13 基于BST定义的红黑树接口
这里还需使用此前二叉树节点模板类BinNode中预留的两个成员变量height和color。
仿照AVL树的实现方式,可借助辅助宏来检查节点的颜色以及判定是否需要更新(黑)高度记录,如此可大大简化相关算法的描述。
#define IsBlack(p) (!(p) || (RB_BLACK == (p)->color)) //外部节点也视作黑节点
#define IsRed(p) (!IsBlack(p)) //非黑即红
#define BlackHeightUpdated(x) ( \
(stature((x).lChild) == stature((x).rChild)) && \
((x).height == (IsRed(&x) ? stature((x).lChild) : stature((x).lChild) + 1)) \
) //RedBlack高度更新条件
代码8.14 用以简化红黑树算法描述的宏
template <typename T> int RedBlack<T>::updateHeight(BinNodePosi(T) x) { //更新红黑树节点高度
x->height = max(stature(x->lChild), stature(x->rChild)); //孩子一般黑高度相等,除非出现双黑
return IsBlack(x) ? x->height++ : x->height; //若当前节点为黑,则计入黑深度
} //因统一定义stature(NULL) = -1,故height比黑高度少一,好在不致影响到各种算法中的比较判断
代码8.15 红黑树节点的黑高度更新
此处的height已不再是指常规的树高,而是红黑树的黑高度。故如代码8.15所示,节点黑高度需要更新的情况共分三种:或者左、右孩子的黑高度不等;或者作为红节点,黑高度与其孩子不相等;或者作为黑节点,黑高度不等于孩子的黑高度加一。
3.节点插入算法
节点插入与双红现象
template <typename T> BinNodePosi(T) RedBlack<T>::insert(const T& e) { //将关键码e插入红黑树
BinNodePosi(T) & x = search(e); if (x) return x; //确认目标节点不存在(留意对_hot的设置)
x = new BinNode<T>(e, _hot, NULL, NULL, -1); _size++; //创建红节点x:以_hot为父,黑高度-1
solveDoubleRed(x); return x; //经双红修正后,即可返回
} //无论e是否存在于原树中,返回时总有x->data == e
代码8.16 红黑树insert()接口
因新节点的引入,而导致父子节点同为红色的此类情况,称作“双红”(double red)。为修正双红缺陷,可调用solveDoubleRed(x)接口。每引入一个关键码,该接口都可能迭代地调用多次。在此过程中,当前节点x的兄弟及两个孩子(初始时都是外部节点),始终均为黑色。
将x的父亲与祖父分别记作p和g。既然此前的红黑树合法,故作为红节点p的父亲,g必然存在且为黑色。g作为内部节点,其另一孩子(即p的兄弟、x的叔父)也必然存在,将其记作u。
双红修正 (RR-1)
考查u为黑色的情况。此时,x的兄弟、两个孩子的黑高度,均与u相等。
此时红黑树条件(3)的违反,从B-树角度等效地看,即同一节点不应包含紧邻的红色关键码。故如图8.25(c’)所示,只需令黑色关键码与紧邻的红色关键码互换颜色。从图(c)红黑树的角度看,这等效于按中序遍历次序,对节点x、p和g及其四棵子树,做一次局部“3 + 4”重构。
双红修正(RR-2)
考查节点u为红色的情况。此时,u的左、右孩子非空且均为黑色,其黑高度必与x的兄弟以及两个孩子相等。
此时红黑树条件(3)的违反,从B-树角度等效地看,即该节点因超过4度而发生上溢。
以图8.26(b)为例。从图(c)红黑树的角度来看,只需将红节点p和u转为黑色,黑节点g转为红色,x保持红色。从图(c’)B-树的角度来看,等效于上溢节点的一次分裂。
双红修正算法的实现
/******************************************************************************************
* RedBlack双红调整算法:解决节点x与其父均为红色的问题。分为两大类情况:
* RR-1:2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
* RR-2:3次颜色翻转,3次黑高度更新,0次旋转,需要递归
******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleRed(BinNodePosi(T) x) { //x当前必为红
if (IsRoot(*x)) //若已(递归)转至树根,则将其转黑,整树黑高度也随之递增
{ _root->color = RB_BLACK; _root->height++; return; } //否则,x的父亲p必存在
BinNodePosi(T) p = x->parent; if (IsBlack(p)) return; //若p为黑,则可终止调整。否则
BinNodePosi(T) g = p->parent; //既然p为红,则x的祖父必存在,且必为黑色
BinNodePosi(T) u = uncle(x); //以下,视x叔父u的颜色分别处理
if (IsBlack(u)) { //u为黑色(含NULL)时
if (IsLChild(*x) == IsLChild(*p)) //若x与p同侧(即zIg-zIg或zAg-zAg),则
p->color = RB_BLACK; //p由红转黑,x保持红
else //若x与p异侧(即zIg-zAg或zAg-zIg),则
x->color = RB_BLACK; //x由红转黑,p保持红
g->color = RB_RED; //g必定由黑转红
///// 以上虽保证总共两次染色,但因增加了判断而得不偿失
///// 在旋转后将根置黑、孩子置红,虽需三次染色但效率更高
BinNodePosi(T) gg = g->parent; //曾祖父(great-grand parent)
BinNodePosi(T) r = FromParentTo(*g) = rotateAt(x); //调整后的子树根节点
r->parent = gg; //与原曾祖父联接
} else { //若u为红色
p->color = RB_BLACK; p->height++; //p由红转黑
u->color = RB_BLACK; u->height++; //u由红转黑
if (!IsRoot(*g)) g->color = RB_RED; //g若非根,则转红
solveDoubleRed(g); //继续调整g(类似于尾递归,可优化为迭代形式)
}
}
代码8.17 双红修正solveDoubleRed()
4.节点删除算法
节点删除与双黑现象
template <typename T> bool RedBlack<T>::remove(const T& e) { //从红黑树中删除关键码e
BinNodePosi(T) & x = search(e); if (!x) return false; //确认目标节点存在(留意对_hot的设置)
BinNodePosi(T) r = removeAt(x, _hot); if (0 >= --_size) return true; //实施删除
// assert: _hot某一孩子刚被删除,且被r所指节点(可能是NULL)接替。以下检查是否失衡,并做必要调整
if (!_hot) //若刚被删除的是根节点,则将其置黑,并更新黑高度
{ _root->color = RB_BLACK; updateHeight(_root); return true; }
// assert: 以下,原x(现r)必非根,_hot必非空
if (BlackHeightUpdated(*(_hot))) //若所有祖先的黑深度依然平衡,则无需调整
return true;
if (IsRed(r)) //否则,若r为红,则叧需令其转黑
{ r->color = RB_BLACK; r->height++; return true; }
// assert: 以下,原x(现r)均为黑色
solveDoubleBlack(r); return true; //经双黑调整后返回
} //若目标节点存在且被删除,迒回true;否则返回false
代码8.18 红黑树remove()接口
如图(a)和(a’)所示,若x为红色,则在删除x并代之以r后,条件(3~4)依然满足;反之,若x为黑色,则要看其替代者r的颜色
如图(b)和(b’)所示,若r为红色,则只需将其翻转为黑色,即可使条件(3~4)重新满足。
然而如图(c)和(c’)所示,若x和r均为黑色,则为使条件(3~4)重新成立,还需要做略微复杂一些的处理。
因某一无红色孩子的黑节点被删除,而导致的此类复杂情况,称作“双黑” (double black)现象。此时,需从r出发调用solveDoubleBlack(r)算法予以修正。
自然,原黑节点x的兄弟必然非空,将其记作s;x的父亲记作p,其颜色不确定。以下视s和p颜色的不同组合,按四种情况分别处置。
双黑修正(BB-1)
节点x的另一孩子w = NULL,故从B-树角度可以等效地理解为:关键码x原所属的节点发生下溢;
双黑修正(BB-2-R)
节点s及其两个孩子均为黑色时,视节点p颜色的不同,又可进一步分为两种情况。
先考虑p为红色的情况。如图8.30(a)所示,即为一种典型的此类情况(与之对称的情况自行补充
双黑修正(BB-2-B)
再考虑节点s、s的两个孩子以及节点p均为黑色的情况。如图8.31(a)所示,即为一种典型的此类情况(与之对称的情况,请自行补充
双黑修正(BB-3)
最后,考虑节点s为红色的情况。
双黑修正算法的实现
/******************************************************************************************
* RedBlack双黑调整算法:解决节点x与被其替代的节点均为黑色的问题
* 分为三大类共四种情况:
* BB-1 :2次颜色翻转,2次黑高度更新,1~2次旋转,不再递归
* BB-2R:2次颜色翻转,2次黑高度更新,0次旋转,不再递归
* BB-2B:1次颜色翻转,1次黑高度更新,0次旋转,需要递归
* BB-3 :2次颜色翻转,2次黑高度更新,1次旋转,转为BB-1或BB2R
******************************************************************************************/
template <typename T> void RedBlack<T>::solveDoubleBlack(BinNodePosi(T) r) {
BinNodePosi(T) p = r ? r->parent : _hot; if (!p) return; //r的父亲
BinNodePosi(T) s = (r == p->lChild) ? p->rChild : p->lChild; //r的兄弟
if (IsBlack(s)) { //兄弟s为黑
BinNodePosi(T) t = NULL; //s的红孩子(若左、右孩子皆红,左者优先;皆黑时为NULL)
if (HasLChild(*s) && IsRed(s->lChild)) t = s->lChild;
else if (HasRChild(*s) && IsRed(s->rChild)) t = s->rChild;
if (t) { //黑s有红孩子:BB-1
RBColor oldColor = p->color; //备份原子树根节点p颜色,并对t及其父亲、祖父
BinNodePosi(T) b = FromParentTo(*p) = rotateAt(t); //重平衡,并将新子树的左、右孩子染黑
if (HasLChild(*b)) b->lChild->color = RB_BLACK; updateHeight(b->lChild); //左孩子
if (HasRChild(*b)) b->rChild->color = RB_BLACK; updateHeight(b->rChild); //右孩子
b->color = oldColor; updateHeight(b); //新子树根节点继承原根节点的颜色
} else { //黑s无红孩子
s->color = RB_RED; s->height--; //s转红
if (IsRed(p)) { //BB-2R
p->color = RB_BLACK; //p转黑,但黑高度不变
} else { //BB-2B
p->height--; //p保持黑,但黑高度下降
solveDoubleBlack(p);
}
}
} else { //兄弟s为红:BB-3
s->color = RB_BLACK; p->color = RB_RED; //s转黑,p转红
BinNodePosi(T) t = IsLChild(*s) ? s->lChild : s->rChild; //取t与其父s同侧
_hot = p; FromParentTo(*p) = rotateAt(t); //对t及其父亲、祖父做平衡调整
solveDoubleBlack(r); //继续修正r处双黑——此时的p已转红,故后续只能是BB-1或BB-2R
}
}
代码8.19 双黑修正solveDoubleBlack()
完。