红黑树笔记——红黑树的插入操作

红黑树的插入操作可以在O(logn)的时间内完成。开始插入节点的时候和二叉查找树一样,只需要最后将插入的节点着成红色,为了保证红黑树的性质,需要通过RB_InsertFixUp函数来调整该节点,对其重新着色并旋转。

下面先调用RB_Insert()函数将一个节点插入到红黑树中,同样先上伪代码

 

RB-INSERT(T, z)
 1  y ← nil[T]
 2  x ← root[T]
 3  while x ≠ nil[T]
 4      do y ← x
 5         if key[z] < key[x]
 6            then x ← left[x]
 7            else x ← right[x]
 8  p[z] ← y
 9  if y = nil[T]
10     then root[T] ← z
11     else if key[z] < key[y]
12             then left[y] ← z
13             else right[y] ← z
14  left[z] ← nil[T]
15  right[z] ← nil[T]
16  color[z] ← RED
17  RB-INSERT-FIXUP(T, z)

接着上C++代码并进行代码分析

 

 

bool RB_Insert(int key, int data)
    {
        RB_Node *p = root;
        RB_Node *insert_p = NIL;

        //找到插入在哪个节点之后
        while(p != NIL)
        {
            insert_p = p;
            if(key < p->key) p = p->left;
            else if(key > p->key) p = p->right;
            else return false;//重复的数据不进行插入
        }

        //初始化插入节点,注意插入节点初始的颜色为RED
        RB_Node *insert_node = new RB_Node();
        insert_node->key = key;
        insert_node->data = data;
        insert_node->RB_COLOR = RED;
        insert_node->right = NIL;
        insert_node->left = NIL;

        //如果插入的是一颗空树,设定root就是插入节点
        if(insert_p == NIL)
        {
            root = insert_node;
            root->parent = NIL;
            NIL->left = NIL->right = NIL->parent = root;
        }
        else//判断插入左节点还是右节点
        {
            if(key < insert_p->key)
                insert_p->left = insert_node;
            else insert_p->right = insert_node;
            insert_node->parent = insert_p;
        }
        //修复红黑树的性质
        RB_InsertFixUp(insert_node);
        return true;
    }

为了保证红黑树的结构性质,需要调用RB_InsertFixUp函数来调整刚刚插入的节点

 

我们考虑下,如果一个红色节点(下文称用Z指向它)被插入到树中,那么有哪些红黑性质可能被破坏呢?只有性质2(根节点是黑色的,由于插入的可能是根节点)以及性质4(红色节点的子节点一定是黑色节点,其父节点是红色的),其它都不会被破坏。

如果插入的节点的父节点是黑色的,那么不需要做任何调整,这红黑树是正常的。如果父节点是红色,或插在树根的位置,那么就要进行调整以保证红黑树性质。

首先对性质4进行分析,我们知道,插入一个新的节点,这个节点肯定会被放到树的底部成为一个叶节点,那么这个红色节点就没有可能和自己的子节点同色(因为叶节点的子节点是NIL节点,都是黑色的),如果性质4被破坏的话,肯定是Z指向的节点的父节点是红色的。因此,为了使分析和解决更加容易和清晰,我们在对树进行调整以恢复红黑特性时,始终使得Z总是指向相邻红色的节点中的子节点(指针Z可能会向上升到树的中部或根部)。基于这个做法,我们可以知道,如果是性质2被破坏了的话,也就是Z指向根节点了,那么性质4肯定就符合了(因为Z的父节点是NIL,黑色的),因此对性质2的恢复变得很简单,只需要被根从红色变为黑色即可。
现在只需要处理性质4被破坏的问题,如果性质4被破坏了,也就是说,Z的父节点是红色的,那么,说明,Z一定有祖父节点,而且是黑色的(否则插入前原树就有问题,又或是调整时的方法不正确)。因此可以把问题放到以Z的祖父节点为根节点的子树内进行解决,这样可以把调整的范围最小化,而且这也是有可能的:只要不改变这子树的黑高度,那么就不会对树的其它部分产生影响。我们要做的就是在这个子树范围内把红黑性质调整回来。再看子树的根是否与其父节点同为红色,是的话,就再次用前面所说的去解决它,一直向上递归到红黑性质被恢复为止。

