红黑树是一种特殊的二叉搜索树,它在每个结点上增加了一个存储位来表示结点的颜色,可以是RED或BLACK。通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其他路径长出2倍,近似于平衡。
红黑树的特性
每个结点包含5个属性:color、key、left、right和p。如果一个结点没有子结点或父结点,则该结点相应指针属性的值为NIL。把这些NIL视为外部结点,而将带有关键字的结点视为内部结点。一棵红黑树满足以下5条性质:
1. 每个结点或是红色的,或是黑色的;
2. 根结点是黑色的;
3. 每个叶结点[NIL]是黑色的(这里的叶结点指为空[NULL]的结点);
4. 如果一个结点是红色的,则它的两个子结点都是黑色的;
5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。
一个红黑树例子如下:
为了便于处理红黑树代码中的边界条件,使用一个哨兵来代表NIL。对于一棵红黑树T,哨兵T.nil是一个与树中普通结点有相同属性的对象。它的color属性为BLACK,而其他属性key、left、right和p的取值并不重要,为了方便起见可以在程序中设定它们。
注:从某个结点x出发(不含该结点)到达一个叶结点的任意一条简单路径上的黑色结点个数称为该结点的黑高(black-height),记为bh(x)。
红黑树的基本操作——旋转
在对红黑树进行插入或者删除操作之后,树就发生了变化,可能不再满足红黑树的5条性质。为了维护这些性质,必须要改变树中某些结点的颜色及指针结构,而指针结构的修改则是通过旋转来完成的,这是一种能保持二叉搜索树性质的搜索树局部操作。
旋转包括两种:左旋和右旋。对x进行左旋,使得y成为该子树新的根结点,x成为y 的左孩子,y的左孩子成为x的右孩子;对y进行右旋,使得x成为该子树新的根结点,y成为x的右孩子,x的右孩子成为y的左孩子。
对红黑树T中结点x进行左旋的代码如下:
LEFT-ROTATION(T, x)
{
y = x.right;// y为x的右孩子
x.right = y.left;// 将x的右孩子指向y的左孩子
if (y.left != T.nil)
{
y.left.p = x;// 若y的左孩子不为NIL,将其父结点指向x
}
y.p = x.p;// 将y的父节点指向x的父节点
if (x.p == T.nil)
{
T.root = y;// 若x为根结点,将y设为新的根结点
}
else if(x == x.p.left)
{
x.p.left = y;// 若x为其父结点的左孩子,将其父结点的左孩子指向y
}
else
{
x.p.right = y;// 若x为其父结点的右孩子,将其父结点的右孩子指向y
}
y.left = x;// 将y的左孩子指向x
x.p = y;// 将x的父结点指向y
}
对红黑树T中结点y进行右旋的代码如下:
RIGHT-ROTATION(T, y)
{
x = y.left;// x为y的左孩子
y.left = x.right;// 将y的左孩子指向x的右孩子
if (x.right != T.nil)
{
x.right.p = y;// 若x的右孩子不为NIL,将其父结点指向y
}
x.p = y.p;// 将x的父节点指向y的父节点
if (y.p == T.nil)
{
T.root = x;// 若y为根结点,将x设为新的根结点
}
else if(y == y.p.left)
{
y.p.left = x;// 若y为其父结点的左孩子,将其父结点的左孩子指向x
}
else
{
y.p.right = x;// 若y为其父结点的右孩子,将其父结点的右孩子指向x
}
x.right = y;// 将x的右孩子指向y
y.p = x;// 将y的父结点指向x
}
红黑树的基本操作——插入
我们可以在O(log n)的时间内完成向一棵含n个结点的红黑树中插入一个新结点。首先与二叉搜索树插入结点一样,将结点z插入红黑树中,然后将z着色为RED,接着对结点重新着色并旋转,使得红黑树保持其性质。
向红黑树T中插入结点z的代码如下:
RB-INSERT(T, z)
{
x = T.root;// x为根结点
y &