《STL源码剖析》笔记-RB-tree

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/WizardtoH/article/details/82818236

上一篇:《STL源码剖析》笔记-树的介绍

除了上一篇说到的AVL-tree之外,另一个常用的平衡二叉搜索树是RB-tree(红黑树)。RB-tree满足二叉搜索树的规则之外,还遵循以下特性:

  • 每个节点不是红色就是黑色。
  • 根节点为黑色。
  • 如果节点为红色,其子节点必须为黑色。
  • 任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同。

在这里插入图片描述

第四个特性和第三个特性确保了没有一条路径会比其他路径长出俩倍。因而,红黑树是相对是接近平衡的二叉树。和AVL树相比,红黑树用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决;而AVL是严格平衡树,因此在增加或者删除节点的时候,,旋转的次数可能会比红黑树要多,所以红黑树的插入/删除效率更高。但是,由于红黑树不是严格平衡,那么搜索的效率会低于AVL树
总结来说,红黑树读取比AVL树差(但是不会差很多,因为有红黑条件限制了2倍平衡),维护强于AVL树,空间消耗与AVL树接近,因此综合来说红黑树性能较好。

插入节点

根据红黑树的规则可以得出插入节点的要求:特性4限制了插入新节点必须是红色节点;如果插入节点的父节点是红色,那么破坏了特性3。,那么需要通过重新着色或旋转,来维持性质; 如果插入节点的父节点是黑色,满足规则。
按照以上的分析,可以得出4种插入的情况。

一、父节点为黑色,外侧插入。

不存在伯父节点需要旋转保持平衡。
在这里插入图片描述

二、父节点为黑色,内侧插入。
不存在伯父节点需要旋转保持平衡。
在这里插入图片描述

三、父节点为红色,外侧插入,祖父节点的父节点为黑色。

需要对父节点和祖父节点做一次单旋转(SGI STL源码中并没有进行旋转,只改变了颜色,实际上改变颜色即可,因为有伯父节点时不存在不平衡),同时进行一次颜色变换。如果此时祖父节点的父节点为黑色(特性3,红色节点的子节点必须为黑色),那么插入完成,否则按照第四种情况进行处理。
在这里插入图片描述

四、父节点为红色,外侧插入,祖父节点的父节点为红色,需要从祖父节点开始继续向上调整,直到调整过的祖父节点的父节点为黑色。

五、对第四种情况的优化(由原作者提供)
在这里插入图片描述

改变颜色后,如果X的父节点P也是红色,那么做一次单旋转或者双旋转,同时改变颜色。在此之后,插入节点就很简单了:直接插入,或者插入后做一次单旋转。

在这里插入图片描述

RB-tree节点设计

SGI STL的树节点,设计了两层结构:__rb_tree_node_base为树的基本成员,__rb_tree_node继承了__rb_tree_node_base并提供节点的值。

typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false;
const __rb_tree_color_type __rb_tree_black = true;

struct __rb_tree_node_base
{
    typedef __rb_tree_color_type color_type;
    typedef __rb_tree_node_base* base_ptr;
    
    color_type color;
    base_ptr parent;
    base_ptr left;
    base_ptr right;
    
    // 最左边为最小节点
    static base_ptr minimum(base_ptr x)
    {
        while (x->left != 0) x = x->left;
        return x;
    }
    
    // 最右边为最大节点
    static base_ptr maximum(base_ptr x)
    {
        while (x->right != 0) x = x->right;
        return x;
    }
};

// 继承了__rb_tree_node_base
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base
{
  typedef __rb_tree_node<Value>* link_type;
  Value value_field;
};

RB-tree迭代器

迭代器同样适用了双层设计,节点和迭代器的双层设计能够保证在功能实现时的便捷。因为base层只包含了红黑树本身的抽象功能,继承模板层才拥有其他功能的封装,这样一来针对红黑树本身的操作就不需要使用到上层的功能。

struct __rb_tree_base_iterator
{
    typedef __rb_tree_node_base::base_ptr base_ptr;
    typedef bidirectional_iterator_tag iterator_category;
    typedef ptrdiff_t difference_type;
    base_ptr node;    // 关联到节点
    
    // 自增
    void increment()
    {
        if (node->right != 0) {   // 有右节点,一直向右节点的左子节点移动,直到最后得到自增结果
            node = node->right;
            while (node->left != 0)
                node = node->left;
        }
        else {    // 没有右节点
            base_ptr y = node->parent;

			// 该节点是父节点的右子节点
			// 一直上溯右节点,直到node不是右子节点
            while (node == y->right) {
                node = y;
                y = y->parent;
            }
            
            // 上溯完成后,上溯后节点是父节点的左子节点,那么父节点就是自增结果
            if (node->right != y)
                node = y;
                
           // 状况4,当前迭代器为根节点,且无右节点时,根节点本身为自增结果(依赖于hrader节点的特殊实现)
        }
    }
    
