数据结构之红黑树

一、红黑树性质:

红黑树是具有以下性质的二叉查找树(左小右大):

1.每一个节点或者是红色或者是黑色
2.根是黑色
3.如果一个节点是红的,那么它的子节点必须的黑的
4.从一个节点到一个NULL指针的每一条路径必须包含相同数目的黑色节点。

红黑树的高度最多是2log(N+1),平均红黑树的深度和AVL树一样,(所以查找时间一般接近最优)红黑树的优点是执行插入操作所需要的开销相对较低,在实践中发生旋转相对较少。
困难的操作是插入和删除,需要通过颜色的改变和树的旋转来满足以上4条性质

二、红黑树的插入:
分情况讨论:
1、如果插入节点的父节点是黑色的,那么插入操作很容易完成,直接进行插入。
2、如果插入节点的父节点是红色的,【因为性质4.所以我们插入的节点一定是红色的,那么这时,我们需要进行颜色翻转和位置旋转来恢复性质】
插入红色节点,由于性质3,我们必须进行颜色翻转和位置旋转来完成插入,保持红黑树的性质
     分两种情况:

           一字型:

             

           之字形:
                 

我们对以下红黑树进行插入:

              


但是当插入45时上述的方法不行了,经过旋转后有两个连续的红节点。我们需要新的方法

             

这时,我们想到将红色节点颜色与黑色节点颜色进行翻转。因此又分为两种情况:
1、插入节点的父节点的兄弟节点是黑色的,则利用上述一字型、之字形规则颜色翻转和位置旋转直接恢复
2、插入节点的父节点的兄弟节点是红色的(即一个节点右两个红儿子),则需要将拥有两个红色儿子的节点与父节点颜色翻转,然后再进行插入

               

应用上述规则,我们重新插入45。

a、我们需要先对拥有两个红儿子的节点进行颜色翻转,然后再进行插入。

           

b、经过翻转后,出现一字型情形,我们进行一字型旋转。
           

c、经过一字型旋转,现在就可以直接插入节点45
           

因此我们得到红黑树插入操作的步骤:

1、在从根节点向插入节点,有两个红色儿子的节点则需要进行颜色翻转(下滤)
2、翻转完后若不满足红黑树性质,则进行旋转操作。
3、插入


三、程序实现:

        #include "redblack.h"
        #include <stdlib.h>
        #include "fatal.h"

        typedef enum ColorType { Red, Black } ColorType;

        struct RedBlackNode
        {
            ElementType  Element;
            RedBlackTree Left;
            RedBlackTree Right;
            ColorType    Color;
        };

        static Position NullNode = NULL;  


        RedBlackTree
        Initialize( void )
        {
            RedBlackTree T;

            if( NullNode == NULL )
            {
                NullNode = malloc( sizeof( struct RedBlackNode ) );
                if( NullNode == NULL )
                    FatalError( "Out of space!!!" );
                NullNode->Left = NullNode->Right = NullNode;
                NullNode->Color = Black;
                NullNode->Element = 12345;
            }


            T = malloc( sizeof( struct RedBlackNode ) );
            if( T == NULL )
                FatalError( "Out of space!!!" );
            T->Element = NegInfinity;
            T->Left = T->Right = NullNode;
            T->Color = Black;

            return T;
        }


        void
        Output( ElementType Element )
        {
            printf( "%d\n", Element );

        }


        static void
        DoPrint( RedBlackTree T )
        {
            if( T != NullNode )
            {
                DoPrint( T->Left );
                Output( T->Element );
                DoPrint( T->Right );
            }
        }

        void
        PrintTree( RedBlackTree T )
        {
            DoPrint( T->Right );
        }


        static RedBlackTree
        MakeEmptyRec( RedBlackTree T )
        {
            if( T != NullNode )
            {
                MakeEmptyRec( T->Left );
                MakeEmptyRec( T->Right );
                free( T );
            }
            return NullNode;
        }

        RedBlackTree
        MakeEmpty( RedBlackTree T )
        {
            T->Right = MakeEmptyRec( T->Right );
            return T;
        }

        Position
        Find( ElementType X, RedBlackTree T )
        {
            if( T == NullNode )
                return NullNode;
            if( X < T->Element )
                return Find( X, T->Left );
            else
            if( X > T->Element )
                return Find( X, T->Right );
            else
                return T;
        }

        Position
        FindMin( RedBlackTree T )
        {
            T = T->Right;//
            while( T->Left != NullNode )
                T = T->Left;

            return T;
        }

        Position
        FindMax( RedBlackTree T )
        {
            while( T->Right != NullNode )
                T = T->Right;

            return T;
        }


        static Position
        SingleRotateWithLeft( Position K2 )
        {
            Position K1;

            K1 = K2->Left;
            K2->Left = K1->Right;
            K1->Right = K2;

            return K1;  /* New root */
        }


        static Position
        SingleRotateWithRight( Position K1 )
        {
            Position K2;

            K2 = K1->Right;
            K1->Right = K2->Left;
            K2->Left = K1;

            return K2;  /* New root */
        }


        static Position
        Rotate( ElementType Item, Position Parent )//旋转
        {

            if( Item < Parent->Element )//判断是左旋转还是右旋转
                return Parent->Left = Item < Parent->Left->Element ?
                    SingleRotateWithLeft( Parent->Left ) :
                    SingleRotateWithRight( Parent->Left );
            else //判断是左旋转还是右旋转
                return Parent->Right = Item < Parent->Right->Element ?
                    SingleRotateWithLeft( Parent->Right ) :
                    SingleRotateWithRight( Parent->Right );
        }

        static Position X, P, GP, GGP;//静态全局变量,保存节点X,父节点,祖父节点,曾祖父节点

        static
        void HandleReorient( ElementType Item, RedBlackTree T )//主要进行颜色翻转,并调用旋转函数
        {
            X->Color = Red;        //翻转X与其儿子节点的颜色
            X->Left->Color = Black;
            X->Right->Color = Black;

	    //X翻转完后颜色是红色

            if( P->Color == Red )  //如果父节点是红色的
            {
                GP->Color = Red;
                if( (Item < GP->Element) != (Item < P->Element) )//判断是否需要多一次旋转(之字形双旋转)
                    P = Rotate( Item, GP );  
                X = Rotate( Item, GGP );
                X->Color = Black;
            }
            T->Right->Color = Black;  /* Make root black */
        }

        RedBlackTree
        Insert( ElementType Item, RedBlackTree T )
        {
            X = P = GP = T;
            NullNode->Element = Item;//这样初始化,当X下滤到叶子节点时,下面的while循环就会跳出
            while( X->Element != Item )  //进行下滤 
            {
                GGP = GP; GP = P; P = X;
                if( Item < X->Element )
                    X = X->Left;
                else
                    X = X->Right;
                if( X->Left->Color == Red && X->Right->Color == Red )//如果X有两个红儿子节点,就进行颜色翻转,位置旋转
                    HandleReorient( Item, T );
            }

            if( X != NullNode )//说明没有下滤到叶子节点,上述while循环就跳出了,所以Item已经在树中了,不用插入,返回NULL
                return NullNode;  

			//将Item进行插入
            X = malloc( sizeof( struct RedBlackNode ) );
            if( X == NULL )
                FatalError( "Out of space!!!" );
            X->Element = Item;//执行到这一步说明树中没有Item,X已经等于NullNode,X->Color=Black;
            X->Left = X->Right = NullNode;

            if( Item < P->Element )  //将Item插入合适的位置
                P->Left = X;
            else
                P->Right = X;
            HandleReorient( Item, T ); //因为之前X的Color为黑色,所以这里将颜色变为红色

            return T;
        }


        RedBlackTree
        Remove( ElementType Item, RedBlackTree T )
        {
            printf( "Remove is unimplemented\n" );
            if( Item )
                return T;
            return T;
        }

        ElementType
        Retrieve( Position P )
        {
            return P->Element;
        }

