龙盟编程博客 | 无障碍搜索 | 云盘搜索神器
快速搜索
主页 > 软件开发 > C/C++开发 >

红黑树的使用详解

时间:2014-05-21 02:32来源:网络整理 作者:网络 点击:
分享到:
本篇文章是对红黑树的使用详解。需要的朋友参考下

(学习的参考资料主要是《算法导论》)

  首先是红黑树的性质。一棵二叉查找树满足以下的红黑性质,则为一棵红黑树。

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

  2)根结点是黑的。

  3)每个叶结点(NIL)是黑的。

  4)红结点的两个孩子都是黑的。

  5)对任意结点,从它到其子孙结点所有路径上包含相同数目的黑结点。

  初学时并不在意,但是仔细研究相关算法就会知道,算法都是围绕保持这些性质来进行的。性质5)保证了红黑树使用时的高效。定理证明了n个内结点的红黑树高度至多为2lg(n+1)。

 

  不同于一般二叉查找树,红黑树一般采用一个哨兵结点代表NIL,这对算法的使用提供了很多方便,具体编写时可以体会的到。哨兵设置为黑色,它是根的父结点,也是所有的叶子结点。而它的其他域可以设置为任意值。我用关键字把它和普通的结点进行区分。

 

  旋转是红黑树的特有操作。以前搞不清左旋和右旋究竟是如何进行的,现在比较明白,可以这样概括:以x结点左旋即为,使x从一棵子树的根变成这个子树的左孩子;对称的,同理。旋转是红黑树插入和删除时为了维持红黑性质而可能进行的操作。

 

  插入的原理:

  除了空指针的处理,插入的过程和二叉查找树相同,但是插入后需要进行独有的调整算法以保证红黑性质。下面的描述是我的个人概括,看上去比较混乱,和算法以及实例相对照着可能容易理解一些。

  新插入的点z直接染成红色,再根据其父结点是否为红(与性质4冲突)和插入的结点是否为根(与性质2冲突)进行调整。后者直接把根染黑即可。

  对于前者,找到z的叔叔y(找叔叔y虽然需要分情况处理,但比较简单,不详写),根据y是红还是黑进一步分清况。z的父亲为左孩子时,前者只需要把z的父亲和叔叔同时变黑、z的父结点的父结点变红、令z指向z的父结点的父结点迭代处理即可;后者进一步分z是左孩子还是右孩子处理。z是左孩子时直接以z的父结点进行旋转让z的父亲左旋并成为新z即成为后一种情况。在后一种情况中,将z的父亲染黑,祖父染红,以z的祖父右旋就能获得。

 

  删除的原理:

  算法导论上的删除算法把两种情况同时进行处理,确实很有技巧。红黑树的删除除了最后需要根据对于删除结点的颜色来判断是否需要进行调整外,和普通的二叉查找树没有区别,这里稍微做一下分析。

代码如下:

RB-DELETE(T, z)                     //   情况1           ||  情况2
 if left[z] = nil[T] or right[z] = nil[T]    //  z最多一个孩子时       ||  z有两个孩子时
   then y ← z                    //   令y=z           ||   令y是z后继(此时y必然不是z的右孩子)
   else y ← TREE-SUCCESSOR(z)           //===============================================================================
 if left[y] ≠ nil[T]                //  令x为y的孩子或哨兵      ||   令x是y的右孩子(x必然不为左孩子,否则y不可能是z的后继)
    then x ← left[y]                //                          ||    将来x会代替y的位置
    else x ← right[y]               //================================================================================
 p[x] ← p[y]                    //
 if p[y] = nil[T]                 //
     then root[T] ← x               //                         x与x的新父亲之间建立关系
     else if y = left[p[y]]           //
           then left[p[y]] ← x          //
           else right[p[y]] ← x         //=================================================================================
 if y !≠ z                     //                  ||
     then key[z] ← key[y]                     //       删完后整体上移       ||    替代,用于替代的原结点删除
         copy y's satellite data into z       //                             ||
 if color[y] = BLACK                          //                             ||
     then RB-DELETE-FIXUP(T, x)               //                             ||
  return y

   删除后,如果删除的结点是黑色,可能会造成性质2、4、5的违反。调整算法思想是使得代替y的x多染一层黑色而成为红黑或二重黑色结点。这个处理只是用指针x标示,并不用改变结点color域的内容。调整算法按8种情况,其中两两对称,只描述4种。

  用w表示x的兄弟。

  情况1为w红。此时调整w为黑,p[x]为红,以p[x]左旋,w指向x的新兄弟,此时则成为情况2或3或4。

  情况2为w黑,且w的两个孩子均黑。此时把w染红,令p[x]成为新的x。这相当于把x剥离了一层黑色,使这层黑色上移。

  情况3为w黑,w的左孩子为红,右孩子为黑。这时交换w和左孩子颜色,并对w右旋,此时成为情况4。

  情况4为w黑,w有孩子为红。这时使w成为p[x]的颜色,p[x]置为黑色,w的右孩子置为黑,对p[x]左旋。令x为根。这时相当于把原先x上的一重黑色传给了其父亲并于它一起下移,而w代替了其父亲原先的颜色和位置。这是不存在红黑结点或二重黑结点。

  每次处理都判断x是否为根且x是否为黑。x不为根且为黑时才代表有红黑结点或二重黑结点,需要进行新一轮循环。循环结束后把根染黑就结束了。

 

  最后附上一个我自己用C写的红黑树操作。插入操作验证无误,删除操作验证次数有限,可能有bug存在。

代码如下:

#include    <stdlib.h>
#include    <stdio.h>

#define T_nil -1
//T_nil is a key of nil[T] in the book.
#define RED        1
#define BLACK    0//T_nil is BLACK

//T_nil's p is itself. need to set.


struct rb_tree {
    int color;
    int key; //normally a positive number.
    struct rb_tree *left;
    struct rb_tree *right;
    struct rb_tree *p;
};

int rb_walk(struct rb_tree *node) {
    if (node->key != T_nil) {
        rb_walk(node->left);
        printf("%d, color is %s\n",node->key,(node->color?"RED":"BLACK"));
        rb_walk(node->right);
    }
    return 1;
}

struct rb_tree* rb_search(struct rb_tree *node, int k) {
    if ((node->key == T_nil) || (node->key == k))
        return node;

    if ( k < node->key )
        return rb_search(node->left,k);
    else
        return rb_search(node->right,k);
}

struct rb_tree* tree_minimum(struct rb_tree *node) {
    if (node->key == T_nil)
        return node;
    while (node->left->key != T_nil)
        node = node->left;
    return node;
}

struct rb_tree* tree_maximum(struct rb_tree *node) {
    if (node->key == T_nil)
        return node;
    while (node->right->key != T_nil)
        node = node->right;
    return node;
}

struct rb_tree* tree_successor(struct rb_tree *node) {
    struct rb_tree *y;
    if (node->right->key != T_nil)
        return tree_minimum(node->right);
    y = node->p;
    while ((y->key != T_nil) && (node == y->right)) {
        node = y;
        y = y->p;
    }
    return y;
}
//3 function of below is copy from tree.


struct rb_tree * left_rotate(struct rb_tree *rb, struct rb_tree *x) {
    struct rb_tree *y;
    //if (x->right->key == T_nil) {
    //    printf("have no right child,rotation cancel.\n");
    //    return rb;
    /

精彩图集

赞助商链接