内核红黑树-II

主要涉及红黑树的删除操作,还是先列一下红黑树的5个准则。

  1. A node is either red or black
    节点的颜色非红即黑
  2. The root is black
    根节点为黑色
  3. All leaves (NULL) are black
    所有的叶子(NULL)节点都为黑色
  4. Both children of every red node are black
    红色节点的子节点都为黑色
  5. Every simple path from root to leaves contains the same number of black nodes.
    每个由根到叶子节点的路径必须包含相同数量的黑色节点

如下函数rb_erase负责由红黑树root上删除node节点,主要由两个函数组成。

void rb_erase(struct rb_node *node, struct rb_root *root)
{   
    struct rb_node *rebalance;
    rebalance = __rb_erase_augmented(node, root, NULL, &dummy_callbacks);
    if (rebalance)
        ____rb_erase_color(rebalance, root, dummy_rotate);
} 

1 删除操作

这包括节点node的删除,以及寻找合适节点替换其位置,但若是节点node没有子节点,不需要替换操作。如下函数__rb_erase_augmented。

static __always_inline struct rb_node *
__rb_erase_augmented(struct rb_node *node, struct rb_root *root,
             struct rb_node **leftmost, const struct rb_augment_callbacks *augment)
{               
    struct rb_node *child = node->rb_right;
    struct rb_node *tmp = node->rb_left;
    struct rb_node *parent, *rebalance;
    unsigned long pc;

    if (leftmost && node == *leftmost)
        *leftmost = rb_next(node);

1.1 左子节点为空

删除节点node的左子节点为空,由准则5可得出,如果存在右子节点,其必定为红色,进而根据准则4得出此时删除节点的颜色为黑色。函数__rb_change_child使用node的右子节点child替换掉node节点自身,并且将child设置为之前父节点的颜色(即node节点的黑色),此时红黑树不需要再进行平衡操作,无需调用__rb_erase_color函数。

