STL关联容器-红黑树

关联式容器分为set(集合)和map(映射)两大类,以及这两大类衍生体multiset(多键集合)和multimap(多键映射)。这些容器的底层机制均以RB-tree(红黑树)完成。RB-tree是一个独立的容器,并不开放给外界使用。
此外SGI STL还提供了一个不再标准规格之列的关联式容器:hash table(散列表),以及以此hash table为底层机制而完成的hash_set(散列集合)hash_map(散列映射表),hash_multiset(散列多键集合),hash_multimap(散列多键映射表)。hash table(散列表)及其衍生容器相当重要,它们未被纳入C++标准的原因是,提案太迟了。
一般而言,关联式容器(map, multimap)内部结构是一个 balanced binary tree(平衡二叉树),以便获得良好的搜寻效率,其中包括:AVL-tree(AVL树),RB-tree(红黑树),AA-tree,其中最被广泛运用于STL的是RB-tree。

《STL关联容器-红黑树》

本文简要的分析rb_tree在SGI STL中的实现,主要包括:
  1. rb-tree节点设计

  2. rb-tree迭代器设计

  3. rb_tree 的构造以及内存管理

  4. rb_tree 元素的操作

1. rb-tree节点设计

为了有更大的弹性,节点设计分为两层:

//节点颜色类型,非红即黑
typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red   = false;
const __rb_tree_color_type __rb_tree_black = true;

下面的base仅仅有指针,没有实值()

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;      //指向右孩子节点 

    //RB-Tree最小节点,即最左节点
    static base_ptr minimum(base_ptr x)
    {

        while (x->left != 0) x = x->left;
        return x;
    }

    //RB-Tree最大节点,即最右节点
    static base_ptr maximum(base_ptr x)
    {
        //一直往RB-Tree的右边走,最右的节点即是最大节点
        //这是二叉查找树的性质
        while (x->right != 0) x = x->right;
        return x;
    }

};
template<typename Value>
struct __rb_tree_node:public __rb_tree_node_base{
    typedef __rb_tree_node<Value>* link_type;  // 节点指针,指向数据节点
    Value value_field;  // 节点数据域,即关键字
};

下面是RB-tree的节点图标,其中将__rb_tree_node::value_field填为10:

《STL关联容器-红黑树》

2. rb-tree迭代器设计

RB-tree实现一个泛型容器,其迭代器设计是关键。主要考虑迭代器的类别,前进,后退,提领,成员访问等操作。它的迭代器设计也是双层结构。使用双向迭代器,但不具有随机定位能力。其前进和后退操作,利用基层迭代器的前进和后退操作实现。下面是对这部分源码的分析。

《STL关联容器-红黑树》

注意:基类iterator里面并没有value, 只有指针。

struct __rb_tree_base_iterator{
    typedef __rb_tree_node_base::base_ptr base_ptr;
    typedef ptrdiff_t difference_type;

    base_ptr node;  //节点指针,连接容器

    /* 重载运算符++和--;目的是找到前驱和后继节点. */
    //下面只是为了实现oprerator++的,其他地方不会调用.
    //RB-Tree的后继节点
    void increment();

    //RB-Tree的前驱节点
    void decrement();
};

上面的increment, decrement找的是二叉树节点的前驱(比当前值小的元素)和后继(比当前值大的元素):如下图,节点25的前驱是19,后继节点是29

《STL关联容器-红黑树》

下面看increment函数(不用全部理解,只用知道,例如节点如果有右节点,那么右节点的最左的孩子节点就是后继节点)

void __rb_tree_base_iterator::increment()
{
    //【情况1】:存在右子树,则找出右子树的最小节点
    if (node->right != 0) {//如果有右子树
        node = node->right;//向右边走
        while (node->left != 0)//往右子树中的左边一直走到底
            node = node->left;//最左节点就是后继结点
    }
        //没有右子树,但是RB-Tree中节点node存在后继结点
    else {
        base_ptr __y = node->parent;//沿其父节点上溯
        while (node == __y->right) { //【情况2】:若节点是其父节点的右孩子,则上溯
            node = __y;                  //一直上溯,直到“某节点不是其父节点的右孩子”为止
            __y = __y->parent;
        }
        if (node->right != __y)//【情况3】:若此时的右子节点不等于此时的父节点
            node = __y;//此时的父节点即为解答
    }
}

