因二叉搜索树的字典操作的时间复杂度都是O(h),所以,当二叉搜索树的高度较小时,可以获得较快的执行。只有当二叉搜索树变得“平衡”时,高度才会达到最低。
红黑树是许多“平衡”搜索树中的一种,可以保证在最坏情况下基本动态集合操作的时间为O(lg n)。
一:概念与性质
红黑树是一种二叉搜索树,同时它又具有其他的性质。红黑树的结点具有颜色属性,分别为RED或者BLACK。通过对每条分支上的结点颜色做约束,可以保证没有一条路径会比其他路径长2倍,所以红黑树近似于平衡。
红黑树的结点具有5个属性:color, key, left, right, p。红黑树的性质是:
a:每个结点或者是红色,或者是黑色;
b:根节点是黑色的;
c:叶节点(见附注)是黑色的;
d:如果一个结点是红色的,则它的两个子节点都是黑色的;
e:每个结点,从该节点到其所有后代叶节点的简单路径上,均包含相同数目的黑色结点。
下图就是一个红黑树:
附注:为了便于处理边界条件,使用一个哨兵T.nil来处理所有NULL的情况,它的color属性为BLACK,其他属性可以为任意值。原红黑树中,所有指向NULL的情况都指向T.nil,所以,上述性质中的“叶节点是黑色的”,指的是哨兵T.nil是黑色的。有了哨兵之后,上面的红黑树就如下图:
有了哨兵结点之后,我们一般关注内部结点,他们才是原红黑树真正的结点,所以一般不再画出哨兵结点:
二:红黑树的高度
因为从某个结点到叶节点所有路径上的黑结点数都是相同的。所以:从某个结点x出发(不包括该节点),到达一个叶节点的任意一条路径上,黑色结点的个数成为该节点的黑高,记为bh(x)。
一个具有n个内部结点的红黑树,高度最多为2lg(n+1)。
证明:
可以先证明x为根的子树,最少包含 个内部结点。使用数学归纳法证明:
如果x的黑高为0,则x为叶节点(T.nil),所以bh(x) = 0,内部结点数为 = 0。
如果x有两个子节点,则子节点的黑高为bh(x)或者bh(x)-1,具体的取值取决于子节点本身的颜色。所以,每个子节点至少包含 个内部节点,所以,x为根的子树至少包含2*( )+ 1个内部节点,也就是 。因而得证。
如果树的高度为h,根据红黑树的性质,从根到叶节点的任何一个简单路径上,黑结点的个数至少占到一半,所以根的黑高至少为h/2。所以bh(x) >= h/2。所以:n 。所以:h 2lg(n+1)。
三:旋转
红黑树的插入和删除操作会改变红黑树的性质,所以为了保持红黑树的性质,需要改变结点的颜色或者指针结构。
红黑树的旋转就是其中的一种操作,旋转操作能够保持红黑树的二叉搜索树性质。有两种旋转,左旋和右旋,如下图:
左旋代码如下:
LEFT-ROTATE(T, x)
y = x.right // set y
x.right = y.left // turn y’s left subtree into x’sright subtree
if y.left != T.nil
y.left.p = x
y.p = x.p // link x’s parent to y
if x.p == T.nil
T.root = y
else if x == x.p. left
x.p. left = y
else x.p.right = y
y.left= x // put x on y’sleft
x.p= y
右旋操作的代码是对称的,如下,左旋和右旋都可以在O(1)时间内完成。
RIGHT-ROTATE(T, x)
y = x.left // set y
x.left = y.right // turn y’s right subtree into x’s leftsubtree
if y.right ≠ T.nil
y.right.p = x
y.p = x.p // link x’s parent to y
if x.p == T.nil
T.root = y
elseif x == x.p. right
x.p. right = y
elsex.p.left = y
y.right= x // put x on y’sright
x.p= y