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

所谓关联容器:

         每笔数据(每个元素)都有一个键值(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和false
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;
  }
};

//第二层
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

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值