第12章 红黑树
12.1 红黑树的性质
-
红黑树在每个结点上添加一个存储位来表示结点的颜色,可以是RED或BLACK。
-
通过对任何一条从根到叶子的简单路径上各个结点的颜色进行约束,红黑树确保没有一条路径会比其它路径长出2倍,因而近似于平衡的
-
树中每个结点包含5个属性: c o l o r , k e y , l e f t , r i g h t , p color, key, left, right, p color,key,left,right,p
-
红黑树性质:
-
是二叉搜索树
-
结点或是红色,或是黑色
-
根结点是黑色结点
-
每个叶子结点都带有两个空的黑色结点(NIL结点)
-
如果一个结点是红色,则它的两个子结点都是黑色(或者说从每个叶子到根的所有路径上不能有两个连续的红色节点)
-
对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点
-
-
为了便于处理红黑树代码边界条件,使用一个哨兵来代表NIL。哨兵 T . n i l T.nil T.nil与树中普通结点有相同属性。它的 c o l o r color color属性为BLACK,其它属性为任意值。所以我们可以使用一个哨兵 T . n i l T.nil T.nil来代表所有的NIL。
-
黑高:从某一结点出发(不包含该节点)到达一个叶结点的任意一条简单路径上黑色结点个数称为该结点的黑高,记为 b h ( x ) bh(x) bh(x)。
-
一颗有 n n n个内部结点的红黑树的高度至多为 2 lg ( n + 1 ) 2\lg(n+1) 2lg(n+1)。
-
红黑树的python实现:
class ColorEnum(Enum): RED = "red" BLACK = "black" class RBTreeNode(): def __init__(self): self.value = None self.left = None self.right = None self.p = None self.color = None
12.2 旋转
-
指针结构的修改是通过旋转来完成的,这种操作能保持二叉搜索树性质。
-
左旋:假设在某个结点x上做左旋,它的右孩子为y。左旋则以x到y的链为“支链”进行,它使得y称为该子树新的根结点,x为y的左孩子,y的左孩子成为x的右孩子。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lL1hCiCI-1614663448616)(img/20181031000037863.png)]
-
伪代码:LEFT-ROTATE(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 elseif x == x.p.left x.p.left = y else x.p.right = y y.left = x x.p = y
-
python代码:
class RB_Tree(object): def __init__(self): self.root = None def left_rotate(self, x: RBTreeNode): y = x.right x.right = y.left y.left.p = x y.p = x.p if not x.p: self.root = y elif x == x.p.left: x.p.left = y else: x.p.right = y y.left = x x.p = y def right_rotate(self, y: RBTreeNode): x = y.left y.left = x.right x.right.p = y y.p = x.p if not y.p: self.root = x elif y == y.p.right: y.p.left = x else: y.p.right = x x.right = y y.parent = x
12.3 插入
-
插入过程与二叉搜索树差不太多,搜索树中为NULL的地方,现在被替换成nil哨兵节点,其次插入的节点我们总是将其着色为红色。在完成插入之后,为保证红黑性质能继续保持,我们调用一个辅助程序RB-INSERT-FIXUP来对结点重新着色并旋转。
-
伪代码:
-
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 elseif z.key < y.key y.left = z else y.right = z z.left = T.nil z.right = T.nil z.color = RED BR-INSERT-FIXUP(T, z)
-
BR-INSERT-FIXUP(T, z)
while z.p.color == RED if z.p == z.p.p.right y = z.p.p.left if y.color == RED z.p.color = BLACK y.color = BLACK z.p.p.color = RED z = z.p.p else if z = z.p.right z = z.p LEFT-ROTATE(T, z) z.p.color = BLACK z.p.p.color = RED RIGHT-ROTATE(T, z, p, p) else T.root.color = BLACK
-
-
python代码:
def rb_insert(self, z: RBTreeNode): y = None x = self.root while x: y = x if z.value < x.value: x = x.left else: x = x.right z.p = y if not y: self.root = z elif z.value < y.value: y.left = z else: y.right = z z.left = None z.right = None z.color = ColorEnum.RED self.rb_insert_fixup(z) def rb_insert_fixup(self, z): while z.p.color == ColorEnum.RED: if z.p.p.left == z.p: y = z.p.p.right if y.color == ColorEnum.RED: z.p.color = ColorEnum.BLACK y.color = ColorEnum.BLACK z.p.p.color = ColorEnum.RED z = z.p.p elif y.color == ColorEnum.BLACK and z == z.p.right: z = z.p self.left_rotate(z) else: z.p.color = ColorEnum.BLACK z.p.p.color = ColorEnum.RED self.right_rotate(z.p.p) else: y = z.p.p.left if y.color == ColorEnum.RED: z.p.color = ColorEnum.BLACK y.color = ColorEnum.BLACK z.p.p.color = ColorEnum.RED z = z.p.p elif y.color == ColorEnum.BLACK and z == z.p.left: z = z.p self.right_rotate(z) else: z.p.color = ColorEnum.BLACK z.p.p.color = ColorEnum.RED self.left_rotate(z.p.p)
-
我们之前的插入操作,可能出现问题的就是性质2,4,对于2号性质,如果插入的是一颗空树(只有nil节点),那么根结点就是红的,就不满足2性质。如果插入的节点的父节点是红色,而我们插入的也是一个红色的节点,那么就不满足4性质了。
-
处理:
-
性质2:在调整函数最后加上一句root->color=BLACK即可,将根结点染黑。
-
性质4:我们需要调整的判断条件就是父节点如果为红,就需要继续调整。这里实际上就要分三种情况,首先都已经默认z的父节点z->p的颜色为红色,循环条件为while(z.color==RED):
-
情况一:z的叔节点y是红色,其中z为要调整的节点(下同):
将z的父节点和叔节点都染黑,而将z的祖父节点染红,然后这时我们再将z点指向z的祖父节点位置,然后继续进入循环
-
情况二:z的叔节点y是黑色的且z是一个右孩子
先让z=z.p,然后再以z为支点进行左旋,因为左旋会让z下降一级,所以实际上z还是指向的原来那一层的节点,z.p.p的位置还是没有变。
-
情况三:z的叔节点y是黑色的且z是一个左孩子
z.p染黑,z.p.p染红,然后以z.p.p为支点右旋
-
-
12.4 删除
-
步骤:
- 查找要删除的值所在的节点,如果不存在,删除失败,若存在,执行步骤2
- 如果要删除的节点不是叶子节点,用要删除节点的后继节点替换(只进行数据替换即可,颜色不变,此时也不需要调整结构),然后删除后继节点。
-
伪代码:
-
RB-TRANSPLANT(T, u, v)
if u.p == T.nil T.root = v elseif u == u.p.left u.p.left = v else u.p.right = v v.p = u.p
-
RB-DELETE(T, z)
y = z y-original-color = y.color if z.left == T.nil x = z.right RB-TRANSPLANT(T, z, z.right) elseif z.right == T.nil x = z.right RB-TRANSPLANT(T, z, z.right) else y = TREEE-MINIMUM(z.right) y-original-color = y.color x = y.right if y.p == z x.p = y else RB-TRANSPLANT(T, y, y.right) y.right = z.right y.right.p = y RB-TRANSPLANT(T, z, y) y.left = z.left y.left.p = y y.color = z.color if y.original-color == BLACK RB-DELETE-FIXUP(T, x)
-
RB-DELETE-FIXUP(T, x):恢复红黑树性质
while x != T.root and x.color == BLACK if x == x.p.left w = x.p.right if w.color = RED w.color = BLACK x.p.color = RED LEFT-ROTATE(T, x.p) w = x.p.right if w.left.color == BLACK and w.right.color == BLACK w.color = RED x = x.p else if w.right.color == BLACK w.left.color = BLACK w.color = RED RIGHT-ROTATE(T, w) w = x.p.right w.color = x.p.color x.p.color = BLACK w.right.color = BLACK LEFT_ROTATE(T, x.p) x = root[T] else x.color = BLACK
-
-
红黑树T中删除结点z可能出现的4种情况:
-
z没有左孩子(即左孩子为T.nil),则用其右孩子来替换z,这个右孩子可以是T.nil,也可以不是,所以这种情况包含了两种情形:z没有孩子、z只有一个右孩子。
-
z有左孩子,没有右孩子,则用其左孩子来替换z。
-
z既有左孩子又有右孩子。我们找z的右子树的最小结点y来替换z,因为这样能继续保证左孩子<父结点<右孩子。这种情况下y可以分为两种情况处理:
-
y是z的右孩子,则直接用y替换z。
-
y不是z的右孩子,则先用y的右孩子替换y,然后再用y替换z。
这两种情况用y替换z后,y的颜色要变为z的颜色。
-
-
-
python代码:
class RB_Tree(object): def rb_delete(self, z): if not z.left or not z.right: y = z else: y = self.find_successor(z) if y.left: x = y.left else: x = y.right x.p = y.p if not y.p: self.root = x elif y == y.p.left: y.p.left = x else: y.p.right = x if y != z: z.value = y.value if y.color == ColorEnum.BLACK: self.rb_delete_fixup(x) return y def rb_delete_fixup(self, x):n: while self.root != x and x.color == ColorEnum.BLACK: if x == x.p.left: w = x.p.right if w.color == ColorEnum.RED: w.color = ColorEnum.BLACK x.p.color = ColorEnum.RED self.left_rotate(x.p) w = x.p.right if w.left.color == ColorEnum.BLACK and w.right.color == ColorEnum.BLACK: w.color = ColorEnum.RED x = x.p elif w.right.color == ColorEnum.BLACK: w.left.color = ColorEnum.BLACK w.color = ColorEnum.RED self.right_rotate(w) w = x.p.right w.color = x.p.color x.p.color = ColorEnum.BLACK w.right.color = ColorEnum.BLACK self.left_rotate(x.p) self.root = x else: w = x.p.left if w.color == ColorEnum.RED: w.color = ColorEnum.BLACK x.parent.color = ColorEnum.RED self.right_rotate(x.p) w = x.p.left if w.right.color == ColorEnum.BLACK and w.left.color == ColorEnum.BLACK: w.color = ColorEnum.RED x = x.p elif w.left.color == ColorEnum.BLACK: w.right.color = ColorEnum.BLACK w.color = ColorEnum.RED self.left_rotate(w) w = x.p.left w.color = x.p.color x.p.color = ColorEnum.BLACK w.left.color = ColorEnum.BLACK self.right_rotate(x.p) self.root = x x.color = ColorEnum.BLACK