红黑树
红黑树(red-black tree)是一种自平衡二叉查找树。
一颗红黑树是满足下面性质的染色二叉排序树:
- 结点是红色或黑色;
- 根结点是黑色;
- 每个叶节点(NIL节点,空节点)是黑色的;
- 每个红色结点的左右子结点都是黑色;
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。
可以看出,在一棵红黑树中,最短的道路是所有的结点都是黑色的,最长的道路必是红黑相间。所以一条道路其长度最多是其他道路的两倍,因此红黑树实际上是关于高度近似均衡的树。
**定理 :**一棵含有n个内结点的红黑树的树高至多为2log(n+1)(以2为底)。
可知,红黑树可以在O(logn)时间内实现各种操作。但是,在插入和删除之后,红黑属性有可能被破坏。
恢复红黑属性需要少量的颜色变更,并且不超过三次旋转(对于插入是2次),保证了插入和删除操作维持在O(logn)次,但它带来了相对复杂的操作。
先介绍必要的两个操作:左旋转和右旋转。
- 左旋转:把结点向左下方移动一格,然后让原来的右子结点代替移动结点的原来位置;
- 右旋转:把结点向右下方移动一格,然后让原来的左子结点代替移动结点的原来位置,即把左旋转左、右反一下。
红黑树的插入
同二叉排序树一样,首先找到插入位置,然后把新结点插入到某一个叶子结点的位置上。将插入结点设置为红色。
由前面提到的性质5:从根结点向下到空结点的每一条路径上的黑色结点数要相同,推知,如果新插入的结点是黑色,那么它所在的路径上就多出一个黑色结点,所以新插入的结点一定要设置为红色。
但是,这样可能又有一个矛盾,如果新插入结点的父结点也是红色,根据性质4:每个红色结点的左右子结点都是黑色,此时要执行下面一个迭代的过程来修补这棵红黑树。
在整个迭代过程中,p一定都指向一个红色结点。
为了说明情况。引入几个结点及表示:
p:指向待插入结点,红色;
f:待插入结点p的父结点,待插入结点p的祖父结点的左子结点;
g:待插入结点p的祖父结点;
u:待插入结点p的叔父结点,即待插入结点p的祖父结点的右子结点。
- 如果p的父结点f是黑色,那么成功返回;
- 如果f是红色,有3种情况:
(1)u也是红色。
这时,只要把父结点f和叔父结点u都设置为黑色,并把祖父结点g设置为红色。这样仍然确保了每一条路径上的黑色结点数不变。然后把p指向g,并开始新一轮的迭代。
(2)u是黑色,且p是f的右子结点。
这时,只要把p指向其父结点f,然后做一次左旋转就化成下面要讲的第三种情况。
(3)u是黑色,且p是f的左子结点。
这种情况,把p的父结点f设置为黑色,把其祖父结点g设置为红色,再对父结点f做一次右旋转,整棵树就修补完成了。
红黑树的删除
红黑树的删除操作比插入操作复杂一点,基本删除算法与二叉排序树类似,但需要检查红黑平衡性。
首先,找到待删结点,然后分以下几种情况:
- 待删结点没有子结点:直接删除。
(1)若待删结点为根结点,则该树成为空树;
(2)否则,将待删结点的父结点的相应子结点置空。 - 待删结点有一个子结点:与上面一样,直接删除。
(1)若待删结点为根结点,则它的子结点变为根结点;
(2)否则,将它的父结点的相应子结点置为待删结点的子结点。 - 待删结点有两个子结点。
在这种情况下,我们先找到这个结点的后继结点,即它的右子树中最小的结点。然后将其与待删结点数据元素互换,之后删除这个后继结点。由于这个后继结点不可能有左子结点,因此删除该后继结点的操作必然会落到上面两种情况之一。
我们称最终删除的结点为v,并称它的父结点为p(v)。v的子结点中至少有一个为叶结点。如果v有一个非叶子结点,那么v在这棵树中的位置将被这个子结点取代;否则,它的位置将被一个叶子结点取代。用u来表示二叉搜索树删除操作后在树中取代了v的位置的那个结点。如果u是叶结点,那么可以确定它是黑色的。
- 如果v是红色的,那么删除操作就完成了。因为这种删除不会破坏红黑树的任何性质;
- 如果v是黑色的。删除了v之后,从根结点到v结点的所有子孙叶结点的路径将会比树中其他的从根结点到叶结点的路径拥有更少的黑色结点,这会破坏红黑树性质5。
**双黑色问题的产生:**当我们将黑色结点v删除时,我们将它的黑色下推到它的子结点u,这样u就可能具有双重黑色,从而破坏了红黑树的性质1,产生了双重黑色问题。
让我们在头脑中给u打上一个黑色记号。这个记号表示从根结点到这个带记号结点的所有子孙结点的路径上都缺少一个黑色结点。我们会将这个记号一直朝树的顶部移动直到性质5重新恢复。
解决双黑色问题的四种不同情况:
(1)如果带记号的结点是红色或者是树的根结点(或两者皆是),只要将它染为黑色就可以完成删除操作。
这样就会恢复红黑树的性质4,而且,性质5也会被恢复,因为这个记号表示从根结点到该结点的所有子孙叶结点的路径需要增加一个黑色结点,以便使这些路径与其他的根结点到叶结点路径所包含的黑色结点数量相同。通过将这个红色结点改变为黑色,我们就在这些缺少一个黑色结点的路径上添加了一个黑色结点。
(2)如果这个双黑色结点的兄弟结点以及两个侄子结点都是黑色的,那么我们就将它的兄弟结点染成红色之后将这个记号朝树根的方向移动一步。
图中白色结点表示不关心该结点颜色。
(3)如果带记号的结点的兄弟结点是红色的,那么我们就进行一次旋转操作并改变结点颜色。
(4)最终,我们遇到了双黑色结点有一个黑色兄弟结点并至少一个侄子结点是红色的情况。
给一个结点x的近侄子结点的定义:
如果x是其父结点的左子结点,那么x的兄弟结点的左子结点为x的近侄子结点,否则x的兄弟结点的右子结点为x的近侄子结点;而另一个侄子结点则为x的远侄子结点。
现在可以分为两种情况:
- 双黑色结点的远侄子结点是黑色,它的近侄子结点一定为红色;
- 远侄子结点是红色,它的近侄子结点可以是任何颜色。
子情况1可以通过一次旋转和变色转换为子情况2,而在子情况2下只要通过一次旋转和变色就可以完成删除操作。
在这种情况下生成了一个额外的黑色结点,记号被丢掉,删除操作完成。
可以看出,所有到带记号结点的子孙叶结点的路径上的黑色结点数量增加了1,而其他的路径上的黑色结点数量保持不变。很显然,在此刻红黑树的任何性质都没有遭到破坏。
基于树的查找第一篇:二叉排序树
基于树的查找第二篇:平衡二叉树
参考资料:《数据结构与算法》 王曙燕 主编 王春梅 副主编 人民邮电出版社
【补图】
双黑结点图: