Red-Black tree

一、红黑树属性:

Red-Black tree 是二叉搜索树,每个节点而外多一位存储颜色,红或者黑.通过对每条从根节点到叶子节点路径上各节点的颜色的限制,红黑树保证没有一条路径是其他路径两倍长,所以红黑树是接近(approximately)平衡.

Red-Black tree包含五个属性: color, key, left, right, parent,如果一个节点的子节点或者父节点不存在,则

对应的指针值为NIL,我们把这些NIL视为指向二叉搜索树的外结点(叶节点)的指针,正常的带值节点称为内节点.

一棵二叉搜索树如果满足下面的红黑性质,则是一棵红黑树(下图a):

1、每个节点或是红色的,或是黑色的.

2、root节点是黑色的.

3、每个叶子节点(NIL)是黑色的.

4、如果一个节点是红色的,他的两个孩子节点均是黑色的.

5、对于每个节点,从该节点到其子孙叶节点所有路径上包含相同数目的黑节点.

为了方便处理边界条件,我们采用一个哨兵代表上面的NIL指针.对于一棵Red-black 树T,哨兵T.nil是一个和普通节点一样的对象。其颜色属性是黑色,其他属性可以是随机值,没有具体意义.如下图b中,所有指向NIL的指针都替换成哨兵T.nil.

我们主要关注红黑树的内节点,因为内节点才含有关键字值;接下来篇幅,我们在画红黑树时会忽略掉所有哨兵T.nil.(如下图c)

《Red-Black tree》

我们把从一个节点x(不包括该节点)到叶子节点的一条路径上所有黑色节点的个数称为该节点的黑高度,简写成bh(x). 红黑树的黑高度定义为根节点的黑高度.

引理一: 一棵含有n个内节点的红黑树高度最大为 2lg(n+1).

(证明略)

二、旋转(Rotation)

在引入红黑树插入和删除操作之前,先的介绍下左旋右旋,因为插入,删除会修改红黑树,破坏红黑树原有的红黑属性,为了恢复属性,我们必须要修改节点颜色值,以及节点指针结构。

我们通过旋转操作来修改指针结构,旋转分为左旋和右旋,当对节点x做左旋的时候,x的右子节点不能为空;右旋和左旋操作对称,对y节点右旋,y节点的左子节点不能为空。

下图是左旋和右旋的具体操作:

《Red-Black tree》

下面是左旋的伪代码:

《Red-Black tree》

右旋的话书上没有,我自己写的伪代码:

Right-Rotate(T,y)
1 x = y.left
2 y.left = x.right
3 if x.right ≠ T.nil
4      x.right.p = y;
5 x.p = y.p;
6 if y.p == T.nil
7     T.root = x
8 elseif y == y.p.left
9      y.p.left = x
10 else y.p.right = x
11 x.right = y
12 y.p = x

其实左旋和右旋,主要是目标节点和旋转对应节点(左旋是目标节点右子节点,右旋是目标节点左子节点)对调,对调后两个节点各自的子节点只要位置没有被占,则继续挂在原来位置,如果被占,则找对应位置挂,以满足二叉搜索树有序的特性。

如从上图可以看出来左旋和右旋,除了x,y节点兑换,α,γ子节点所挂位置没有变动,只有β节点每次旋转后,原来位置被占了,需要重新找挂点。

三、插入:

红黑树的插入 需要在二叉搜索树插入的基础上稍作修改,因为插入会破坏红黑树特性。新增节点着红色,插入后需要调用辅助函数RB-INSERT-FIXUP,用来修改节点颜色等以保持红黑树特性.

RB-INSERT 伪代码:

《Red-Black tree》

分析出当插入新节点会导致哪些红黑属性被破坏,可以得出只有属性2和属性4:

1、每个节点或是红色的,或是黑色的.(明显不会影响)

2、root节点是黑色的.(当插入的红黑树为空时,而新插入节点着红色,可知这时仅违背这一条)

3、每个叶子节点是黑色的.(明显不会影响)

4、一个节点如果是红色的,他的两个孩子节点均是黑色的.(有可能插入位置父节点为红色,可知这时仅违背这一条)