对性质4的恢复,根据Z的父节点是Z的祖节点的左子节点还是右子节点,分为两组对称的情况,每组有3种情况。下面我们以Z的父节点是Z祖节点的左子节点为例:  

定义叔叔的含义:即祖父节点的另外一个子节点

情况1:z的叔叔y是红色的

RBTree1

如上图所示,在这种情况下,将父、叔节点都着为黑色,再将子树根节点着为红色,那么子树的黑高度没有发生改变,而且红黑性质得得到了调整。此时,再将Z指向子树的根节点,向上递归恢复红黑特性。

情况2:z的叔叔y节点是黑色的,而且z是父节点的左子节点

如上图,将Z的父节点与祖节点进行一次右旋,并把父节点着黑色,原来的祖节点着红色。这些子树的红黑特性得到了恢复,而且子树的黑高度没有变化。另外,由于子树根节点已经是黑色了(这个节点不会出现父子同为红色的问题了),所以不必再向上递归了,此时整个树的红黑特性都已经是正确的了。

情况3:z的叔叔y节点是黑色的,而且z是父节点的右子节点

如上图,将Z本身与其父节点进行一次左旋,让Z指向原来的父节点,就可以调整为情况2,而情况2已经得到解决。

由此红黑树插入后调整问题已经解决。

伪代码:

 

 1 while color[p[z]] = RED
 2     do if p[z] = left[p[p[z]]]
 3           then y ← right[p[p[z]]]
 4                if color[y] = RED
 5                   then color[p[z]] ← BLACK                    // Case 1
 6                        color[y] ← BLACK                       // Case 1
 7                        color[p[p[z]]] ← RED                   // Case 1
 8                        z ← p[p[z]]                            // Case 1
 9                   else if z = right[p[z]]
10                           then z ← p[z]                       // Case 2
11                                LEFT-ROTATE(T, z)               // Case 2
12                           color[p[z]] ← BLACK                 // Case 3
13                           color[p[p[z]]] ← RED                // Case 3
14                           RIGHT-ROTATE(T, p[p[z]])             // Case 3
15           else (same as then clause
                         with "right" and "left" exchanged)
16 color[root[T]] ← BLACK

 

 

 

RB_InsertFixUp C++源码:

 void RB_InsertFixUp(RB_Node *node)
    {
        while(node->parent->RB_COLOR == RED)
        {
            if(node->parent == node->parent->parent->left)
            {
                RB_Node *uncle = node->parent->parent->right;
                //情况1,z的叔叔y是红色的
                if(uncle->RB_COLOR == RED)
                {
                    node->parent->RB_COLOR = BLACK;
                    uncle->RB_COLOR = BLACK;
                    node->parent->parent->RB_COLOR = RED;
                    node = node->parent->parent;
                }
                
                else if(uncle->RB_COLOR == BLACK)
                {
                    //情况3,z的叔叔y是黑色的,z是右孩子
                    if(node == node->parent->right)
                    {
                        node = node->parent;
                        RB_LeftRotate(node);
                    }
                    //情况2,z的叔叔y是黑色的,但z是左孩子
                    node->parent->RB_COLOR = BLACK;
                    node->parent->parent->RB_COLOR = RED;
                    RB_RightRotate(node->parent->parent);
                }
            }
            else
            {
                RB_Node *uncle = node->parent->parent->left;
                if(uncle->RB_COLOR == RED)
                {
                    node->parent->RB_COLOR = BLACK;
                    uncle->RB_COLOR = BLACK;
                    uncle->parent->RB_COLOR = RED;
                    node = node->parent->parent;
                }
                else if (uncle->RB_COLOR == BLACK)
                {
                    if(node == node->parent->left)
                    {
                        node = node->parent;
                        RB_RightRotate(node);
                    }
                    node->parent->RB_COLOR = BLACK;
                    node->parent->parent->RB_COLOR = RED;
                    RB_LeftRotate(node->parent->parent);
                }
            }
        }
        root->RB_COLOR = BLACK;
    }

 

 

 


 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值