SGISTL源码探究-STL中的红黑树(上)

前言

本小节将进入到SGISTL的红黑树部分。关于红黑树,是一种比较复杂的数据结构,但是并不是特别难。如果对红黑树不太了解,可以去网上查阅相关的资料,因为本文的主要目的是分析STL中的红黑树的源码,和普通的红黑树略有不同,还要复杂一些,需要有一定的基础。

基本概念

首先什么是二叉搜索树,二叉搜索树即符合任何一个节点的值一定大于其左子树的任何一个节点的值并且小于其右子树的任何一个节点的值(节点的值不允许重复),我们把符合这样一个性质的二叉树称为二叉搜索树。二叉搜索树的查找、插入、删除的平均效率很高,为O(logn)。
但是请思考这样一个场景,插入一个递增的有序数组。这样会使得二叉搜索树退化成链表,而链表的复杂度大家都知道。
为了解决这个问题,引入了平衡二叉搜索树,在还是一个二叉搜索树的前提下,加入了对平衡性的控制。简单来说就是不允许某节点的两个子树的深度相差太大,如果超过标准,则进行调整。

简单的介绍一下红黑树的四大性质:
1. 根节点为黑
2. 每个节点为红色或者黑色
3. 如果一个节点是红色的,则它的左右孩子节点都为黑色
4. 对每个节点,从该节点到其所有后代叶节点的路径上,均包含相同数目的黑色节点。

红黑树属于平衡二叉搜索树的一种,它对平衡的要求不像AVL树那么严格,但是因为以上的几个特性使得一棵有n个节点的红黑树的高度保持在logn,并且它的插入、删除、查找操作的时间复杂度也是O(logn),总的来说是一个很经典并且高效的树。

当我们对红黑树进行插入、删除时,可能会导致红黑树无法满足以上的性质3或4,这样我们就需要操作来调整红黑树,使得它重新满足一棵红黑树的性质。这样的操作有左旋、右旋、双旋等,在这里我们不去将它们具体是怎么旋转的,而是在源码中分析。

源码分析

关于rb_tree,所有的源码都在stl_tree.h中,实现一棵完整的红黑树的代码量一般都在几百行,由于stl需要对其进行泛化,所以代码量有1099行左右。关于红黑树的插入操作,算是比较简单了,它的删除操作需要分的情况特别多,而《STL源码剖析》一书中也并未提到删除操作,关于这一块,可能需要你查阅其他资料并且尝试自己跟着画图去进行理解。
接下来,我们按照它的节点设置,以及迭代器,定义部分、构造/析构函数、一些简单的成员函数,各种复杂的操作这样的顺序来依次分析。

节点设置

红黑树的每个节点有红色/黑色其中一种颜色,在stl中,由truefalse表示。

//红色为false,黑色为true
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;
};

红黑树的迭代器

关于红黑树的迭代器部分,increment以及decrement中,有两种情况比较诡异。这都是因为SGISTL实现红黑树时使用了小技巧,在根节点(root)上面还有一个header节点。
当红黑树处于初始状态时,只有header节点(为红色),它的左右孩子初始状态指向自己,当插入节点时,header的父节点指向根结点,左子节点指向最小的节点,右子节点指向最大的节点。
图解如下:
《SGISTL源码探究-STL中的红黑树(上)》

//迭代器的基础类
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 {
      /* 没有右子树时,则只有往上找了 * 这是因为由于二叉搜索树的特性 * 比node值大的只能是在上层中 * 若当前节点是其父节点的右孩子 * 则一直上移(这是因为若是右孩子,则比父节点的值大,所以不可取) * 只有当当前节点为父节点的左孩子时,才能取得该值 / /* 这里注意一个特殊的情况,即当node为root节点并且无右子树时 * 此时的y为header,而y->right=root * 所以会进一次循环,将node更新为header,y更新为root * 此时由于y->right=null!=node所以退出循环 * 接下来不会进入node->right != y这个条件判断,将node置成y * 结果就是node指向header * 这种情况很特殊,网上该部分大多数都是直接抄书,这里请配合我画的图进行理解 */
      base_ptr y = node->parent;
      while (node == y->right) {
        node = y;
        y = y->parent;
      }
      /* 此时节点的右孩子不为父节点 * 这个判断看起来很奇怪,因为y不是node的父节点吗 * 其实为了处理当node为root时并且root无右子树的特殊情况 * 若不是这种特殊情况,那么node就该取其父节点的值,因为node是y的左孩子,而y的值大于其左子树 */
      if (node->right != y)
        node = y;
    }
  }
  /供重载--操作符调用
  void decrement()
  {
    /* 这里又是一种特殊的情况,配合上面的图还是很容易理解的 * 特殊情况:当node指向header时 * header的颜色为红色,并且header的父节点的父节点为本身,满足条件 * 对应前面只有根结点并且根结点无右子树,进行++为header的情况 * 这里指向header,进行--,应该为header的右子树,为整棵红黑树的最大值节点 */
    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;
    }
  }
};

