关联容器(底层机制) — 红黑树

set、map、multiset、multimap四种关联式容器的内部都是由红黑树实现的。在STL中红黑树是一个不给外界使用的独立容器。既然是容器,那么就会分配内存空间(节点),内部也会存在迭代器。关于红黑树的一些性质,可以参考“数据结构”中的笔记,这里只记录STL中的红黑树是如何实现的。

和slist一样,红黑树的节点和迭代器均采用了双层结构:

  • 节点:__rb_tree_node继承自__rb_tree_node_base
  • 迭代器:__rb_tree_iterator继承自__rb_tree_base_iterator

先看看颜色标识:

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;  // RB树的许多操作必须知道父节点

  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;

  }

};

基础节点定义了一些类型和指针,两个操作为求最小、最大值。

上层节点:

template <class Value>

struct __rb_tree_node : public __rb_tree_node_base

{

  typedef __rb_tree_node<Value>* link_type;

  Value value_field;  // 节点值

};

上层节点很简单,只包含一个指针类型和存放节点值的变量。

基础迭代器:

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()  // 迭代器++时使用

  {

       ....

  }

  void decrement()  // 迭代器--时使用

  {

       ....

  }

};

基础迭代器中注意node成员,然后是两个专供operator++和operator--的内部方法,它们根据红黑树特性使node指向后一个或前一个节点,因为红黑树是一个二叉搜索树,找出键值相邻的节点是有规律可循的。所以,红黑树的迭代器属于双向迭代器。

上层迭代器:

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; } // 初始化node

  __rb_tree_iterator(const iterator& it) { node = it.node; }

  reference operator*() const return link_type(node)->value_field; }  // 解引用,注意link_type类型转换

  pointer operator->() const return &(operator*()); }    // 箭头操作符

  self& operator++() { increment(); return *this; }

  self operator++(int) {

      ....

  }

    

  self& operator--() { decrement(); return *this; }

  self operator--(int) {

      ....

  }

};

上层迭代器就是红黑树类中的迭代器,对它的++或--操作最终都是调用了基础迭代器中的increment()或decrement()。

下面记录一下红黑树的数据结构框架:

template <class Key, class Value, class KeyOfValue, class Compare,

          class Alloc = alloc>

class rb_tree {

  ....

  typedef __rb_tree_node<Value> rb_tree_node;

  typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator; // 空间配置器,一次分配一个节点

  typedef rb_tree_node* link_type;

  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();

    construct(&tmp->value_field, x);

    return tmp;

  }

  ....

  void destroy_node(link_type p) {  // 析构并释放一个节点

    destroy(&p->value_field);

    put_node(p);

  }

protected:

  size_type node_count; // 记录节点数量

  link_type header;     // 使用上的一个技巧

  Compare key_compare;  // 键值大小比较准则

  ....

  static link_type& left(link_type x) { return (link_type&)(x->left); }     // 左儿子

  static link_type& right(link_type x) { return (link_type&)(x->right); }   // 右儿子

  

  ....

  static link_type minimum(link_type x) { 

    return (link_type)  __rb_tree_node_base::minimum(x);  // 调用底层节点的函数获得键值最小的节点

  }

  static link_type maximum(link_type x) {

    return (link_type) __rb_tree_node_base::maximum(x);  // 调用底层节点的函数获得键值最大的节点

  }

  ....

public:

  pair<iterator,bool> insert_unique(const value_type& x);  // 节点键值独一无二

  iterator insert_equal(const value_type& x);            

// 节点键值可重复性

  void erase(iterator position);                           // 删除节点

  ....

public:

  // 以下函数在multimap和multiset中使用

  iterator find(const key_type& x);

  size_type count(const key_type& x) const;

  iterator lower_bound(const key_type& x);

  iterator upper_bound(const key_type& x);

  pair<iterator,iterator> equal_range(const key_type& x);

}

注意上面代码的红色部分使用了一个技巧来处理节点为root时的边界情况,它使用了一个header指针。先看看初始化header的函数:

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; }

void init() {   // 初始化header

    header = get_node();

    color(header) = __rb_tree_red; // used to distinguish header from 

                                   // root, in iterator.operator--

    root() = 0;   // header->parent = null

    leftmost() = header;

    rightmost() = header;

  }

把header指向节点的颜色设置为红色(为了区别根节点的黑色),注意这不是根节点,只是类似一个哨兵节点。header的parent始终指向root(这里初始化为null);left始终指向最小节点;right始终指向最大节点。header记录了这些信息之后,容器的begin()、end()就很好求了:

iterator begin() { return leftmost(); }   // header->left

iterator end() { return header; }       

// 返回header,调用__rb_tree_iterator构造函数由指针转迭代器

