红黑树(red-black tree)

红黑树函数

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

(一)红黑树的性质指针

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

 

红黑树的5个性质:
循环

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

2. 根结点是黑色的。im

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

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

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设置为根后会跳出循环。

相关文章
相关标签/搜索