代码改变世界

数据结构大总结系列之红黑树

2012-08-14 20:03  javaspring  阅读(379)  评论(0编辑  收藏  举报

一,红黑树的性质:

红黑树本质是二叉查找树的一种,它的性能高于普通的二叉查找树,即使是在最坏的情况下也能保证时间复杂度为O(lgn)。红黑树在每个结点上增加一个存储位表示结点的颜色(或红或黑)。通过对任何一条从根到叶子的路径上各个结点着色方式的限制,红黑树可以保证没有一条路径会比其他路径长出两倍,因而是接近平衡的。

       红黑树的每个结点至少包含五个域:color,key,left,right 和 parent,如果某结点没有子结点或者父结点,则该结点相应的指针(p)域包含值NIL,我们将这些 NIL 当作叶子结点.(图a)。

       在实际处理过程中,往往将最底层的孩子结点和根结点的父亲都指向同一个 NIL 结点,以便于处理红黑树代码中的边界条件(这里的哨兵NIL是一个与树内普通结点有相同域的对象,它的color域为BLACK,而它的其他域——parent, left, right, key,可以设置为任意允许的值),而将其它结点当作内结点。 (图b)

      通常表示红黑树的方法是图(c),即忽略叶子与根部的父结点。(图c),图中,黑结点用黑色表示,红结点用浅阴影表示。

      红黑树在很多地方都有应用。在C++ STL中,很多部分(目前包括set, multiset, map, multimap)应用了红黑树的变体(SGI STL中的红黑树有一些变化,这些修改提供了更好的性能,以及对set操作的支持)。

一棵树满足下边的红黑树性质,则它就是一棵红黑树:

性质1. 结点是红色或黑色。

性质2. 根是黑色。

性质3 每个叶结点是黑色的。

性质4 每个红色结点的两个子结点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色结点)

性质5. 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。

红黑树数据结构:

const int RED = 0;
const int BLACK = 1;
typedef struct RBTNode RBT;
typedef struct RBTNode * Position;
typedef struct RBTNode * SearchTree;
//红黑树数据结构
struct  RBTNode{         
         int key;        
         int color;
         Position parent, left, right;
}; 
static RBT sentinel = {0, 1, 0, 0, 0}; //哨兵
#define NIL &sentinel


红黑树的查找,最小值,最大值,前驱,后继操作和二叉查找树基本一样,请参见前面的博文数据结构大总结系列之基本查找算法
二,旋转:

   旋转(Rotate):红黑树的插入和删除操作可能会破坏上边列出的红黑树性质,所以需要通过旋转,来保持红黑树性质,看看这幅图,仔细观察下,左图到右图是LeftRotate,右图到左图是RightRotate,旋转的意思是:整个图(其实就是x,y之间的链)以从左到右水平方向为轴,旋转180°,x,y位置就对调了,然后跟换结果子结点的指针就OK了。

 

左旋伪代码:

 

旋转代码实现:

//左旋,旋转只有指针被改变,其他域保持不变

void LeftRotate(SearchTree &tree, Position x) {

    Position y = x->right;

    //set y's z左子树 into x's 右子树

    x->right = y->left;

    if (y->left != NIL) {

        y->left->parent = x;

    }

 
    //下面代码修改x的父节点与y的关系

    y->parent = x->parent;

    if (x->parent == NIL) {//如果x是根节点

        tree = y;

    } else if (x == x->parent->left) {//如果x不是根节点

        x->parent->left = y;

    } else {

        x->parent->right = y;

    }

    //修改x与y的关系

    y->left = x;

    x->parent = y;

}

 

//右旋,只要把左旋里的程序对称就行了

void RightRotate(SearchTree &tree, Position x) {

    Position y = x->left;

    //set y's 右子树 into x's 左子树

    x->left = y->right;

    if (y->right != NIL) {

        y->right->parent = x;

    }

 

    //下面代码修改x的父节点与y的关系

    y->parent = x->parent;

    if (x->parent == NIL) {//如果x是根节点

        tree = y;

    } else if (x == x->parent->left) {//如果x不是根节点

        x->parent->left = y;

    } else {

        x->parent->right = y;

    }

    //修改x与y的关系

    y->right = x;

    x->parent = y;

} 