和其它容器一样,end()同样是返回最后一个元素(最大节点)的下一个节点。

参考:

《STL源码剖析》 P213.

关联容器(底层机制) — 红黑树,布布扣,bubuko.com

时间: 2024-08-24 18:59:38

关联容器(底层机制) — 红黑树的相关文章

C++ STL容器底层机制

1.vector容器 vector的数据安排以及操作方式,与array非常相似.两者的唯一区别在于空间的运用的灵活性.array是静态空间,一旦配置了就不能改变.vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素.因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块的array. vector动态增加大小,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配

菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t

Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 27h, 2014 1.ngx_rbtree优势和特点 ngx_rbtree是一种使用红黑树实现的关联容器,关于红黑树的特性,在<手把手实现红黑树>已经详细介绍,这里就只探讨ngx_rbtree与众不同的地方:ngx_rbtree红黑树容器中的元素都是有序的,支持快速索引,插入,删除操作,也支持范围查询,遍历操作,应

菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t[转]

菜鸟nginx源码剖析数据结构篇(四)红黑树ngx_rbtree_t Author:Echo Chen(陈斌) Email:[email protected] Blog:Blog.csdn.net/chen19870707 Date:October 27h, 2014 1.ngx_rbtree优势和特点 ngx_rbtree是一种使用红黑树实现的关联容器,关于红黑树的特性,在<手把手实现红黑树>已经详细介绍,这里就只探讨ngx_rbtree与众不同的地方:ngx_rbtree红黑树容器中的元素

STL 笔记(二) 关联容器 map、set、multimap 和 multimap

STL 关联容器简单介绍 关联容器即 key-value 键值对容器,依靠 key 来存储和读取元素. 在 STL 中,有四种关联容器,各自是: map 键值对 key-value 存储,key 不可反复,即一个 key 仅仅能相应一个 value, 相应头文件<map> multimap 键值对 key-value 存储,key 能够反复,即一个 key 能够相应多个 value, 相应头文件<map> set 仅仅有 key, key 不可反复,相应头文件<set>

STL 笔记(二): 关联容器 map、set、multimap 和 multimap

STL 关联容器简介 关联容器即 key-value 键值对容器,依靠 key 来存储和读取元素.在 STL 中,有四种关联容器,分别是: map 键值对 key-value 存储,key 不可重复,即一个 key 只能对应一个 value, 对应头文件<map> multimap 键值对 key-value 存储,key 可以重复,即一个 key 可以对应多个 value, 对应头文件<map> set 只有 key, key 不可重复,对应头文件<set> mult

关联容器 — hash_map

hash_map和map的用法很相似,只是底层机制有所不同. hash_map容器采用的底层机制是hash table代码: template <class Key, class T, class HashFcn = hash<Key>, class EqualKey = equal_to<Key>, class Alloc = alloc> class hash_map { private: typedef hashtable<pair<const Key

0717-----C++Primer听课笔记----------STL之关联容器

1.Map 1.1 map<K, V>是一种pair的容器,pair的种类是pair<K, V>.map采用下标访问一个已存在的key, 会更新value,访问map中不存在的元素时,会增加一个新的键值对.map中的元素按照key进行从小到大排列.map的底层实现是采用二叉树,一般是使用红黑树. #include <iostream> #include <string> #include <map> using namespace std; /*

红黑树并没有我们想象的那么难(下)

SGI STL map 实现概述 根据上一节的红黑树分析, 结合 sgi stl map 的实现, 看看红黑树的源码是如何实现的. 以下主要以代码的注释为主. sgi stl map 底层实现是 _Rb_tree类, 为了方便管理, _Rb_tree 内置了 _M_header, 用于记录红黑树中的根节点, 最小节点和最大节点. 在插入删除中都会对其进行维护. 找到一副美艳的图片: 我只会展开插入和删除的代码. _Rb_tree 有 insert_unique() 和 insert_equal(

史上最清晰的红黑树讲解(上)

http://www.cnblogs.com/CarpenterLee/p/5503882.html 本文以Java TreeMap为例,从源代码层面,结合详细的图解,剥茧抽丝地讲解红黑树(Red-Black tree)的插入,删除以及由此产生的调整过程. 总体介绍 Java TreeMap实现了SortedMap接口,也就是说会按照key的大小顺序对Map中的元素进行排序,key大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Compara

TreeMap红黑树

Java TreeMap实现了SortedMap接口,也就是说会按照key的大小顺序对Map中的元素进行排序,key大小的评判可以通过其本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator). TreeMap底层通过红黑树(Red-Black tree)实现,也就意味着containsKey(), get(), put(), remove()都有着log(n)的时间复杂度.其具体算法实现参照了<算法导论>. 出于性能原因,TreeMap是非同步