    if (!tmp) {
        /*
         * Case 1: node to erase has no more than 1 child (easy!)
         *
         * Note that if there is one child it must be red due to 5)
         * and node must be black due to 4). We adjust colors locally
         * so as to bypass __rb_erase_color() later on.
         */ 
        pc = node->__rb_parent_color;
        parent = __rb_parent(pc);
        __rb_change_child(node, child, parent, root);
        if (child) {
            /*         大写字母表示黑色,小写字母表示红色
			 *    N
			 *	   \
			 *	    c    
			 */
            child->__rb_parent_color = pc;
            rebalance = NULL;

否则,如果删除节点node的右子节点也不存在,即node节点的左右子节点都为空,不能确定删除节点的颜色。此时,如果删除节点node的颜色为红色,将其删除不会违反准则5,红黑树不需要进行平衡操作。但是,如果删除节点为黑色,删除一个黑色节点将导致违反准则5,需要进行再平衡。需要平衡的点为node节点的父节点parent,将其返回,交由函数__rb_erase_color处理。

下图只是列出的示例情况,删除节点node也可能为其父节点的左子节点。

        } else {
            /*    node为红色           |        node为黑色
			 * 
			 *      P           P            (p)         (p)
			 *     / \         /             / \         /
			 *   (l)  n  --> (l)           (l)  N  --> (l)
			 */
            rebalance = __rb_is_black(pc) ? parent : NULL;
        }
        tmp = parent;

1.2 右子节点为空

这里已经明确了,删除节点node的右子节点为空,而左子节点有值。此种情况下,可以确定其左子节点为红色,删除节点node为黑色。将左子节点设置为父节点颜色,此种情况不需要做平衡操作。

    } else if (!child) {  //case-1的第二种形式
        /* Still case 1, but this time the child is node->rb_left */
         *
	     *         (p)  颜色不确定   (p)
	     *         /               /
         *        N               C
         *       /  
         *      c   
         */
        tmp->__rb_parent_color = pc = node->__rb_parent_color;
        parent = __rb_parent(pc);
        __rb_change_child(node, tmp, parent, root);
        rebalance = NULL;
        tmp = parent;

1.3 左右子节点都非空

删除节点node的左子节点和右子节点都不为空,这是需要确定哪个节点可替换删除的节点,即需要确定seccessor节点,内核中在右子树中查找替换节点,首先假定替换节点就是右子节点child。

    } else {
        struct rb_node *successor = child, *child2;

        tmp = child->rb_left;
1.3.1 替换节点的左子节点为空

如下图所示,内核称为Case-2,以上1.1和1.2节描述的都是Case-1。如果要删除的节点(node)的右子节点(s)没有左子节点,那么,使用右子节点s替换删除节点n的位置。此处使用parent记录下替换节点,并未执行实际的替换操作。

节点child2为替换节点的右子节点,之前使用的child为删除节点node的右子节点。

        if (!tmp) {
            /*
             * Case 2: node's successor is its right child
             *
             *    (n)          (s)
             *    / \          / \
             *  (x) (s)  ->  (x) (c)
             *        \
             *        (c)
             */
            parent = successor;
            child2 = successor->rb_right;

            augment->copy(node, successor);

如上所示,如果替换节点s的右子节点为空,即child2为空,并且s节点的颜色为黑色时,去掉一个黑色节点将违反准则5,需要进行红黑树的再平衡操作,parent将作为再平衡点返回,以下将会看到。

            (n)          (s)
            / \          / 
          (x)  S   ->  (x) 
1.3.2 替换节点的左子节点非空

如下Case-3,删除节点node的替换节点(即其右子节点,初始确定的替换节点)存在左子树,找到最左侧的节点,如下图为s(其为node的右树中的最小节点),作为要删除节点node的新的替换节点successor,p和c分别表示替换节点的父节点和子节点,对应变量parent和child2。

节点s替换node之后的树如下所示。这里执行了部分替换工作,将node节点的右树移动到了替换节点s的右树上,并且将替换节点s的右树移动到了s的父节点的左树上。但是,如与上一节一样,这里没有执行node节点的删除,也没有将s节点替换node。

        } else {
            /*
             * Case 3: node's successor is leftmost under node's right child subtree
             *
             *    (n)          (s)
             *    / \          / \
             *  (x) (y)  ->  (x) (y)
             *      /            /
             *    (p)          (p)
             *    /            /
             *  (s)          (c)
             *    \
             *    (c)
             */
            do {
                parent = successor;
                successor = tmp;
                tmp = tmp->rb_left;
            } while (tmp);
            child2 = successor->rb_right;
            WRITE_ONCE(parent->rb_left, child2);
            WRITE_ONCE(successor->rb_right, child);
            rb_set_parent(child, successor);

            augment->copy(node, successor);
            augment->propagate(parent, successor);
        }
1.3.3 节点替换操作

以上两种情况都还没有处理删除节点node的替换工作,此处开始执行。首先将node节点的左节点设置为新的替换节点successor的左节点,同时,将新successor节点设置为左节点的父节点,注意,赋值函数rb_set_parent不会改变左节点的颜色。

另外,就是需要处理删除节点node的父节点,将successor连接到删除节点node的父节点上(可能是左节点,也可能为右节点)。

        tmp = node->rb_left;
        WRITE_ONCE(successor->rb_left, tmp);
        rb_set_parent(tmp, successor);

        pc = node->__rb_parent_color;
        tmp = __rb_parent(pc);
        __rb_change_child(node, successor, tmp, root);

以下将node的父节点设置为successor节点的父节点。对于替换节点successor的原有右子节点child2,如果其存在,其颜色为红色,否则不符合准则5。这意味着其父节点successor为黑色,在执行successor替换node节点之后,将successor的颜色转换为node的颜色,无论node的颜色是黑还是红,都将导致右子树的黑色节点减少,需要将child2翻转为黑色以符合准则5。这样红黑树达到平衡,不需要rebalance处理。

        if (child2) {
            successor->__rb_parent_color = pc;
            rb_set_parent_color(child2, parent, RB_BLACK);
            rebalance = NULL;

否则,如果child2为空,即替换节点s原本没有任何子节点,无法判定其颜色,这里有两种情况,其一是successor节点的颜色为红色,其替换node节点之后,修改为node节点的颜色,对红黑树的平衡没有影响,不需要重新平衡处理。

其二,当successor节点为黑色时,在替换node节点之后,右子树将不平衡(不能像之前child2有值时,将child2设置为黑色来达到平衡),需要重新进行平衡处理。不平衡的节点为rebalance,其等于successor节点的原父节点,将其返回。

        } else {
        /*
         *      替换节点s为红色      |        替换节点s为黑色
         *    (n)          (s)           (n)          (s)
         *    / \          / \           / \          / \
         *  (x) (y)  ->  (x) (y)       (x) (y)  ->  (x) (y)
         *      /            /             /            / 
         *    (p)          (p)           (p)          (p)  
         *    /                          / \            \    
         *   s                          S   B            B   
         * 
         */
            unsigned long pc2 = successor->__rb_parent_color;
            successor->__rb_parent_color = pc;
            rebalance = __rb_is_black(pc2) ? parent : NULL;
        }
        tmp = successor;
    }