插入(RBTreeInsert)::红黑树的插入过程大致是和二叉查找树的插入过程差不多的,只是略微修改了下,因为插入新结点可能会破坏红黑树性质,所以要调用一个辅助函数RbInsertFixup()来对结点重新着色并旋转。因此红黑树插入操作的重点是辅助函数RbInsertFixup()

插入操作伪代码:

 

RBTreeInsert和BSTreeInsert区别有四点:

1)RBTreeInsert内所有NULL(代表空)都用NIL(代表一个结点)代替;

2)14,15行设置z.left和z.right为NIL,来保持正确的树结构。

3)16行将z着色为红色

4)z设置为红色可能会违反某条红黑性质,所以在17行调用RBInsertFixup(T, x)来保持红黑性质

插入有三种情况,所以相应的RBInsertFixup(T, x)case1,2,3分别处理三种情况。

 

如图:

Case1 : z的叔叔y是红色的,如图(a), (b)

A、B都为红色,破坏了性质4,因为要保证黑高度不变(性质5),同时满足性质4,只能让A、D变为黑色,而B、C变为红色(567行),这样就满足所有红黑性质了,然后将C设定为新的z,然后向上迭代。

Case2:z的叔叔y是黑色,而且z是右孩子

Case3:z的叔叔y是黑色,而且z是左孩子

从图(c)中可以看出,Case2和Case3仅仅是孩子位置不同,所以可以将Case2进行一个左旋转变为Case3,此时破坏了性质4,所以需要重新着色来保证性质4,将B着色黑色,C着色红色,此时性质4满足,但是性质5破坏了,所以再对C进行一次右旋,得到最终结果,此时满足所有性质。

PS:

1)调用RBInsertFixup(T, x)时,只可能破坏红黑性质的第2,4(2->根结点为黑色,4->一个红结点不能有红子女),其他的没影响。所以只需判断性质2,4即可。

2)循环每次迭代都会有两种可能:指针z沿着树上移,或者执行某些旋转然后循环结束。

实例:

插入代码实现: 

 

//插入的FixUp

void RBTreeInsertFixUp(SearchTree &tree, Position z) {

    while (z->parent->color == RED) {

        if (z->parent = z->parent->parent->left) {//y在右边

            Position y = z->parent->parent->right;//设定y

            if (y->color == RED) {//Case1

                z->parent->color = BLACK;

                y->color = BLACK;

                z->parent->parent ->color = RED;

                z = z->parent->parent;

            } else {

                if (z == z->parent->right) {//Case2

                    z = z->parent;//旋转会改变新结点的位置,所以要调整

                    LeftRotate(tree, z); //case2->case3

                }

                z->parent->color = BLACK;//Case3

                z->parent->parent->color = RED;

                RightRotate(tree, z->parent->parent);

            }

        } else {//y在左边

            Position y = z->parent->parent->left;

            if (y->color == RED) {

                z->parent->color = BLACK;

                y->color = BLACK;

                z->parent->parent ->color = RED;

                z = z->parent->parent;

            } else {

                if (z == z->parent->left) {//Case2

                    z = z->parent;//旋转会改变新结点的位置,所以要调整

                    RightRotate(tree, z); //case2->case3

                }

                z->parent->color = BLACK;//Case3

                z->parent->parent->color = RED;

                LeftRotate(tree, z->parent->parent);

            }

        }//if-else

    }//while

    tree->color = BLACK;

}//RBInsertFixUp

 

//插入操作

void RBTreeInsert(SearchTree &tree, int k) {

    Position y = NIL;

    Position x = tree;

    Position z = new RBT;

    z->key = k;

    z->parent = z->left = z->right = NIL;//初始化 



     //找到要插入的位置

    while (x != NIL) {

        y = x;

        if (z->key < x->key) {

            x = x->left;

        } else {

            x = x->right;

        }

    }

    //和周边点增加关系

    z->parent = y;

    if (y == NIL) {

        tree = z;

    } else {

        if (k < y->key) {

            y->left = z;

        } else {

            y->right = z;

        }

    }

    z->left = z->right = NIL;

    z->color = RED;

    RBTreeInsertFixUp(tree, z);

}