    // 自减
    void decrement()
    {
        if (node->color == __rb_tree_red && node->parent->parent == node)
            // 状况1,节点为红色,且父节点的父节点为自己,自减结果为右子节点
            // 此情况发生于对end()迭代器自减,依赖于header节点的特殊实现,header节点的右子节点是整颗树的最大节点
            node = node->right;
        else if (node->left != 0) {  // 有左子节点,找到左子树中的最大值
            base_ptr y = node->left;
            while (y->right != 0)
                y = y->right;
            node = y;
        }
        else {   // 不是根节点,又没有左子节点,一直上溯左节点,直到node不是左子节点,得到自减结果
            base_ptr y = node->parent;
            while (node == y->left) {
                node = y;
                y = y->parent;
            }
            node = y;
        }
    }
};

// 继承了__rb_tree_base_iterator
template <class Value, class Ref, class Ptr>
struct __rb_tree_iterator : public __rb_tree_base_iterator
{
    typedef Value value_type;
    typedef Ref reference;
    typedef Ptr pointer;
    typedef __rb_tree_iterator<Value, Value&, Value*>             iterator;
    typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;
    typedef __rb_tree_iterator<Value, Ref, Ptr>                   self;
    typedef __rb_tree_node<Value>* link_type;
    
    __rb_tree_iterator() {}
    __rb_tree_iterator(link_type x) { node = x; }
    __rb_tree_iterator(const iterator& it) { node = it.node; }
    
    reference operator*() const { return link_type(node)->value_field; }
#ifndef __SGI_STL_NO_ARROW_OPERATOR
    pointer operator->() const { return &(operator*()); }
#endif /* __SGI_STL_NO_ARROW_OPERATOR */
    
    self& operator++() { increment(); return *this; }
    self operator++(int) {
        self tmp = *this;
        increment();
        return tmp;
    }
    
    self& operator--() { decrement(); return *this; }
    self operator--(int) {
        self tmp = *this;
        decrement();
        return tmp;
    }
};

在这里插入图片描述

RB-tree数据结构

以下为RB-tree的定义:

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:
    typedef void* void_pointer;
    typedef __rb_tree_node_base* base_ptr;
    typedef __rb_tree_node<Value> rb_tree_node;
    typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;  // 专属配置器,配置单位为一个节点的空间
    typedef __rb_tree_color_type color_type;
public:
    typedef Key key_type;
    typedef Value value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type& reference;
    typedef const value_type& const_reference;
    typedef rb_tree_node* link_type;
    typedef size_t size_type;
    typedef ptrdiff_t difference_type;
protected:
    link_type get_node() { return rb_tree_node_allocator::allocate(); }
    void put_node(link_type p) { rb_tree_node_allocator::deallocate(p); }

    // 配置空间并且构造
    link_type create_node(const value_type& x) {
        link_type tmp = get_node();
        __STL_TRY{
            construct(&tmp->value_field, x);
        }
        __STL_UNWIND(put_node(tmp));
        return tmp;
    }

    // 复制节点的值和颜色
    link_type clone_node(link_type x) {
        link_type tmp = create_node(x->value_field);
        tmp->color = x->color;
        tmp->left = 0;
        tmp->right = 0;
        return tmp;
    }

    void destroy_node(link_type p) {
        destroy(&p->value_field);
        put_node(p);
    }
public:
    // 迭代器定义
    typedef __rb_tree_iterator<value_type, reference, pointer> iterator;
    typedef __rb_tree_iterator<value_type, const_reference, const_pointer> const_iterator;
private:
    iterator __insert(base_ptr x, base_ptr y, const value_type& v);
    link_type __copy(link_type x, link_type p);
    void __erase(link_type x);

    // 初始化时,生成一个头结点
    void init() {
        header = get_node();
        color(header) = __rb_tree_red;   // 头结点颜色为红色,区分于根节点
        root() = 0;
        leftmost() = header;
        rightmost() = header;
    }
...
}

RB-tree的构造与内存管理

RB-tree的内存管理使用专属的配置器rb_tree_node_allocator,并提供两种构造:

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = alloc>
class rb_tree {
protected:
    typedef __rb_tree_node<Value> rb_tree_node;
    typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator;  // 专属配置器,配置单位为一个节点的空间
public:
    // 默认构造,使用init函数生成一个带header
    rb_tree(const Compare& comp = Compare())
        : node_count(0), key_compare(comp) {
        init();
    }