5、每个节点,从节点到子孙节点包含相同数目的黑节点.(新插入节点两个子节点均为T.nil,着黑色,不影响)

RB-INSERT-FIXUP 主要是为了解决插入节点后引起可能的红黑属性第2,4条冲突,其伪代码:

《Red-Black tree》

因新插入节点z着红色,其父节点可能也着红色,违背红黑特性第4条,line 1的while循环就是为解决该冲突,接下来需要辨别节点z的uncle节点(z.p.p.left/right)着啥色,再具体修改以满足红黑树的所有特性.

line 16 说明当前节点z的父节点为黑色,可能z的父节点为T.nil着黑色,这时候z为根节点,违背红黑特性第2条.

RB-INSERT-FIXUP 需要考虑六种情况: 其中三种和另外三种对称,基于line 2的z.p是z.p.p左子树还是右子树。

所以具体只需要考虑三种情况: 其中case 1同case 2、case 3的区别在于line 4 的z.uncle(z.p.p.left/right)的着色,如果z.uncle着红色,是case 1;否则是case 2/case 3. case 2同case 3 的区别在于 line 9,z是z.p的左节点还有右节点,如果是右节点则是case 2,需要先左旋;否则为case 3.

下面具体分析下 RB-INSERT-FIXUP 处理的3种case(假定新插入节点z):

case 1: z.uncle(z.p.p.left/right)是着红色(对应lines 5-8),如下图:

首先给z.p和y(z.uncle)着黑色以解决冲突z和z.p都着红色的问题;同时z.p.p着红色,以维持红黑属性第五条,然后以z.p.p作为新z节点重复line 1的while 循环。

下次循环初始,z’=z.p.p 已经着红色:

1、如果如果z’.p 为黑色,这时有可能z’是根节点,或者z’p是根节点, 或者z.p不是根节点, 处理均一样,不满足while循环,直接调用line 16 修改根节点着色即可.

2、如果z’.p 着红色,可知不是根节点,则和case 1第一遍while循环处理类似.

《Red-Black tree》

case 2: z.uncle 着黑色,同时z 是z.p的右子树(对应lines 10-11) 如下图:

case 3: z.uncle 着黑色,同时z 是z.p的左子树(对应lines 12-14):

因case 2 经过左旋后就同case 3一样了,所以放一起分析:

case 2(line 10-11) z先赋值成z.p,然后进行左旋,从前面左旋操作可知,仅仅修改z,z.p指针结构,z.p.p保持不变;

case 3(line 12-14) 修改节点着色,并进行一次右旋以保障红黑属性第五条。这时已经一条直线上已经没有两个连续红节点了,处理完毕,同时因为z.p.color 在 line 12着黑色,while循环也不会再进入.

这里因为case 2/3不会进行下一轮while循环,所以只需要确保case 2/3的处理确实能保障红黑树属性就OK了:

这时有z,z.p 均着红色,同时这时z 不可能是根节点.

a、case 2 只进行左旋,并不修改颜色值,不会引入冲突违背红黑树形.

b、case 3 line 12 对z.p着黑色,如果z.p是根节点,没有问题;

c、同case 1一样,只有可能红黑特性2,4可能会被违背;

唯一可能违背红黑特性2的地方是 line 13 对z.p.p着红色,然而line 14对z.p.p进行右旋,z.p.p只会变成z.p的右子节点(因有z.p = z.p.p.left),不可能是根节点.

case 3 修正了红黑特性4,同时不会引人新的冲突;

《Red-Black tree》

插入示例图: 下图是插入节点z后调用 RB-INSERT-FIXUP 流程图,包含上面三种case:

《Red-Black tree》

插入时间复杂度分析:

RB-INSERT 时间复杂度如何,因为n个节点的红黑树高度为O(lgn).RB-INSERT前部分插入时间为O(lgn),辅助函数RB-INSERT-FIXUP的while循环也是从叶子节点一直上升到根节点,其时间也为O(lgn),所以RB-INSERT 整体时间为O(lgn).同时RB-INSERT 所做的旋转操作不会超过两次,因为只要执行了case2 和case3之后while循环就会结束.

