红黑树(一)

        红黑树是一种二分查找树,也算是一种自平衡的二叉树,它是在1972年由鲁道夫发明的,他称之为"对称二叉B树"。相对于AVL树的高度平衡,红黑树相对不平衡,但综合其查找、插入、删除操作的代价,仍在stl等地方广泛使用。

       首先是红黑树的五条性质:

       1、每个结点要么是红的,要么是黑的。
       2、根结点是黑色的。
       3、每个叶结点,即空结点(NIL)是黑的。
       4、如果一个结点是红的,那么它的两个儿子节点都是黑的。
       5、对每个结点,从该结点到其子孙结点的所有路径上包含相同数目的黑结点(黑高度相同)。

       记得一点,树中每个节点包含五个域:color, key, left, right和p。如果某一个节点的指针没有可指向的节点,那么该指针将指向一个哨兵。哨兵是一个真实存在的节点,它的color被设置为black,其它域可以被设为任何值。

       首先来看红黑树为什么平衡。由于性质4,每一条路径则最多有一半节点是红色(最多红黑交替排列),最少有零个红色节点,再加上性质5,拥有相同数量的黑色节点,那么最多最长是最短的高度的两倍。所以,红黑树是比较平衡的。(该证明是个人想法,详细的自己参考算法导论)

     

      以上的个人代码

enum Color{ red, black };

 struct Node{
    Node * p, * left, * right;
    int value;
    Color color;
};

Node nilNode;     //哨兵  

/*初始化哨兵节点*/
void initNilNode(){
    nilNode.p = NULL;
    nilNode.left = NULL;
    nilNode.right = NULL;
    nilNode.value = -1;
    nilNode.color = black;      //必须
}

    

      接下来,我们来看红黑树的最基本操作——左旋、右旋。

     

         左旋也是类似的。

        旋转的个人代码如下:

/*左旋函数(不考略x右节点为空,在调用函数前判断)*/
void leftRotate( Node ** root, Node * x ){
    Node * y = (*x).right;
    (*x).right = (*y).left;
    (*(*y).left).p = x;
    (*y).p = (*x).p;
    if ( (*x).p != &nilNode ){                        //原x不是根节点
        if ( x == (*(*x).p).left ){
            (*(*x).p).left = y;
        }else{
            (*(*x).p).right = y;
        }
    }else{
        *root = y;
    }
    (*y).left = x;
    (*x).p = y;
}

/*右旋函数(不考略x左节点为空,在调用函数前判断)*/
void rightRotate( Node ** root, Node * x ){
    Node * y = (*x).left;
    (*x).left = (*y).right;
    (*(*y).right).p = x;
    (*y).p = (*x).p;
    if ( (*x).p != &nilNode ){                        //原x不是根节点
        if ( x == (*(*x).p).left ){
            (*(*x).p).left = y;
        }else{
            (*(*x).p).right = y;
        }
    }else{
        *root = y;
    }
    (*y).right = x;
    (*x).p = y;
}


        接下来就是节点插入操作

        当然,基础是二分查找树的插入操作,下面是二分查找树的插入操作的个人代码:

/*插入节点函数*/
void insertRB( Node ** root, Node * insertNode ){
    if ( *root == &nilNode ){
        *root = insertNode;
        (*insertNode).color = black;
        return;
    }
    Node * now = *root;
    Node * temp = &nilNode;
    while ( now != &nilNode ){
        temp = now;
        if ( (*now).value < (*insertNode).value ){
            now = (*now).right;
        }else{
            now = (*now).left;
        }
    }
    if ( (*temp).value < (*insertNode).value ){
        (*temp).right = insertNode;
    }else{
        (*temp).left = insertNode;
    }
    (*insertNode).p = temp;
    (*insertNode).left = &nilNode;
    (*insertNode).right = &nilNode;
    (*insertNode).color = red;

    fixupInsertRB( root, insertNode );
}

         注意到代码最后有 “fixupInsertRB( root, insertNode );”的语句,那是因为插入节点后,若是根节点,为了性质1,直接涂黑,其他性质自动满足;不是根节点,则为了保持性质5,将其涂红。那么,如果父节点是黑色的,不需要任何操作,5条性质满足;如果父节点也是红色,则违反性质4,所以调用fixupInsertRB()函数,修复被破坏的性质。

        修复的时候,情况有三种(注意,父节点是红色,那么一定还有祖父节点,否则插入前就不是红黑树)


        情况一:当前节点的叔叔节点是红色的

        方法是将当前节点的父节点和叔叔节点涂黑,祖父节点(一定存在且不是哨兵)涂红(保持性质5),当前节点的指针移向祖父节点,再次循环处理。