   // 拷贝另一个树进行构造
    rb_tree(const rb_tree<Key, Value, KeyOfValue, Compare, Alloc>& x)
        : node_count(0), key_compare(x.key_compare)
    {
        header = get_node();
        color(header) = __rb_tree_red;
        if (x.root() == 0) {
            root() = 0;
            leftmost() = header;
            rightmost() = header;
        }
        else {
            __STL_TRY{
                root() = __copy(x.root(), header);
            }
            __STL_UNWIND(put_node(header));
            leftmost() = minimum(root());
            rightmost() = maximum(root());
        }
        node_count = x.node_count;
    }
    ~rb_tree() {
        clear();
        put_node(header);
    }
}
...

init会产生一个header节点,其父节点为根节点,左子节点为最小节点,右子节点为最大节点,每当插入新节点时需要维护header节点的正确性。

RB-tree的元素操作

一、元素插入insert_equal
insert_equal允许插入键值一致的节点,返回值是指向新增节点的迭代器。

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_equal(const Value& v)
{
    link_type y = header;
    link_type x = root();

    // 比较key值大小,小向左,大向右
    while (x != 0) {
        y = x;
        x = key_compare(KeyOfValue()(v), key(x)) ? left(x) : right(x);
    }
    // x为新值插入点,y为父节点,v是插入值
    return __insert(x, y, v);
}

二、元素插入insert_unique
insert_unique不允许插入键值重复的节点,返回值是pair值,包括是否插入成功以及插入成功后的新增节点迭代器。

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
pair<typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator, bool>
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::insert_unique(const Value& v)
{
    link_type y = header;
    link_type x = root();
    bool comp = true;
    // 比较key值大小,小向左,大向右
    while (x != 0) {
        y = x;
        comp = key_compare(KeyOfValue()(v), key(x));
        x = comp ? left(x) : right(x);
    }
    
    iterator j = iterator(y);   // 父节点迭代器
    if (comp)                     // comp为true,表示插入节点较小,插入左侧
        if (j == begin())       // 如果父节点是最左节点,x为新值插入点,y为父节点,v是插入值
            return pair<iterator,bool>(__insert(x, y, v), true);
        else  // 如果父节点不是最左节点,将迭代器向前移动
            --j;
            
     // 比较向前移动后的节点和插入节点的键值大小,新值较大则插入右侧
    if (key_compare(key(j.node), KeyOfValue()(v)))
        return pair<iterator,bool>(__insert(x, y, v), true);
        
    //  否则,不大不小就是重复,那么返回重复的节点并告知失败
    return pair<iterator,bool>(j, false);
}

三、插入操作__insert
以下为真正的执行插入节点操作。

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::
__insert(base_ptr x_, base_ptr y_, const Value& v) {
    link_type x = (link_type) x_;
    link_type y = (link_type) y_;
    link_type z;
    
    // 当插入节点为头结点或插入键值小于父节点时
    // x!=0,插入点不为NULL为特殊情况在insert_unique的另一个版本中使用
    if (y == header || x != 0 || key_compare(KeyOfValue()(v), key(y))) {
        z = create_node(v);
        left(y) = z;        // 插入父节点的左侧,当父节点为header时等价于leftmost() = z; 
        if (y == header) {
            root() = z;
            rightmost() = z;
        }
        else if (y == leftmost())
            leftmost() = z;           // 父节点为最小值,直接插入左侧
    }
    else {  // 插入键值大于父节点的情况
        z = create_node(v);
        right(y) = z;
        if (y == rightmost())
            rightmost() = z;            // 父节点为最大值,直接插入右侧
    }

    // 为插入节点设置父节点等
    parent(z) = y;
    left(z) = 0;
    right(z) = 0;

	// 进行平衡(改变颜色,进行旋转)
    __rb_tree_rebalance(z, header->parent);
    ++node_count;
    return iterator(z);
}

三、调整树 __rb_tree_rebalance

