STL源码剖析——RB_TREE

本文详细介绍了STL中的RB_TREE,解析红黑树的旋转、插入、删除操作。红黑树是一种自平衡二叉查找树,常用于STL容器set和map。文中通过实例分析旋转操作,包括左旋和右旋,以及插入和删除节点可能导致的不平衡情况及相应的修复策略。文章适合对数据结构有一定基础的读者深入理解红黑树。
摘要由CSDN通过智能技术生成

花了差不多一个星期的时间读完了STL红黑树的实现,并凭理解自己写了出来
参考了《算法导论》,教你透彻了解红黑树,强烈推荐,《STL源码剖析》
记录下自己的理解

RB_TREE

红黑树优点:
与BST相比插入,查找,删除在最坏情况下的复杂度仍为O(lgn),相比BST应用范围更广
STL容器set,map都以红黑树为底层容器实现,Linux进程调度算法也用红黑树实现

四种性质:
1. 每个结点要么是红的,要么是黑
2. 根结点是黑的
3. 如果节点为红,子节点必为黑
4. 任一节点至NULL的任何路径,所含黑节点数必定相同

视NULL节点为黑
举个例子,此树为红黑树

这里写图片描述

1. 旋转

红黑树有两种旋转操作,左旋和右旋,都是为了使树保持平衡

1.1左旋

左旋将支点右子树提上去,取代支点位置,支点右子节点的左子树接到支点右子树上
相当于把左边拉高,右边降低
画图表示如下(懒用VISIO画图,手绘…)

这里写图片描述

左旋代码:

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rotate_left(link_type x) {
    //x为旋转支点,将x右节点左旋,y顶替x
    link_type y = x->right;
    x->right = y->left;

    //设父节点
    if (y->left != nullptr)
        y->left->parent = x;
    y->parent = x->parent;

    if (x == root())
        root() = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else
        x->parent->right = y;

    //父子颠倒
    y->left = x;
    x->parent = y;
}
2.2 右旋

画图示意

这里写图片描述

右旋代码:

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rotate_right(link_type x) {
   //x为旋转支点,将x左节点右旋,y顶替x
    link_type y = x->left;
    x->left = y->right;

    if (y->right != nullptr)
        y->right->parent = x;
    y->parent = x->parent;

    if (x == root())
        root() = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else
        x->parent->right = y;

    //父子颠倒
    y->right = x;
    x->parent = y;
}

2. 插入

与二叉搜索树相同,x比当前节点小往左走,比当前节点大往右走
因为不能破坏性质4,插入节点的颜色肯定要是红色,如果它的父节点是黑色没问题,但是如果它的父节点也是红色,那就破坏了性质3,需要修复节点

插入后导致不平衡的三种情况:
1. 父节点为红,叔叔节点为红
2. 父节点为红,叔叔节点为黑,当前节点为父节点右子
3. 父节点为红,叔叔节点为黑,当前节点为父节点左子

对应解法:
以父节点为左子说明,父节点为右子时类似

  1. 父节点和叔叔节点涂黑,祖父节点涂红,当前节点移到祖父节点
  2. 父节点为当前节点,以当前节点为支点左旋(此后导致情况3)
  3. 父节点涂红,祖父节点涂黑,以祖父节点为支点右旋

画过程图如下:
case1
这里写图片描述
4为插入的新节点

经过解法1后变为
这里写图片描述
此时为情况2

经过解法2后变为
这里写图片描述
此时为情况3

经过解法3后变为
这里写图片描述
至此达到平衡

插入修复代码:

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rebalance_for_insert(link_type x) {
    x->color = rb_tree_red;
    while (x != root() && x->parent->color == rb_tree_red) {
        //父节点为左子节点
        if (x->parent == x->parent->parent->left) {
            link_type uncle = x->parent->parent->right;
            if (uncle != nullptr && uncle->color == rb_tree_red) {
                //case1
                x->parent->color = rb_tree_black;
                uncle->color = rb_tree_black;
                x->parent->parent->color = rb_tree_red;
                x = x->parent->parent;
            }
            else {
                //case2
                if (x == x->parent->right) {
                    x = x->parent;
                    rb_tree_rotate_left(x);
                }
                //case3
                x->parent->color = rb_tree_black;
                x->parent->parent->color = rb_tree_red;
                rb_tree_rotate_right(x->parent->parent);
            }
        }
        else {  //父节点为右子
            //case1
            link_type uncle = x->parent->parent->left;
            if (uncle != nullptr && uncle->color == rb_tree_red) {
                uncle->color = rb_tree_black;
                x->parent->color = rb_tree_black;
                x->parent->parent->color = rb_tree_red;
                x = x->parent->parent;
            }
            else {
                //case2
                if (x == x->parent->left) {
                    x = x->parent;
                    rb_tree_rotate_right(x);
                }
                //case3
                x->parent->color = rb_tree_black;
                x->parent->parent->color = rb_tree_red;
                rb_tree_rotate(x->parent->parent);
            }
        }
    }
    root()->color = rb_tree_black;
}

