红黑树是一种自平衡二叉查找树,它能保证在最坏的情况下,基本的动态集合操作(search,insert,delete,predecessor,successor,minimum,maximum)的时间复杂度为o(lgN)。
- 基本性质
树中每个结点包含5个域:color,key, left, right, p。如果某个结点没有一个子结点或父结点,则该结点相应的指针域p的值为NIL。(NIL为指向二叉查找树的外结点【叶子】的指针,而带关键字的结点视为树的内结点)。
放了方便,对于一棵红黑树T来说,引入一个哨兵nil[T]来代表所有NIL。哨兵与数的普通结点具有相同域的对象,其color域为BLACK,而它的其他域key, left, right, p可以设置成任意允许的值。这样不仅可以节省空间,也可保证结点node的NIL孩子视为父结点为node的普通结点。
红黑树应满足的基本性质:
- 每个结点的color域的值或是红的【red】,或是黑的【black】
- 根节点是黑的,每个叶子结点(NIL)为黑的
- 如果一个结点是红的,则它的两个孩子都是黑的
- 对于每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点
图1,给出了一棵红黑树的例子
图 1
相关概念:
【黑高度】从某个结点node出发(不包括该结点)到达一个叶结点的任意一条路径上,黑色结点的个数
相关结论:
- 一棵有n个内结点的红黑树的高度至多为2lg(n+1)
- 基本操作
- 旋转
当在含有n个关键字的红黑树上运行时,对树进行“插入”和“删除”操作对树进行了修改,可能会违反红黑树性质。为此,需要改变树中某些结点的颜色以及指针结构,以保持这些基本性质。
旋转就是通过改变指针结构从而保持二叉查找树性质的查找树局部操作。分为【左旋】和【右旋】两种旋转。下面以【左旋】为例。
【左旋】在结点x上做左旋操作,假设它的右孩子y不是nil[T]; x可以是树内任意右孩子不是nil[T]的结点。右旋以x到y之间的链为“支轴”。它使y成为该子树新的根,x成为y的左孩子,而y的左孩子则成为x的右孩子。如图2:
图 2
【右旋】与左旋是对称的。图2中,右边结构---->左边结构
- 插入
向一棵含有n个结点的红黑树中插入一个新结点的操作时间复杂度为O(lgn)。插入分为两个步骤:一,将待插入结点z插入到树T内,并将z的color域值设为RED;二,重新着色并旋转,保证其满足红黑树的基本性质。
一,插入结点z
RB_INSERT(T, z),将结点z插入到树T内,伪代码如下:
RB_INSERT(T, z)
{
y = nil[T];
x = root[T];
// 遍历rb_tree,找到插入结点位置
while( x != nil[T] ) {
y = x;
if( key[z] < key[x] ) {
x = left[x];
}else {
x = right[x];
}
}
// 插入结点z
p[z] = y;
if( y == nil[T] ) { // 根结点
root[T] = z;
}else {
if( key[z] < key[y] )
left[y] = z;
else
right[y] = z;
}
// 设置z的域值
left[z] = nil[T];
right[z] = nil[T];
color[z] = RED;
// 调整红黑树:重新着色及旋转
RB_INSERT_FIXUP(T, z);
}
RB_INSERT_FIXUP(T, z)的伪代码:
RB_INSERT_FIXUP(T, z)
{
while( color[p[z]] == RED ) { // z的父结点color域为RED
if( p[z] == left[p[p[z]]] ) { // z的父结点p[z]为p[z]的父结点的左孩子
y = right[p[p[z]]];
if( color[y] == RED ) {
color[p[z]] = BLACK;
color[y] = BLACK;
color[p[p[z]]] = RED;
z = p[p[z]];
}else if( z == right[p[z]] ) { // z为其父结点的右孩子
z = p[z];
LEFT_ROTATE(T, z);
}else {
color[p[z]] = BLACK;
color[p[p[z]]] = RED;
RIGHT_ROTATE(T, p[p[z]]);}
}else {
// do something
// same as then clause with "right" and "left" exchange
}
}
color[root[T]] = BLACK;
}
- 删除
删除操作的时间复杂度也是O(lgn)。在删除一个结点后,调用辅助程序RB_DELETE_FIXUP来改变结点的颜色并做旋转,从而保持红黑树的性质。
RB_DELETE(T, z)的伪代码:
RB_DELETE(T, z)
{
if( left[z] ==nil[T] || right[z] == nil[T] ) {
y = z;
}else {
y = TREE_SUCCESSOR(z); // z的后继
}
if( left[y] != nil[T] )
x = left[y];
else
x = right[y];
p[x] = p[y];
if( p[y] == nil[T] ) {
root[T] = x;
}else if( y == left[p[y]] ){
left[p[y]] = x;
}else {
right[p[y]] = x;
}
if( y != z ) {
key[z] = key[y];
// copy y's satellite data into z
}
if( color[y] == BLACK ) {
RB_DELETE_FIXUP(T, x);
}
return y;
}
如果删除结点y是红色的,红黑树的性质保持不变,原因如下:
1)树中各结点的黑高度没有变化
2)不存在两个相邻的红色结点
3)居然y为红色,所以不可能为根节点,故根节点依然为黑色
如果删除结点是黑色的,则调用RB_DELETE_FIXUP调整树的结构;
RB_DELETE_FIXUP(T, x)
{
while( x != root[T] && color[x] == BLACK ) {
if( x == left[p[x]] ) { // x为左孩子
w = right[p[x]]; // w为p[x]的右孩子(x的兄弟结点)
if( color[w] == RED ) {
color[w] = BLACK;
LEFT_ROTATE(T, p[x]);
w = right[p[x]];
}
if( color[left[w]] == BLACK && color[right[w]] == BLACK ) {
color[w] = RED;
x = p[x];
}else if( color[right[w]] == BLACK ) {
color[left[w]] = BLACK;
color[w] = RED;
RIGHT_ROTATE(T, w);
w = right[p[x]];
}else {
color[w] = color[p[x]];
color[p[x]] = BLACK;
color[right[w]] = BLACK;
LEFT_ROTATE(T, p[x]);
x = root[T]; }
}else { // x为右孩子
// do something
// same as then clause with "right" and "left" exchange
}
}
}
参照:《算法导论》第13章 红黑树