三、删除

同红黑树其他基本操作一样,删除的时间也为O(lgn),删除操作比插入稍许复杂.

红黑树的删除操作基于二叉搜索树的删除操作,鉴于此,首先需要修改前面的 TRANSPLANT 函数,

下面是RB-TRANSPLANT 的伪代码(对比二叉搜索树的 TRANSPLANT):

《Red-Black tree》《Red-Black tree》

下面是RB-DELETE 的伪代码:

《Red-Black tree》

 RB-DELETE 是在TREE-DELETE 上面略作修改而来,删除节点z后,调用辅助函数 RB-DELETE-FIXUP,修改某些节点颜色并作旋转操作,以便恢复红黑属性。

   RB-DELETE 同 TREE-DELETE 有下面几点区别(可以看做对RB-DELETE 伪代码的分析):

1、我们引入节点y作为要直接删除节点,或者要移动(移入z原来位置)的节点。line 1 y指向节点z,如果z 子节点少于两个,则y作为直接删除的节点; line 9 z有两个子节点,则y 指向z的后继节点,同TREE-DELETE 一样,y将移入到z原来的位置。

2、因为节点y 的颜色可能被修改,因此在节点y颜色被修改前,需要用变量 y-original-color 保存y修改前的颜色。保存y-original-color 是为了在最后验证,如果其是黑色,移除或者移动节点y将会破坏红黑属性。

3、引入节点x ,作为将移入y原来位置的节点,如果y没有子节点,则x 为哨兵t.nil。

4、因为x节点将移动到y原有位置,所以理论上x.p 一直指向的是y.p, 因为需要把z的后继y 从原来位置裁剪掉到z位置。唯一特殊的情况是 当y = z.right(line 12), 这时候x.p 这时候还是 为y,相当于整体上移一层。

5、最后,如果y之前着黑色,前面的转换可能会破坏红黑属性,line 22行调用 RB-DELETE-FIXUP 以便恢复红黑属性;反之,如果y之前着红色,红黑属性依然保持,原因如下:

a) 红黑树的黑高度没有被修改.

b) 没有红色节点连在一起。因为y移入z原来位置,同时着z之前颜色,所以不可能有连着的红色节点;此外,如果y不是z的右子节点,这时y之前的右子节点移入y原来位置,如果y为着红色,则x必定着黑色,所以用x移入y原来位置不会导致连续的红色节点。

c) 因为y着红色,所以不可能是根节点,所以根节点还是黑色.

  当然如果删除的y之前的颜色为黑色,则会引起三个问题:

1、如果y是根节点,而其一个红色子节点作为新根节点,违背了红黑属性2;

2、如果x 和 x.p 均为红色,则违背红黑属性4;

3、移动黑色节点y,会导致之前包含节点y的路径黑节点个数少一个,红黑属性5被y之前的祖先破坏。补救该问题的一个办法就是把移入y原来位置的x节点视作还有额外的一层黑色,也就是说将任意包含节点x的路径上黑节点个数加一,在这种解释下,红黑属性5成立。当我们删除或者移动节点y时,我们把节点y的黑色下移到节点x上。现在的问题变成,节点x既不是黑色也不是红色,违背红黑属性1。x节点是”双层黑色”,或”红黑色”,同时分别增加对应路径上黑节点个数2,1个。但是x节点对应的着色是红色(如果节点是”红黑色”), 黑色(如果节点是”双层黑色”),换言之,额外的黑色反映在x所指向的节点而不是x的着色.下面具体分析下RB-DELETE-FIXUP 如何恢复红黑属性.

下面是 RB-DELETE-FIXUP 的伪代码:

《Red-Black tree》

RB-DELETE-FIXUP 修正红黑属性第1,2和4条。整个while循环主要的目标是将额外的黑色上移直到满足:

1、x指向红黑节点(属性为红色),这时,在line 23行(单独)将x着为黑色.

2、x指向root节点,这时我们简单的移除额外的黑色就行了.