//迭代器
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 */

  //重载++操作符,主要就是调用了基类的increment函数
  self& operator++() { increment(); return *this; }
  self operator++(int) {
    self tmp = *this;
    increment();
    return tmp;
  }

  //重载--操作符,主要调用了基类的decrement函数
  self& operator--() { decrement(); return *this; }
  self operator--(int) {
    self tmp = *this;
    decrement();
    return tmp;
  }
};

关于红黑树的迭代器部分就这样,它和红黑树的节点有这样的联系。(这里直接使用了《STL源码剖析》书中的图。
《SGISTL源码探究-STL中的红黑树(上)》

定义部分及构造/析构、一些简单的成员函数

该部分主要是介绍红黑树的相关定义以及初始化、构造等,还有一些简单的函数。比较复杂的操作,如插入、删除等,我们放在下一小节分析。
以下是定义部分,以及一些获取节点成员的函数。

template <class Key, class Value, class KeyOfValue, class Compare,
          class Alloc = alloc>
/* 首先我们应该注意的是模板的参数 * key代表红黑树上存储节点时依据的值 * value即节点的值 * KeyOfValue是仿函数,用于通过value获取到key,至于仿函数是什么,现在没必要知道,我们后面会进行专门的分析 * Compare为比较key大小的标准 * 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); }
  //创建一个节点并初始化为x
  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);
  }

protected:
  size_type node_count; // keeps track of size of tree
  //header节点
  link_type header;
  //大小比较的标准,因为节点的类型并不是只能int之类的,也可能是对象,所以需要有一个比较大小的标准 
  Compare key_compare;

  /* 获取根结点 */
  link_type& root() const { return (link_type&) header->parent; }
  /* 获取红黑树的key值最小的节点 * header的左孩子指向key值最小的节点 */
  link_type& leftmost() const { return (link_type&) header->left; }
  /* 获取红黑树的key值最大的节点 * header的右孩子指向key值最大的节点 */
  link_type& rightmost() const { return (link_type&) header->right; }

  //返回当前节点的左孩子
  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& parent(link_type x) { return (link_type&)(x->parent); }
  //获取当前节点的value
  static reference value(link_type x) { return x->value_field; }
  //通过value获取当前节点的key
  static const Key& key(link_type x) { return KeyOfValue()(value(x)); }
  //获取当前节点的颜色
  static color_type& color(link_type x) { return (color_type&)(x->color); }

  static link_type& left(base_ptr x) { return (link_type&)(x->left); }
  static link_type& right(base_ptr x) { return (link_type&)(x->right); }
  static link_type& parent(base_ptr x) { return (link_type&)(x->parent); }
  static reference value(base_ptr x) { return ((link_type)x)->value_field; }
  static const Key& key(base_ptr x) { return KeyOfValue()(value(link_type(x)));}
  static color_type& color(base_ptr x) { return (color_type&)(link_type(x)->color); }
  //获取最小值和最大值
  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:
  //声明迭代器
  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申请空间并将颜色置为红
    header = get_node();
    color(header) = __rb_tree_red; // used to distinguish header from
                                   // root, in iterator.operator++
    /* 根结点为空并且header的左右孩子指向自己 * 这样的用法第一次见可能有点奇怪,不过它是合法的,因为是左值引用 */
    root() = 0;
    leftmost() = header;
    rightmost() = header;
  }

以下便是构造/析构函数部分

public:
                                // allocation/deallocation
  //设置大小比较标准,并且调用init()进行初始化
  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
    header = get_node();
    color(header) = __rb_tree_red;
    /* 若x无根结点,就比较简单了,将header的左右孩子节点设置为自己即可 * 否则调用__copy函数进行拷贝,并设置header,最后将node_count更新 */
    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;
  }
  /* 析构函数 * 调用clear删除除了header之外的所有节点 * 最后删除header节点 */
  ~rb_tree() {
    clear();
    put_node(header);
  }
  rb_tree<Key, Value, KeyOfValue, Compare, Alloc>&
  operator=(const rb_tree<Key, Value, KeyOfValue, Compare, Alloc>& x);

以下是一些简单的函数

public:    
                                // accessors:
  Compare key_comp() const { return key_compare; }
  //起始迭代器是最小节点
  iterator begin() { return leftmost(); }
  const_iterator begin() const { return leftmost(); }
  //末尾迭代器是header
  iterator end() { return header; }
  const_iterator end() const { return header; }
  ......
  bool empty() const { return node_count == 0; }
  size_type size() const { return node_count; }
  size_type max_size() const { return size_type(-1); }

小结

在本小节中,我们了解了SGISTL中的红黑树的节点、迭代器及部分实现,它主要是为了实现setmap容器奠定下了基础。在下一小节中,我们将针对它的插入、删除等复杂的操作进行分析。

    原文作者:算法小白
    原文地址: https://blog.csdn.net/Move_now/article/details/78003493
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