红黑树(red-black tree)

红黑树

  普通的二叉搜索树高度如果较高时,一些集合操作可能不比链表上执行的快,而红黑树属于“平衡”搜索树的一种,可以保证在最坏情况下基本动态集合操作的时间复杂度为O(lgn).

(一)红黑树的性质

  红黑树是一棵二叉搜索树,在每个结点上增加了一个存储位来表示结点的颜色,可以为RED或者BLACK。通过一些约束可以保证没有一条路径会比其他路径长出2倍,因而是近似于平衡的。

 

红黑树的5个性质:

1. 每个结点或是红色的,或是黑色的。

2. 根结点是黑色的。

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

4. 如果一个结点是红色的,则它的两个子结点都是黑色的。

5. 对每个结点,从该结点到其所有后代叶结点的简单路径上,均包含相同数目的黑色结点。

 

  为了节省空间,使用一个哨兵T.nil来表示所有的NIL:所有的叶结点和根结点的父结点。

  黑高(black-height):定义从某个结点x出发(不含该结点)到达一个叶结点的任意一条简单路径上的黑色结点个数称为该结点的黑高,记为bh(x).

             如图为一棵红黑树例子,根结点的父结点与叶结点用nil表示

 

  

(二)旋转

  为了实现红黑树的插入和删除操作,这里需要一种能保持二叉搜索树性质的搜索树局部操作:旋转。

  如图给出两种旋转:左旋和右旋。当在某个结点x上做左旋时,假设它的右孩子为y而不是T.nil,即x可以是右孩子不是T.nil结点的树内任意结点。左旋以x到y的链为“支轴”进行,它使y成为该子树新的根结点,x成为y的左孩子,y的左孩子成为x的右孩子。

下面是左旋伪代码,假设x.right != T.nil

右旋与左旋代码对称,两个操作都在O(1)时间内完成。

 

(三)插入

  一棵含n个结点的红黑树中插入一个新结点可以在O(lgn)的时间内完成。为了做到这一点,调用RB-INSERT(T , z)在红黑树中插入结点z,其中利用辅助程序RB-INSERT-FIXED来对结点进行重新着色并旋转,以维持红黑树性质。

  这里RB-INSERT与普通的二叉搜索树插入比较相似,将新插入的结点标记为红色然后找到一个位置插入,然后在最后调用RB-INSERT-FIXUP来维护红黑树性质。

  代码只讨论了z的父亲为爷爷的左孩子的情况,父亲为右孩子的情况刚好相对。这种情况又有3种可能:1. z的叔结点(爷爷的右孩子)为红色。2. z的叔结点为黑色且z是一个右孩子。3. z的叔结点是黑色且z是左孩子。

1.

  直接变换颜色可以让指针z上移两层

2和3.

  对于情况2,直接对z执行z = z.p并对新的z左旋即可变成情况3.对于情况3,改变z的父结点和爷爷结点的颜色并对爷爷结点进行右旋即可。

  可以证明整个过程没有改变二叉搜索树的性质并最终维护了红黑树的性质,RB-INSERT的运行时间为O(lgn).

 

(四)删除

  红黑树删除的过程比较复杂,删除一个节点需要O(lg n)的时间。

首先是辅助函数RB-TRANSPLANT , 用v结点替代u结点的位置并将u结点删除。

 

 

注意第6行即使u是根结点也没有问题,因为引入了nil结点。

下面是RB-DELETE函数,删除掉z结点,并在最后调用RB-DELETE-FIXUP函数来保持红黑树性质。

  代码中z为将要删除的结点,然后记录y结点的踪迹,其中y结点可能破坏红黑性质,因为过程中移动了y的位置。删除掉结点z后,y将会顶替z的位置。如果z只有一个子结点,则可以直接删除并由唯一的子结点顶上。如果z有2个子结点,则y是z的后继,可以在y的右子树中一直向左找到最后一个结点即可,并知道y肯定没有左子结点。还要记录y的子结点x,因为x将移到原来y的位置,并且可能也会导致红黑性质的破坏。

  代码第21行只在y-original-color为black时候才调用FIXUP函数来回复红黑性质,因为可以看出删除或移动一个红色结点不会导致红黑性质的破坏。而如果y是黑色的则会产生3个问题,都可以通过调用FIXUP函数进行补救。第一, 如果y原来是根结点,而y的一个红色孩子成为了新的根结点,就会违反红黑树的性质2.  第二, 如果x和x.p是红色的,就违反了性质4.  第三, 在树中移动y将导致先前包含y的任何简单路径上的黑色结点个数减少1,因此y的任何祖先都不满足性质5.

  这时候针对第三个问题的策略是将原先y的黑色向下推给x,即假想x的黑色性质加1.我们只需要假想而不需要真正修改x.color.如果x本身是黑色,则变成黑黑,如果原先是红色,则变成红黑。这时候需要将x多出来的那重黑色经过适当处理消掉,从而恢复红黑树的性质。

  代码执行一个while循环,目标是将多出的黑色性质往上推,直到遇到x本身为红色的,即假想的红黑结点,这样就可以直接将x.color置为黑色解决;

或者x最终指向根结点,也可以简单的将额外的黑色性质移除;或者可以执行适当的旋转和重新着色而退出循环完成目标。

  代码只给出x为左孩子的情况,另一种情况与这种刚好相对,可以通过left与right互换得到。而x为左孩子又可以分为4种情况,并且其中的一些情况可以通过一些操作转换。4种情况在代码中已经标出。

1.x的兄弟结点w是红色的。

2.x的兄弟结点w是黑色的,而且w的2个孩子都是黑色的。

3.x的兄弟结点w是黑色的,w的左孩子是红色的,又孩子是黑色的。

4.x的兄弟结点w是黑色的,且w的右孩子是红色的。

  图中分别表示4种情况进行的操作,其中加黑的表示黑色结点,深阴影的为红色结点,浅阴影的结点颜色可以为黑色或者红色。

  结合代码与图即可理解每一种操作,注意情况3会转化为情况4,而情况4在将x设置为根后会跳出循环。

 

 

 

 

posted @ 2014-03-22 20:41  Jolin123  阅读(1380)  评论(0编辑  收藏  举报