Node * p = (*insertNode).p;

//如果当前节点的父节点是祖父节点的左孩子节点,只考虑了一半,另一半对称处理

Node * temp = (*(*p).p).right;
            if ( (*temp).color == red ){               //如果叔叔节点是红色
                (*p).color = black;
                (*temp).color = black;
                (*(*p).p).color = red;
                insertNode = (*p).p;
            }


         情况二:当前节点的叔叔是黑色的,且当前节点是右孩子

         方法是对当前节点的父节点左旋(由于当前节点和父节点都是红色,左旋将不影响性质5),当前节点的指针移向左旋前的当前节点的父节点,也就是现在的左孩子节点。这样,就转化成了情况3.

if ( insertNode == (*p).right ){    //如果当前节点是父节点的右孩子节点
                    insertNode = p;
                    leftRotate( root, p );
                    p = (*insertNode).p;
                }


         情况三:当前节点的叔叔是黑色的,且当前节点是左孩子

         方法是将父节点涂为黑色,祖父节点(一定存在,具体原因自己想)涂为红色,对祖父节点右旋。此时,所有性质满足。

(*p).color = black;
(*(*p).p).color = red;

rightRotate( root, (*p).p );


          插入修复函数的完整代码:

/*插入修复函数(由于插入节点函数使红黑树性质缺失)*/
void fixupInsertRB( Node ** root, Node * insertNode ){
    if ( insertNode == *root ){
        (*insertNode).color = black;
        return;
    }

    Node * p = (*insertNode).p;
    while ( (*p).color == red ){
        //父节点是红色,那么一定还有祖父节点,否则插入前就不是红黑树
        if ( p == (*(*p).p).left ){                        //如果当前节点的父节点是祖父节点的左孩子节点
            Node * temp = (*(*p).p).right;
            if ( (*temp).color == red ){               //如果叔叔节点是红色
                (*p).color = black;
                (*temp).color = black;
                (*(*p).p).color = red;
                insertNode = (*p).p;
            }else{                                              //如果叔叔节点是黑色
                if ( insertNode == (*p).right ){    //如果当前节点是父节点的右孩子节点
                    insertNode = p;
                    leftRotate( root, p );
                    p = (*insertNode).p;
                }
                (*p).color = black;
                (*(*p).p).color = red;
                rightRotate( root, (*p).p );
            }
        }else{                                                 //如果当前节点的父节点是祖父节点的右孩子节点(与上面对称)
            Node * temp = (*(*p).p).left;
            if ( (*temp).color == red ){               
                (*p).color = black;
                (*temp).color = black;
                (*(*p).p).color = red;
                insertNode = (*p).p;
                p = (*insertNode).p;
            }else{                                              
                if ( insertNode == (*p).left ){    
                    insertNode = p;
                    rightRotate( root, p );
                    p = (*insertNode).p;
                }
                (*p).color = black;
                (*(*p).p).color = red;
                leftRotate( root, (*p).p );
            }
        }
        p = (*insertNode).p;
    }
    (*(*root)).color = black;
}


下接《红黑树(二)》http://blog.youkuaiyun.com/u010169008/article/details/26057579

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值