    augment->propagate(tmp, NULL);
    return rebalance;

2 重新平衡

根据上节的介绍,可知在替换节点没有子节点,并且其颜色为黑色时,需要进行再平衡操作,需要平衡的点为替换节点的父节点,不过,对于上一节的Case-2,需要平衡的节点为替换节点自身。以下为处理函数:____rb_erase_color。

需要说明的是,在替换节点successor替换了删除节点node之后,相当于删除了替换节点successor,而successor节点原本没有任何子节点,颜色为黑色,这就需要进行红黑树的旋转操作,以达到平衡状态。

static __always_inline void
____rb_erase_color(struct rb_node *parent, struct rb_root *root,
    void (*augment_rotate)(struct rb_node *old, struct rb_node *new))
{
    struct rb_node *node = NULL, *sibling, *tmp1, *tmp2;

    while (true) {
        /*
         * Loop invariants:
         * - node is black (or NULL on first iteration)
         * - node is not the root (parent is not NULL)
         * - All leaf paths going through parent and node have a
         *   black node count that is 1 lower than other leaf paths.
         */

以上while循环的条件有三个:

  • node节点为黑色,或者为NULL,
  • node节点不是根节点,
  • 所有经过parent和node节点的路径中的黑色节点数量比其他的路径少一个。

2.1 替换节点为父节点的左子节点

对于本节中函数使用的变量约定如下:

sibling – 父节点的右子节点
tmp1 – sibling节点的右子节点
tmp2 – sibling节点的左子节点

最初时,node为空,如果父节点的右子节点不为空,表明替换节点为父节点的左子节点,执行以下操作。对应于上一节的Case-1的第二种情况,以及Case-3。相当于删除了父节点parent的左子节点。

256         sibling = parent->rb_right;
257         if (node != sibling) {  /* node == parent->rb_left */
2.1.1 parent右子节点(sibling)为红色

如上节1.3.3所示,替换节点(等于被删除节点)位于父节点的左侧(左子节点),如果右侧的右子节点为红色,根据准则4,Sl和Sr节点为黑色,明显的,左右子树的黑色节点不平衡了,进行左旋操作。之后,将父节点p由黑转红,节点s由红转黑,由__rb_rotate_set_parents实现。

            if (rb_is_red(sibling)) {
                /*
                 * Case 1 - left rotate at parent
                 *
                 *     P               S
                 *    / \             / \
                 *   N   s    -->    p   Sr
                 *      / \         / \
                 *     Sl  Sr      N   Sl
                 */
                tmp1 = sibling->rb_left;
                WRITE_ONCE(parent->rb_right, tmp1);
                WRITE_ONCE(sibling->rb_left, parent);
                rb_set_parent_color(tmp1, parent, RB_BLACK);
                __rb_rotate_set_parents(parent, sibling, root, RB_RED);
                augment_rotate(parent, sibling);
                sibling = tmp1;
            }
2.1.2 sibling节点为黑色 且其右子节点为空或者黑色

此小节可经过以上2.1.1节的处理之后而来,Sl取代了原来的sibling节点位置,成为父节点的右子节点,这种情况下,父节点已为红色,其右子节点Sl为黑色。否则,上一小节2.1.1不成立,父节点的右子节点本就为黑色,这时不能确定父节点的颜色。

以下处理sibling节点的右子节点为空或者黑色的情况。

            tmp1 = sibling->rb_right;
            if (!tmp1 || rb_is_black(tmp1)) {
                tmp2 = sibling->rb_left;
2.1.2.1 sibling左子节点为空或者黑色

如果sibling的左子节点(tmp2)也是空或者黑色,即Case-2。通过将sibling设置为红色,同时将父节点设置为黑色,减少右子树的黑色节点数量来达到平衡,处理完成。否则,如果父节点已经是黑色了,则不能简单的将其设置为红色,因为这将可能与祖父节点的颜色冲突。此时,将父节点设置为node,再取出其父节点(祖父节点),跳回到函数开始重新进行调整。如果其父节点已经是树根了,则不需要处理。

                if (!tmp2 || rb_is_black(tmp2)) {
                    /*
                     * Case 2 - sibling color flip (p could be either color here)
                     *
                     *    (p)           (p)
                     *    / \           / \
                     *   N   S    -->  N   s
                     *      / \           / \
                     *     Sl  Sr        Sl  Sr
                     *
                     * This leaves us violating 5) which can be fixed by flipping p to black
                     * if it was red, or by recursing at p. p is red when coming from Case 1.
                     */
                    rb_set_parent_color(sibling, parent, RB_RED);
                    if (rb_is_red(parent))
                        rb_set_black(parent);
                    else {
                        node = parent;
                        parent = rb_parent(node);
                        if (parent) continue;
                    }
                    break;
                }
2.1.2.2 sibling左子节点存在且为红色

此处,sibling节点的颜色为黑色,sibling的左子节点存在且为红色,右子节点为空或者黑色,如下所示。以sibling节点为基点进行右旋操作,sl节点的左子树不变,sl的右子树移动到sibling节点的左树上,并且倒换sl和sibling节点的父子关系。

经过右旋之后,sl节点成为了父节点的右子节点(sibling),而原有的sibling节点成为了新的sibling节点的右子节点(tmp1)。另外,在右旋之后,如果父节点p的颜色为红色,那么p和sl节点为连续的红色节点,违反了准则4,这将在下一节左旋时进行处理。

左旋操作以父节点p为基点,如下第二个图所示,左旋之后,将sl设置为父节点p的颜色,将父节点p设置为黑色,达到平衡。

                /*
                 * Case 3 - right rotate at sibling (p could be either color here)
                 *
                 *   (p)           (p)
                 *   / \           / \
                 *  N   S    -->  N   sl
                 *     / \             \
                 *    sl  Sr            S
                 *                       \
                 *                        Sr
                 *
                 * Note: p might be red, and then both p and sl are red after rotation(which
                 * breaks property 4). This is fixed in
                 * Case 4 (in __rb_rotate_set_parents() which set sl the color of p and set p RB_BLACK)
                 *
                 *   (p)            (sl)
                 *   / \            /  \
                 *  N   sl   -->   P    S
                 *       \        /      \
                 *        S      N        Sr
                 *         \
                 *          Sr
                 */
                tmp1 = tmp2->rb_right;
                WRITE_ONCE(sibling->rb_left, tmp1);
                WRITE_ONCE(tmp2->rb_right, sibling);
                WRITE_ONCE(parent->rb_right, tmp2);
                if (tmp1)
                    rb_set_parent_color(tmp1, sibling, RB_BLACK);
                augment_rotate(sibling, tmp2);
                tmp1 = sibling;
                sibling = tmp2;
            }

以上的右旋操作并没有完成,sibling的父节点仍然指向p,而不是sl。节点sl的父节点仍然指向sibling。在最后经过sibling替换之后,意味着,新的sibling节点的父节点为还是指向S节点,而s节点的父节点还是指向p。

                 *   (p)         |-->(p)                        |-->(p)
                 *   / \         |   / \      parent-node       |   / \       parent-node
                 *  N   S    --> |  N   sl -------|         --> |  N   s ---------|
                 *     / \       |       \        |             |       \         | 
                 *    sl  Sr     |------- S <-----|             |------- TMP1 <---|
                 *            parent-node  \                 parent-node  \
                 *                          Sr                             Sr
2.1.3 sibling节点为黑色 且其右子节点为红色

如下所示,以父节点p为基点进行左旋操作,之后,将父节点p设置为黑色,sibling节点设置为父节点p的颜色,达到平衡。

另外,如果经过了2.1.2.2节的处理,sibling节点为红色,其右子节点为黑色,与本节处理的情况相反,但是,根据上一节的介绍,s节点和sl节点的父节点指向还不正确,并且,在父节点p为红色的情况下,违反准则4,经过本节的处理才能修正这些问题。

            /*
             * Case 4 - left rotate at parent + color flips (p and sl could be either color here.
             *  After rotation, p becomes black, s acquires p's color, and sl keeps its color)
             *
             *      (p)             (s)
             *      / \             / \
             *     N   S     -->   P   Sr
             *        / \         / \
             *      (sl) sr      N  (sl)
             */
            tmp2 = sibling->rb_left;
            WRITE_ONCE(parent->rb_right, tmp2);
            WRITE_ONCE(sibling->rb_left, parent);
            rb_set_parent_color(tmp1, sibling, RB_BLACK);
            if (tmp2)
                rb_set_parent(tmp2, parent);
            __rb_rotate_set_parents(parent, sibling, root, RB_BLACK);
            augment_rotate(parent, sibling);
            break;

2.2 替换节点为父节点的右子节点

对于本节中函数使用的变量约定如下:

sibling – 父节点的左子节点
tmp1 – sibling节点的左子节点
tmp2 – sibling节点的右子节点

替换节点node为父节点的右子节点,这里将sibling节点设置为父节点的左子节点。与以上的2.1节处理基本相同,仅是更换了左右节点。

        } else {
            sibling = parent->rb_left;
2.2.1 parent左子节点(sibling)为红色

如下图所示,替换节点(等于被删除节点)位于父节点的右侧(右子节点),如果左侧的左子节点为红色,根据准则4,Sl和Sr节点为黑色,明显的,左右子树的黑色节点不平衡了,进行右旋操作。并且,将sibling节点设置为黑色,父节点p设置为红色。

另外,更新sibling的值,其始终指向父节点的左子节点。

            if (rb_is_red(sibling)) {
                /* Case 1 - right rotate at parent */
                /*
                 *       P               S
                 *      / \             / \
                 *     s   N    -->   Sl   p
                 *    / \                 / \
                 *   Sl  Sr              Sr  N
                 */
                tmp1 = sibling->rb_right;
                WRITE_ONCE(parent->rb_left, tmp1);
                WRITE_ONCE(sibling->rb_right, parent);
                rb_set_parent_color(tmp1, parent, RB_BLACK);
                __rb_rotate_set_parents(parent, sibling, root, RB_RED);
                augment_rotate(parent, sibling);
                sibling = tmp1;
            }

以下几节的操作中,sibling节点都为黑色了。

2.2.2 sibling节点为黑色 且其左子节点为空或者黑色

此小节可经过以上2.2.1节的处理之后而来,Sr取代了原来的sibling节点位置,成为父节点的左子节点,这种情况下,父节点已为红色,其左子节点Sr为黑色。否则,上一小节2.2.1不成立,父节点的左子节点本就为黑色,这时不能确定父节点的颜色。

以下处理sibling节点的左子节点为空或者黑色的情况。

            tmp1 = sibling->rb_left;
            if (!tmp1 || rb_is_black(tmp1)) {
2.2.2.1 sibling右子节点为空或者黑色

如果sibling的右子节点(tmp2/Sr)也是空或者黑色,即Case-2。通过将sibling设置为红色,同时将父节点设置为黑色,减少左子树的黑色节点数量来达到平衡,处理完成。否则,如果父节点已经是黑色了,则不能简单的将其设置为红色,因为这将可能与祖父节点的颜色冲突。此时,将父节点设置为node,再取出其父节点(祖父节点),跳回到函数开始重新进行调整。如果其父节点已经是树根了,则不需要处理。

                tmp2 = sibling->rb_right;
                if (!tmp2 || rb_is_black(tmp2)) {
                    /* Case 2 - sibling color flip */
                     *
                     *      (p)           (p)
                     *      / \           / \
                     *     S   N    -->  s   N
                     *    / \           / \
                     *   Sl  Sr        Sl  Sr
                     *
                     * This leaves us violating 5) which can be fixed by flipping p to black
                     * if it was red, or by recursing at p. p is red when coming from Case 1.
                     */
                    rb_set_parent_color(sibling, parent, RB_RED);
                    if (rb_is_red(parent))
                        rb_set_black(parent);
                    else {
                        node = parent;
                        parent = rb_parent(node);
                        if (parent)
                            continue;
                    }
                    break;
                }
2.2.2.2 sibling右子节点存在且为红色

此处,sibling节点的颜色为黑色,sibling的右子节点存在且为红色,左子节点为空或者黑色,如下所示。以sibling节点为基点进行左旋操作,sr节点的右子树不变,sr的左子树移动到sibling节点的右子树上,并且倒换sr和sibling节点的父子关系。

经过左旋之后,sr节点成为了父节点的左子节点(sibling),而原有的sibling节点成为了新的sibling节点的左子节点(tmp1)。另外,在左旋之后,如果父节点p的颜色为红色,那么p和sr节点为连续的红色节点,违反了准则4,这将在下一节右旋时进行处理。

下一节的右旋操作以父节点p为基点,如下第二个图所示,右旋之后,将sr设置为父节点p的颜色,将父节点p设置为黑色,达到平衡。

                /* Case 3 - left rotate at sibling */
                 *
                 *     (p)           (p)
                 *     / \           / \
                 *    S   N    -->  sr  N
                 *   / \           /
                 *  Sl  sr        S 
                 *               /   
                 *              Sl      
                 *
                 * Note: p might be red, and then both p and sr are red after rotation(which
                 * breaks property 4). This is fixed in
                 * Case 4 (in __rb_rotate_set_parents() which set sr the color of p and set p RB_BLACK)
                 *
                 *        (p)            (sr)
                 *        / \            /  \
                 *       sr  N   -->    S    P
                 *      /              /      \
                 *     S              Sl       N
                 *    /          
                 *   Sl            
                 */
                tmp1 = tmp2->rb_left;
                WRITE_ONCE(sibling->rb_right, tmp1);
                WRITE_ONCE(tmp2->rb_left, sibling);
                WRITE_ONCE(parent->rb_left, tmp2);
                if (tmp1)
                    rb_set_parent_color(tmp1, sibling, RB_BLACK);
                augment_rotate(sibling, tmp2);
                tmp1 = sibling;
                sibling = tmp2;
            }

以上的左旋操作并没有完成,sibling的父节点仍然指向p,而不是sr。节点sr的父节点仍然指向sibling。在最后经过sibling替换之后,意味着,新的sibling节点的父节点还是指向S节点,而s节点的父节点还是指向p。

                 *      (p)           |------->(p)                 |--------> (p)
                 *      / \           |        / \                 |         /   \      
                 *     S   N    -->   |      sr   N          -->   |     sibling  N
                 *    / \             |      / \----|              |       / \----|  parent-node
                 *   sl  Sr           |---- S <-----|              |---- TMP1 <---|
                 *           parent-node   /     parent-node parent-node /
                 *                        Sl                            Sl
2.2.3 sibling节点为黑色 且其左子节点为红色

如下所示,以父节点p为基点进行右旋操作,之后,将父节点p设置为黑色,sibling节点设置为父节点p的颜色,达到平衡。

另外,如果经过了2.2.2.2节的处理,sibling节点为红色,其左子节点为黑色,与本节处理的情况相反,但是,根据上一节的介绍,s节点和sr节点的父节点指向还不正确,并且,在父节点p为红色的情况下,违反准则4,经过本节的处理才能修正这些问题。

            /* Case 4 - right rotate at parent + color flips */
             *
             *
             *       (p)             (s)
             *       / \             / \
             *      S   N     -->   Sl  P
             *     / \                 / \
             *    sl (sr)            (sr) N
             */
            tmp2 = sibling->rb_right;
            WRITE_ONCE(parent->rb_left, tmp2);
            WRITE_ONCE(sibling->rb_right, parent);
            rb_set_parent_color(tmp1, sibling, RB_BLACK);
            if (tmp2)
                rb_set_parent(tmp2, parent);
            __rb_rotate_set_parents(parent, sibling, root, RB_BLACK);
            augment_rotate(parent, sibling);
            break;
        }
    }
}

内核版本 5.0

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页