完整程序代码:GitHub

红黑树的具体实现比较复杂,我们使用两个标记节点:一个是根,一个是NullNode,它的作用像是在伸展树中那样是指示一个Null指针。
根标记将存储关键字-oo和一个指向真正的根的右指针。

四、插入程序执行过程分析:

插入元素45:
X=P=GP=T=-10000
进入while循环

1、

GGP=GP=-10000; GP=P=-10000; P=X=-10000;

Item=45>-10000

X=X->Right=30;

X->Left->Color == Red && X->Right->Color == Red 不成立

2、                    

GGP=GP=-10000;GP=P=-10000;P=X=30;

Item=45>30

X=X->Right=70;

X->Left->Color == Red && X->Right->Color == Red 不成立

3、

GGP=GP=-10000;GP=P=30;P=X=70;

Item=45<70

X=X->Left=60;

X->Left->Color == Red && X->Right->Color == Red 不成立

4、

GGP=GP=30;GP=P=70;P=X=60;

Item=45<60

X=X->Left=50;

X->Left->Color == Red && X->Right->Color == Red 成立

   HandleReorient( 45, T );

   将X与其儿子节点的颜色翻转互换,X翻转完后颜色是红色。

   P=60颜色也是红色,所以需要进行旋转变色

   根据上面旋转示意图可知,不论单旋转还是双旋转,都需要将G的颜色变为红色。

   一字型,进行单旋转。

   X = Rotate( 45, 30 );

   因为

   Item > Parent->Element 

   45>30      

   Item < Parent->Right->Element  

   45<70

   所以 

   SingleRotateWithLeft( Parent->Right ) 

   SingleRotateWithLeft( 70 ) 

   K2=70

   K1=60

   K2->Left=65

   K1->Right=70

   因此Parent->Right=60

   Rotate( 45, 30 )返回60

X=60;

最后再将X的颜色变为黑色。

T->Right->Color = Black并确保根的颜色为黑色

5、 

GGP=GP=70;GP=P=60;P=X=60;

Item=45<60

X=X->Left=50;

X->Left->Color == Red && X->Right->Color == Red 不成立

6、

GGP=GP=60;GP=P=60;P=X=50;

Item=45<50

X=X->Left=40;

X->Left->Color == Red && X->Right->Color == Red 不成立

7、

GGP=GP=60;GP=P=50;P=X=40;

Item=45>40

X=X->Right=NullNode;

X->Left->Color == Red && X->Right->Color == Red 不成立

8、跳出循环

Item=45 > P->Element=40
P->Right=X=45
HandleReorient( 45, T )
将X颜色变为红色,X子节点的颜色变为黑色
P->Color为黑色
T->Right->Color也为黑色

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值