3、进行某型颜色修改以及旋转操作,以退出循环.

    可知,一进入while循环,就说明节点x 非根节点,双重黑色,line 2 判断x 是x.p的左节点还是右节点(上面给出了x右节点,x是左节点的情况同时右节点情况刚好对称),总共八种情况,其中四种和另四种情况对称.

    在具体分析四种情况前,我们核实下每种情况的转换如何保持红黑属性5的。关键的思想是,从(且包括)子树的根节点到每棵子树α,β,γ…ζ 之间黑色节点个数(包括x的额外黑色)并不被转换所改变。即转换前后,红黑属性5均成立。例如,下图a,对应cese 1,可知转换前后,从子树根节点到α,β的黑节点个数均是3,从根节点到γ,ε,ζ的黑节点个数均为2.

下面具体分析下 RB-DELETE-FIXUP 处理的4种case:

case 1 : x的兄弟节点 w 着红色(如下图a):

对应着RB-DELETE-FIXUP lines 5-8, 因为节点x的兄弟节点 w 着红色,可以交换w 和x.p的颜色,然后对x.p左旋,不会破坏红黑树形.新的w节点指向左旋之前w节点的孩子节点,着黑色,变成case2,3,或者4了.

《Red-Black tree》

当w节点着黑色时,属于case 2,3,4,而这几个具体区分在于w孩子节点的着色:

case 2 : 节点x的兄弟节点w着黑色,以及w两个孩子节点均着黑色(如下图b):

    对应着RB-DELETE-FIXUP lines 10-11, 因为节点 w 着黑色,同时其两个孩子节点均着黑色,可以两边同时去掉节点x 和 节点w的黑色,为保持红黑属性5,接着我们给x.p额外新增一重黑色。以x.p节点作为新的x节点重复while循环.

    给x.p 新增一重黑色前,x.p可能着红色,也可能黑色;特殊情况,如果是从case 1 转到case 2时,可以知道x.p着红色,如此x.p变成红黑色(属性是红色),下一次循环因以x.p 作为新的x节点,所以退出循环.

《Red-Black tree》

case 3 : 节点x的兄弟节点w着黑色,以及w 左孩子着红色,右孩子着黑色(如下图c):

     对应着RB-DELETE-FIXUP lines 13-16, 可以交换w 同 w.left的颜色,然后对w节点做右旋操作,可以保存红黑属性。这时x的兄弟节点 w 着黑色,其右孩子节点着红色.这样就将case 3 转换成case 4了.

《Red-Black tree》

case 4 : 节点x的兄弟节点w着黑色,以及w 右孩子着红色(如下图d):

    对应着RB-DELETE-FIXUP lines 13-16,通过某些颜色修改以及对x.p进行左旋操作,可以去除掉x 节点额外的黑色,变成单层黑色,同时保持红黑属性不变。line 21 把x 赋值为T.root,致使while循环直接退出。

《Red-Black tree》

删除时间复杂度分析:

    因为n各节点的红黑树高度为O(lgn),除去RB-DELETE-FIXUP 的RB-DELETE 的时间花费为O(lgn)。RB-DELETE-FIXUP 中的case 1,3,4 在执行一定的颜色修改和不超过3次的旋转 后就会推出循环; case 2是while循环中唯一可以重复进入的情况,其中x指针沿树上升O(lgn)次,且不执行任何旋转。所有RB-DELETE-FIXUP 花费O(lgn)时间,执行最多三次旋转,所以RB-DELETE 总共时间为O(lgn).

本文只是初步解释红黑树。具体为什么要左旋,右旋,我也不甚明白,只知道左旋,右选后刚好红黑属性保持OK。至于时间复杂度以及算法效能等,还需要实践去验证。以及对红黑树的优化和拓展也有很多大师在研究。

reference:

Introduction to algorithms(算法导论第三版)

算法导论第二版中文版

http://zh.wikipedia.org/wiki/%E7%BA%A2%E9%BB%91%E6%A0%91

http://saturnman.blog.163.com/blog/static/5576112010969420383/

    原文作者:红黑树
    原文地址: https://my.oschina.net/amince/blog/296855
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