一、红黑树性质
二叉搜索树中若一个节点没有父节点或子节点则该节点相应属性的值设为NIL,我们视NIL为二叉搜索树的叶节点(外部节点),带关键字的为内部节点。一棵红黑树是满足下列五条性质的二叉搜索树:
1. 树的每个节点不是红色就是黑色
2. 树的根节点是黑色
3. 树的每个叶节点(NIL)为黑色
4. 若节点是红色则它的孩子必须是黑色(若有孩子的话)
5. 树中每一个节点到其后代叶节点的路径上黑色节点的数目相同
红黑树黑高的定义:从某个节点出发(不包含此节点)到达其后代叶节点的路径上黑色节点数目称为此节点的黑高,由性质5知节点到其每个后代叶节点的路径上的黑色节点数目是相同的,因此红黑树的黑高可以定义为根节点的黑高。
定理:一棵具有n个内部节点的红黑树的树高至多是2lg(n+1)
证明: 证明以任一节点x为根的红黑树至少有2^bh(x)-1个内部节点。(bh(x)为节点x的黑高)数学归纳法证明:当bh(x)=0,x为叶节点,则x有0个内部节点,命题成立。当bh(x)=1,则x有1个内部节点,命题成立。考虑一个高度为正值且有两个孩子的内部节点x,其黑高为bh(x)它的孩子的黑高为bh(x) 或bh(x)-1(具 体取那个值与X自身的颜色有关),根据命题节点x的每个孩子至少包含2^(bh(x)-1)-1个内部节点,于是,以x为根的子树至少包含2^(bh(x)-1)-1+2^(bh(x)-1)-1+1=2^bh(x)-1个内部节点。命题证明完毕,设树的高度为h,由红黑树性质4知道从根到叶节点(不包括根节点)的任一条路径至少有一半的节点为黑色,因此,根的黑高bh(x)>=h/2,所以由n>=2^bh(x)-1>=2^(h/2)-1,因此可得h<=2lg(n+1)。
注意:通过对任一一条从根到叶节点的简单路径上各个节点颜色的约束,红黑树确保没有一条路径会比其他路径长出2倍,因此红黑树是近似平衡的。
备注:
红黑树和AVL树(平衡二叉树)的区别在于它使用颜色来标识结点的高度,它所追求的是局部平衡而不是AVL树中的非常严格的平衡
参考:
平衡二叉树(AVL)
二、旋转
- 左旋转
left-rotated(T, x)
{
y = x.right;
x.right = y.left;
if(y.left!=T.nil)
{
y.left.p = x;
}
y.p = x.p;
if(x.p == T.nil)
{
T.root = y;
}
else if(x == x.p.left)
{
x.p.left = y;
}
else if(x == x.p.right)
{
x.p.right = y;
}
y.left = x;
x.p = y;
}
- 右旋转
right-rotated(T, y)
{
x = y.left;
y.left = x.right;
if(x.right!=T.nil)
{
x.right.p = y;
}
x.p = y.p;
if(y.p == T.nil)
{
T.root = x;
}
else if(y == y.p.left)
{
y.p.left = x;
}
else if(y == y.p.right)
{
y.p.right = x;
}
x.right = y;
y.p = x;
}
注意:为防止遗漏了指针的赋值,可以查看旋转时都哪些节点的left,right,p指针值发生了变化。
参考:《算法导论》第十三章红黑树上面旋转部分的图
三、插入
rb_insert(T, z)
{
y = T.nil;
x = T.root;
while(x!=T.nil)
{
y = x;
if(z.key<x.key)
{
x = x.left;
}
else
{
x = x.right;
}
}
z.p = y;
if(y==T.nil)
{
T.root = z;
}
else if(z.ley<y.key)
{
y.left = z;
}
else
{
y.right = z;
}
z.clolr = RED;
z.left = T.nil;
z.right = T.nil;
rb_insert_fixup(T, z);
}
rb_insert_fixup(T, z)
{
while(z.p.color=RED)
{
if(z.p==z.p.p.left)
{
y = z.p.p.right;
if(y.color==RED)
{
z.p.color = BLACK; //case1
y.color = BLACK; //case1
z.p.p.color = RED; //case1
z = z.p.p;
}
else
{
if(z==z.p.right)
{
z = z.p; //case2
left_rotated(T,z); //case2
}
z.p.color = BKACK; //case3
z.p.p.color = RED; //case3
right_rotated(T,z.p.p); //case3
}
}
else // z.p==z.p.p.right
{
y = z.p.p.left;
if(y.color==RED)
{
z.p.color = BLACK; //case1
y.color = BLACK; //case1
z.p.p.colr = RED; //case1
z = z.p.p; //case1
}
else
{
if(z==z.p.left)
{
z = z.p; //case2
right_rotated(T, z); //case2
}
z.p.color = BLACK; //case3
z.p.p.color = RED; //case3
left_rotated(T,z.p.p); //case3
}
}
}
T.root.color = BLACK;
}
参考《算法导论》第十三章 图13-4帮助理解代码。
对于插入的节点z为什么着色为红色RED,而不是黑色BLACK,个人理解如下:
1. 着色为红色RED不会破坏红黑树性质5。
2. 若着色为黑色就需要调整z节点以上的许多节点从新平衡树,调整量大。
3. 着色为红色只需沿着z向上调整,调整量下;在调整颜色时在case1中是两层两层向上升的,只要有一次进入case2或case3就表示调整结束。
四、删除
rb_transplant(T, u, v)
{
if(u.p == T.nil)
{
T.root = v;
}
else if(u.p.left == u)
{
u.p.left = v;
}
else
{
u.p.right = v;
}
v.p = t.p;
}
//保持红黑树性质
rb_delete_fixup(T, x)
{
while(x!=T.root && x.color == BLACK)
{
if(x == x.p.left)
{
w = x.p.right;
if(w.color == RED)
{
w.color = BLACK; //case1
x.p.color = RED; //case1
left_rotated(T, x.p); //case1
w = x.p.right; //case1
}
else if(w.color == BLACK)
{
if(w.left.color == BLACK
&& w.right.color == BLACK)
{
w.color = RED; //case2
x = x.p; //case2
}
else
{
if(w.right.color == BLACK)
{
w.left.color = BLACK; //case3
w.color = RED; //case3
right_rotated(T, w); //case3
w = x.p.right; //case3
}
w.color = x.p.color; //case4
x.p.color = BLACK; //case4
w.right.color = BLACK; //case4
left_rotated(T, x.p); //case4
x = T.root;
}
}
}
else if(x == x.p.right) //和以上是对称的
{
w = x.p.left;
if(w.color == RED)
{
w.color = BLACK;
x.p.color = RED;
right_rotated(T, x.p);
w = x.p.left;
}
else if(w.color == BLACK)
{
if(w.left.color == BLACK
&& w.right.color == BLACK)
{
w.color = RED;
x = x.p;
}
else
{
if(w.left.color == BLACK)
{
w.right.color = BLACK;
w.color = RED;
left_rotated(T, w);
w = x.p.left;
}
w.color = x.p.color;
x.p.color = BLACK;
w.left.color = BLACK;
right_rotated(T, x.p);
x = T.root;
}
}
}
}
x.color = BLACK;
}
//删除操作
rb_delete(T,z)
{
y = z; //当z只有一个孩子时y,z指向同一个节点
y_original_color = y.color;
if(z.left == T.nil)
{
x = z.right;
rb_transplant(T, z, z.right);
}
else if(z.right == T.nil)
{
x = z.left;
rb_transplant(T, z, z.left);
}
else
{
//寻找z右子树上的最小值也即z的后继节点
y = tree_minmum(z.right);
y_original_color = y.color;
x = y.right;
if(y.p == z)
{
//这一步是必要的也即当x时叶节点时也把其父节点指针赋值为y
//这是为了rb_delete_fixup(T, x)中保持红黑树性质的需
//要
x.p = y;
}
else
{
rb_transplant(T, y, y.right);
y.right = z.right;
y.right.p = y;
}
y.left = z.left;
y.p = z.p;
y.color = z.color;
}
if(y_original_color == BLACK)
{
rb_delete_fixup(T, x);
}
}
对于rb_delete(T,z)可能比较好理解但是对于rb_delete_fixup(T,x)就不是很好理解了。虽然说是删除z节点但是其实是把y节点的值赋值给了z节点真正删除的是y节点,而x节点占据了y节点原来的位置。若删除的y节点是黑色的那就破坏了红黑树的性质5。算法中是将y的黑色“下推”给节点x。使节点x成为双重颜色(黑黑或红黑),然后再rb_delete_fixup中将x节点上的双重性去掉,去掉的方法是从x节点向上通过一系列的旋转和重新赋值在某一层上找到一个红色节点将其赋值为黑色来弥补由于删除y节点而导致的某些路径的黑高比其他的低一个单位(这里利用了红黑树的黑色节点的孩子可以是红色也可以是黑色,但是红色节点的孩子必须是黑色)。