3. 删除

与二叉搜索树相同,从红黑树中删除一个节点用左子树的最右节点或者右子树的最左节点来替换它,并且保持删除点的颜色不变。当替换点为红色时没有问题,但是当替换点为黑色时,则所有包含之前替换点的路径少了一个黑节点,破坏性质4导致了不平衡

这里写图片描述

接下来的讨论都是基于用左子树的最右子节点替换,并且替换点的左子节点为初始的当前节点x

若当前节点为红,直接涂黑即可
删除后导致不平衡的四种情况:
1. 当前节点为黑,兄弟节点为红
2. 当前节点为黑,兄弟节点为黑,兄弟节点左右子都为黑
3. 当前节点为黑,兄弟节点为黑,兄弟节点右子为红,左子为黑
4. 当前节点为黑,兄弟节点为黑,兄弟节点左子为红,右子为任意色

解法:
1. 兄弟节点涂黑,父节点涂红,以父节点为支点右旋(此后导致情况2或3或4)
2. 兄弟节点涂红,当前节点父节点为当前节点
3. 兄弟节点涂红,右子涂黑,以兄弟节点为支点左旋
4. 兄弟节点涂为父节点颜色,父节点和兄弟节点左子涂黑,以父节点为支点右旋,结束

画图说明:

这里写图片描述

解法的所有目的都是为了把当前节点父节点之下的路径的黑节点数调到平衡(举个例
子,若当前节点为右子树,因为之前删除的当前节点的父节点是黑色的,则现在当
前节点的父节点往右所有的路径上黑色节点的个数减少了1,我们通过变换把当前
节点父节点左子树上所有的黑色节点也减少1,则包含当前节点父节点的所有路径
黑节点数少1, 把当前节点移到当前节点父节点,如果为红或者为根,则把它涂黑
结束,如果为黑则继续这些步骤)

如果用右子树的最右子节点替换,我也画了如下变化图

这里写图片描述

删除修复代码:

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
void rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::rb_tree_rebalance_for_erase(link_type x, link_type parent_of_x) {
    while (x != root() && (x == nullptr || x->color == rb_tree_black)) {
        //当前节点为右子树
        if (x == parent_of_x->right) {
            link_type silbing = parent_of_x->left;
            // case1
            if (silbing->color == rb_tree_red) {
                silbing->color = rb_tree_black;
                parent_of_x->color = rb_tree_red;
                rb_tree_rotate_right(parent_of_x);
                silbing = parent_of_x->left;
            }

            // case2
            if ((silbing->left == nullptr || silbing->left->color == rb_tree_black) && 
                (silbing->right == nullptr || silbing->right->color == rb_tree_black)) {
                silbing->color = rb_tree_red;
                x = parent_of_x;
                parent_of_x = parent_of_x->parent;
            }
            else {
                //case3
                if (silbing->left == nullptr ||
                    silbing->left->color == rb_tree_black) {
                    //if (silbing->right != nullptr)
                    silbing->right->color = rb_tree_black;
                    silbing->color = rb_tree_red;
                    rb_tree_rotate_left(silbing);
                    silbing = parent_of_x->left;
                }
                //case4
                silbing->color = parent_of_x->color;
                parent_of_x->color = rb_tree_black;
                silbing->left->color = rb_tree_black;
                rb_tree_rotate_right(parent_of_x);
                break;
            }
        }
        else {      //当前节点为左子树
            link_type silbing = parent_of_x->right;
            // case1
            if (silbing->color == rb_tree_red) {
                silbing->color = rb_tree_black;
                parent_of_x->color = rb_tree_red;
                rb_tree_rotate_left(parent_of_x);
                silbing = parent_of_x->right;
            }

            // case2
            if ((silbing->left == nullptr || silbing->left->color == rb_tree_black) &&
                (silbing->right == nullptr || silbing->right->color == rb_tree_black)) {
                silbing->color = rb_tree_red;
                x = parent_of_x;
                parent_of_x = parent_of_x->parent;
            }
            else {
                // case3
                if (silbing->right == nullptr ||
                    silbing->right->color == rb_tree_black) {
                    silbing->color = rb_tree_red;
                    silbing->left->color = rb_tree_red;
                    rb_tree_rotate_right(silbing);
                    silbing = parent_of_x->right;
                }
                // case4
                silbing->color = parent_of_x->color;
                parent_of_x->color = rb_tree_black;
                silbing->right->color = rb_tree_black;
                rb_tree_rotate_left(parent_of_x);
                break;
            }
        }
    }

    if (x != nullptr)
        x->color = rb_tree_black;
}

4. 小结

学透红黑树花了不少时间,深感人类的伟大,实现技巧的巧妙,很多只是看懂了这么做,但是他为什么这么做,他是怎么想出来的就想不透了。世界需要这种具有开创性的人,推动社会的发展,推动人类文明的进步,真是深感佩服!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值