decrement函数同理,故而先省略。

继承类iterator,也只是包含指针,并没有节点value(不过也是迭代器本质就是指针,里面也不应该有对应的value)

//RB-Tree的迭代器iterator结构
//继承基类迭代器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; }

    pointer operator->() const { return &(operator*()); }
};

接下来看下operator++,operator–主要是调用__rb_tree_base_iterator的increment, decrement:

//前缀operator++找出后继节点,调用基类的 increment()
self& operator++() { increment(); return *this; }
//后缀operator++找出后继节点,调用基类的 increment()
self operator++(int) {
    self tmp = *this;
    increment();
    return tmp;
}

//前缀operator--找出前驱节点,调用基类的decrement()
self& operator--() { decrement(); return *this; }
//后缀operator--找出前驱节点,调用基类的decrement()
self operator--(int) {
    self tmp = *this;
    decrement();
    return tmp;
}

3. rb_tree 的构造以及内存管理

re_tree的定义如下(下面做了一定的简化不影响理解,具体的见set,map那一节):

template<typename Value, typename Compare>
class rb_tree{
    protected:
        typedef typename __rb_tree_node_base* base_ptr;
        typedef __rb_tree_node<Value> rb_tree_node;
        // 空间配置器,一次分配一个节点
        typedef allocator<rb_tree_node> rb_tree_node_allocator;

        typedef __rb_tree_color_type color_type;

    public:
        typedef size_t size_type;
        typedef ptrdiff_t difference_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 _rb_tree_iterator<Value, Value&, Value*> iterator;

    protected:
        //一下三个成员,表现rb_tree
        size_type node_count; // 记录节点数量
        link_type header;     // 使用上的一个技巧
        Compare key_compare;  // 键值大小比较准则
}

注意rb_tree的根节点的设计,见下图的初始化以及最右边的结果:(header和root互为对方的父节点,这是一种实现技巧)

《STL关联容器-红黑树》

首先看下最简单的初始化,构造函数:

template<typename Value, typename Compare>
rb_tree<Value, Compare>::rb_tree(const Compare& comp = Compare()){
    node_count = 0;    //节点个数为0
    key_compare(comp); //比较函数
    init();
}

接下来看init函数:
1. 节点的分配以及构造:

// 分配一个新结点(分配内存), 注意这里并不进行构造,
link_type alloc_node(){
    return rb_tree_node_allocator::allocate();
}
// 产生(配置并构造)一个节点, 首先分配内存, 然后进行构造
link_type alloc_ctor_node(const value_type& value){
    link_type tmp = alloc_node(); // 配置空间
    construct(tmp, value);        // 构造节点
    return tmp;
}
  1. 常用节点的返回(返回的是引用,const限定符函数):
link_type& root()const{
    return (link_type&)header->parent;
}

link_type& leftmost()const{
    return (link_type&)header->left;
}

link_type& rightmost()const{
    return (link_type&)header->right;
}

template<typename Value, typename Compare>
void rb_tree<Value, Compare>::init(){
    header = alloc_node(); // 申请一个节点(header无需构造)
    //以下几个函数返回的是引用
    color(header) = __rb_tree_red; // 令header红色,区分root
    root() = 0;
    leftmost() = header;
    rightmost() = header;
}

4. rb_tree 元素的操作

re_tree的核心函数如下(不给出具体的定义):

// 插入新的节点,节点键值允许重复
iterator insert_equal(const Value& v);
// 插入新的节点,节点键值不允许重复,重复则无效
iterator insert_unique(const Value& v);

// 节点查找,比STL算法find高效
iterator find(const Value& v);

private:
// 真正执行,插入新的节点
iterator _insert(base_ptr parent, base_ptr new_p, const Value& v);

// 旋转保持树平衡
void __rb_tree_rebalance(__rb_tree_node_base* x,
                         __rb_tree_node_base*& root);

// 左旋转
void __rb_tree_rotate_left(__rb_tree_node_base* x,
                            __rb_tree_node_base*& root);

// 右旋转
void __rb_tree_rotate_right(__rb_tree_node_base* x,
                            __rb_tree_node_base*& root);
    原文作者:算法小白
    原文地址: https://blog.csdn.net/haluoluo211/article/details/80877064
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