STL源码分析之RB-tree结构简析

来源:互联网 发布:宁波seo教程 编辑:程序博客网 时间:2024/05/21 10:40

所谓关联容器:

         每笔数据(每个元素)都有一个键值(key)和一个实值(value)。当元素被插入到关联式容器中时,容器内部结构(可能是RB-Tree 也可能是hash-table)便依照其键值大小,以某种特定规则将这个元素置放于适当的位置。

         关联容器没有所谓的头尾(只有最大元素和最小元素),所以不会有所谓的push_back(), push_front(), pop_back(), pop_front(), begin(), end()这样的行为

 

         一般而言,关联式容器的内部结构是一个平衡二叉树,以便获得良好的搜寻效率。二叉平衡树有许多种类型,包括 AVL-tree,  RB-tree,  AA-tree, 最被广泛用在STL的是RB-tree

路径长度(length):         根节点至任何节点之间有唯一路径,路径所经过的边数深度(depth):         根节点至任一节点的路径长度         根节点的深度永远为0高度(height):         某节点至其最深子节点(叶子节点)的路径长度节点大小(size):         其所有子代的节点总数

 


RB-tree结构设计

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

 

//第一层//可见为了方便,将红黑两色设定为true和falsetypedef 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;  }};

//第二层template <class Value>struct __rb_tree_node : public __rb_tree_node_base{  typedef __rb_tree_node<Value>* link_type;  Value value_field;};


 

RB-tree的迭代器


//第一层迭代器
struct__rb_tree_base_iterator{//在第一层RB-tree结构体中typedef__rb_tree_node_base* base_ptr;  typedef __rb_tree_node_base::base_ptr base_ptr;  typedef bidirectional_iterator_tag iterator_category;  typedef ptrdiff_t difference_type;  base_ptr node; //这个即使找下一个节点的意思,用于operate ++  void increment()  {    if (node->right != 0) {         //右子存在,即找右子中在最左      node = node->right;      while (node->left != 0)        node = node->left;    }    else {                          //右子不存在,即找某个父节点      base_ptr y = node->parent;      while (node == y->right) {        node = y;        y = y->parent;      }        //以上的循环我们可以理解,但是y不是一直是node节点的parent吗?        //普通情况下,node->right是肯定不会等于y的,所以最终node也会成功指向要寻找的节点        //但是,我们这个RB-tree是有个header的        //以下我们会画图理解      if (node->right != y)        node = y;    }  } //用于operate--  void decrement()  {    if (node->color == __rb_tree_red &&         //这种情况可能较难理解,等下看图        node->parent->parent == node)      node = node->right;    else if (node->left != 0) {         //左子存在,即找左子的最右      base_ptr y = node->left;      while (y->right != 0)        y = y->right;      node = y;    }    else {      base_ptr y = node->parent;            //否则找某个父节点      while (node == y->left) {        node = y;        y = y->parent;      }      node = y;                     //成功找到目标节点    }  }};

要理解上面两个函数的两个难点,我们就要看下这个RB-tree的特殊之处导致的特殊情况:

                                                                               

可见,RB-tree是有一个不装数据的header的,左子指向最小,右子指向最大,parent指向root。

且header颜色是红的

难点一:

当我们调用increment函数时候,当前node即为root节点,此时想要寻找root的下一个节点。发现root节点并没有右子,只能向上寻找。想要找到以子节点是父节点左子节点的父节点作为返回值。因为此时特殊情况header的右子指向root,因此header作为当前节点继续往上寻找。因为header的parent为root,此时root的右子并不等于header,循环结束。此时出现的特殊情况是header也不是root的左子,反倒是node->right != y,即header->right=root,这种情况下node也不再需要等于y,因为我们的目标返回节点就是header

 

难点二:

当我们调用decrement函数时候,当前node为header(或者说是end()),该节点的parent的parent即是本身,我们要求返回的就是根节点。否则按照正常的逻辑来的话,会返回节点7。假设返回的是7,那么节点7调用decrement函数返回到header,header返回的又是节点7,就不合常理了

 

 

RB-tree的真正迭代器:

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 { returnlink_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的内容,细致实现之前已经有过,这里就不再重复。

 

 

关于Set

Set的特性是所有元素都会根据元素的键值自动被排序。Set的元素不像map那样可以同时拥有实值(value)和键值(key),set元素的键值就是实值,实值就是键值。Set不允许两个元素具有相同的键值(就是说用的是RB-tree中的insert_unique插入方法)

 

我们不能通过set的迭代器更改set的元素值,因为set的元素值就是其键值,关系到其排列规则。(通过查看set实现中关于iterator的声明我们可以看出来:

  typedef typename rep_type::const_iterator iterator;

 

Set以RB-tree为底层机制,几乎所有的set操作行为,都只是转调用RB-tree的操作

因此,如果希望深入的了解set,那么理解RB-tree是最直观的方法

 

我们可以调用iterator的基础find函数来寻找我们需要的元素,但是通过内部实现我们可以知道它是通过遍历所有元素来查找的,复杂度为O(n)。所以,如果在用RB-tree为底层实现的数据结构中进行寻找时,我们一般使用该结构自己实现的find函数(即set内部实现的find函数,复杂度为O(logn))

 

关于Map

Map的特性是,所有元素都会根据元素的键值自动被排序。Map的所有元素都是pair,同时

 

拥有实值和键值。Map不允许两个元素拥有相同的键值

我们可以通过迭代器修改元素的实值,但不允许修改其键值

 

Map的底层实现也是RB-tree

 

0 0
原创粉丝点击