inline void
__rb_tree_rebalance(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
    // 因为规则:任意一个节点到到NULL(树尾端)的任何路径,所含之黑色节点数必须相同
    // 所以插入节点默认为红色,保证这条规则必然符合
    x->color = __rb_tree_red;  

    // 父节点颜色为红,且当前节点不为根节点(违反规则:如果节点为红色,其子节点必须为黑色)
    while (x != root && x->parent->color == __rb_tree_red) {
        if (x->parent == x->parent->parent->left) {
            // 父节点是祖父节点的左子节点
            __rb_tree_node_base* y = x->parent->parent->right;
            if (y && y->color == __rb_tree_red) {   
                // 伯父节点存在且为红色(伯父节点必然和父节点同色)
                // 因为规则:如果节点为红色,其子节点必须为黑色,插入点位红色不符合要求
                // 所以此时需要改变父节点和伯父节点的颜色为黑,祖父节点为红
                x->parent->color = __rb_tree_black;
                y->color = __rb_tree_black;
                x->parent->parent->color = __rb_tree_red;
                
                // 同时以祖父节点为调整起点继续调整
                // 如果祖父节点的父节点为黑色按照循环条件就调整结束
                // 否则继续调整
                x = x->parent->parent;
                // 注意:上文中介绍插入情况时,有伯父节点还需要进行一次旋转,实际代码中并没有
            }
            else {
                // 伯父节点不存在,需要进行旋转保持平衡
                if (x == x->parent->right) {
                   // 插入节点为右节点,内侧插入,需要先左旋再右旋
                   // 以父节点为旋转点进行左旋
                    x = x->parent;
                    __rb_tree_rotate_left(x, root);
                }
                
                // 使父节点颜色为黑,祖父节点颜色为红,这样旋转之后颜色和深度都能保证平衡
                x->parent->color = __rb_tree_black;
                x->parent->parent->color = __rb_tree_red;
                
               // 以祖父节点为旋转点进行右旋
                __rb_tree_rotate_right(x->parent->parent, root);
            }
        }
        else {
           // 父节点是祖父节点的右节点,和以上的情况对称
            __rb_tree_node_base* y = x->parent->parent->left;
            if (y && y->color == __rb_tree_red) {
                x->parent->color = __rb_tree_black;
                y->color = __rb_tree_black;
                x->parent->parent->color = __rb_tree_red;
                x = x->parent->parent;
            }
            else {
                if (x == x->parent->left) {
                    x = x->parent;
                    __rb_tree_rotate_right(x, root);
                }
                x->parent->color = __rb_tree_black;
                x->parent->parent->color = __rb_tree_red;
                __rb_tree_rotate_left(x->parent->parent, root);
            }
        }
    }

    // 按照规则根节点始终为黑
    root->color = __rb_tree_black;
}

// 左旋转
inline void
__rb_tree_rotate_left(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
    __rb_tree_node_base* y = x->right;  // 旋转点的右子节点
    // 1、将y的左子节点设置成x的右子节点
    x->right = y->left; 
    if (y->left != 0)
        y->left->parent = x;
    // 2、将y的父节点设置成x的父节点
    y->parent = x->parent;

    // 3、将y替换到x的位置
    if (x == root)
        root = y;
    else if (x == x->parent->left)
        x->parent->left = y;
    else
        x->parent->right = y;
  
    // 4、将x设置成y的左子节点
    y->left = x;
    x->parent = y;
}

// 右旋转
inline void
__rb_tree_rotate_right(__rb_tree_node_base* x, __rb_tree_node_base*& root)
{
    __rb_tree_node_base* y = x->left;  // 选装点的左子节点
    // 1、将y的右子节点设置成x的左子节点
    x->left = y->right;
    if (y->right != 0)
        y->right->parent = x;
    // 2、将y的父节点设置成x的父节点
    y->parent = x->parent;

    // 3、将y替换到x的位置
    if (x == root)
        root = y;
    else if (x == x->parent->right)
        x->parent->right = y;
    else
        x->parent->left = y;

    // 4、将x设置成y的右子节点
    y->right = x;
    x->parent = y;
}

元素查找

查找函数提供两个版本,其中一个返回const迭代器。

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::find(const Key& k) {
    link_type y = header; 
    link_type x = root(); 

    // 节点不为空
    while (x != 0)
        // 比较查找值和当前值的大小,大向右,小或等于向左
        // y用来保存x节点,最后返回结果,因为小于是向左移动,找到之后会一直向右直到叶子节点为空
        if (!key_compare(key(x), k))
            y = x, x = left(x);                    
        else
            x = right(x); 
            
    // 获取查找结果y的迭代器
    // 判断y是否为end()也就是header节点,此时返回end()
    // 判断k是否大于j的值,如果是说明没有找到,返回end()
    iterator j = iterator(y);
    return (j == end() || key_compare(k, key(j.node))) ? end() : j;
}

template <class Key, class Value, class KeyOfValue, class Compare, class Alloc>
typename rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::const_iterator
rb_tree<Key, Value, KeyOfValue, Compare, Alloc>::find(const Key& k) const {
    link_type y = header; 
    link_type x = root();

    while (x != 0) {
        if (!key_compare(key(x), k))
            y = x, x = left(x);
        else
            x = right(x);
    }
    const_iterator j = const_iterator(y);
    return (j == end() || key_compare(k, key(j.node))) ? end() : j;
}

下一篇:《STL源码剖析》笔记-set

猜你喜欢

转载自blog.csdn.net/WizardtoH/article/details/82818236