带你深入理解STL之RBTree

最近一直忙于校招的笔试,STL的深入理解系列也耽搁了好几天,再加上!红黑树真的是超级超级难理解,超级超级复杂,参考了好多博客上的大神的理解才稍微明白一点,勉强入个门,下面请以一个菜鸟的角度跟着我一起学习STL的红黑树吧。

概述

红黑树是平衡二叉搜索树的一种,其通过特定的操作来保持二叉查找树的平衡。首先,我们来复习一下二叉查找树的知识,建议如果对二叉查找树不理解的先去搜一下相关博客来了解一下。

二叉搜索树是指一个空树或者具有以下性质的二叉树:

  • 任意节点的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 任意节点的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 任意节点的左、右子树也分别为二叉查找树;
  • 没有键值相等的节点

我们知道,一颗由n个节点随机构造的二叉搜索树的高度为logn,但是,由于输入值往往不够随机,导致二叉搜索树可能失去平衡,造成搜索效率低下的情况。从而,引出了平衡二叉搜索树的概念。对于“平衡”这个约束不同的结构有不同的规定,如AVL树要求任何节点的两个子树的高度最大差别为1,可谓是高度平衡啊;而红黑树仅仅确保没有一条路径会比其他路径长出两倍,因而达到接近平衡的目的。红黑数不仅是一个平衡二叉搜索树,而且还定义了相当多的约束来确保插入和删除等操作后能达到平衡。

平衡二叉搜索树

那么,红黑树究竟是怎么定义,来使得能够达到平衡的目的呢?我们接着看下去。

红黑树的定义

红黑树既然属于二叉搜索树的一种,当然需要满足上述二叉搜索树的性质,除此之外,红黑树还为每一个节点增加了一个存储位来表示节点的颜色属性,它可以为red或者black,通过对任何一条从根到叶子节点的路径上每个点进行着色方式的限制,来确保没有一条路径会比其他路径长出两倍,因而达到接近平衡的目的。

那么,红黑树是如何进行着色的呢?下面引出了红黑树的五条性质:

  • 每个节点或者是黑色,或者是红色。
  • 根节点是黑色。
  • 每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
  • 如果一个节点是红色的,则它的子节点必须是黑色的。
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。

正是这五条性质,使得红黑树的高度能保持在logn,从而达到平衡的目的,进而使得其在查找、插入和删除的时间复杂度最坏为O(logn),下面就是一棵典型的红黑树。

红黑树

注: 因本人能力有限可能无法将红黑树讲得很清楚全面,而且STL红黑树的实现也较为复杂,建议先到下面推荐的几篇博客里去补补知识

红黑树的节点结构

红黑树的节点在二叉树的节点结构上增加了颜色属性,而且,为了更好的进行插入和删除操作,进而增加了指向父节点的指针。为了更好的弹性,STL红黑树的节点采用双层设计,将不依赖模板的参数提取出来,作为base结构,然后用带模板的节点结构取继承它。下面是红黑树节点结构的源码:

typedef bool __rb_tree_color_type;
const __rb_tree_color_type __rb_tree_red = false;  // 紅色為 0
const __rb_tree_color_type __rb_tree_black = true; // 黑色為 1

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;    // 節點實值
};

红黑树的迭代器

为了将RBtree实现为一个泛型容器,迭代器的设计很关键。我们要考虑它的型别,以及前进(increment)、后退(devrement)、提领(dereference)和成员访问(member access)等操作。

迭代器和节点一样,采用双层设计,STL红黑树的节点__rb_tree_node继承于__rb_tree_node_base;STL的迭代器结构__rb_tree_iterator继承于__rb_tree_base_iterator,我们可以用一张图来解释这样的设计目的。

STLRBTreeNodeAndIterator

将这些分开设计,可以保证对节点和迭代器的操作更具有弹性。下面来看迭代器的源码:

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 {                    // 没有右子节点
      base_ptr y = node->parent;    // 找出父节点
      while (node == y->right) {    // 如果该节点一直为它的父节点的右子节点
        node = y;                       // 就一直往上找,直到不为右子节点为止
        y = y->parent;
      }
      if (node->right != y)      // 若此时该节点不为它的父节点的右子节点
        node = y;                // 此时的父节点即为要找的后继节点
                                 // 否则此时的node即为要找的后继节点,此为特殊情况,如下
                                 // 我们要寻找根节点的下一个节点,而根节点没有右子节点
                                 // 此种情况需要配合rbtree的header节点的特殊设计,后面会讲到
    }                        
  }

  // 寻找该节点你的前置节点
  void decrement()
  {
    if (node->color == __rb_tree_red && // 如果此节点是红节点
        node->parent->parent == node)       // 且父节点的父节点等于自己
      node = node->right;                               // 则其右子节点即为其前置节点
    // 以上情况发生在node为header时,即node为end()时
    // 注意:header的右子节点为mostright,指向整棵树的max节点,后面会有解释
    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; }
  pointer operator->() const { return &(operator*()); }
  // 前置++和后置++
  self& operator++() { increment(); return *this; }
  self 
  • 6
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值