红黑树初探-STL为例

初学者初探红黑树算法

因为前些时间看了EPOLL的实现,一颗红黑树,一个链表,加几个简单的回调函数,解决了服务端编程人员瓶颈了数年的问题,不得不让人敬佩,如果你看过STL源码剖析,那么你对这个数据结构也一定不陌生。我会从以下几个方面具体的介绍红黑树,难免有很多地方从他人博客中采集,附有链接,仅供学习,没错,这个文章不看框架,只扣细节,每一个细节:


本质

实际上,红黑树是一棵树型数据结构,主要用于查找,他是一种不追求完全的平衡的一种二叉树,有着与AVL算法接近的时间复杂度,但是他可以在插入操作后,三次旋转内就让二叉树恢复平衡,比如文件系统或者数据库采用的B/B+树等,但是各有优劣,后者实现起来对程序员有着很大的挑战,我将在下次博客中讲解这种数据结构。如果你找到我这个文章,肯定是已经对红黑树有一个大致的概念,所以我不会将其与其他查找算法比较之类,我的目的是教会你了解并可以使用红黑树去让你的程序效率更高。

如果大家学过编译原理或者了解过压缩算法,想必对二叉树也不会陌生,我有时间会写一个关于加密/压缩的文章,说实话,一个个二进制在你眼前不断地变化真是个美妙的过程。

二叉树名词解释

这里写图片描述

二叉树由节点(nodes)和边(edges)构成,如上图所示,一棵树最上方有一个节点,也就是俗称的根节点(root),也就是图中的A,每个节点有两条有向边,每条边与一个节点相连,哪怕这个节点是空节点,类比图中的B/C两个节点,与左边的连接的称为左节点,与右边连接的称为右节点,图中A称为B/C的父节点(parent),B/C节点称为A的子节点,如果一个节点的左右子节点均为空节点,那么他被称为叶节点(leaf),如果图中的L节点。如果两个节点有共同的父节点,那么他们互相称对方为兄弟节点(siblings),一个节点有几个非空子节点,则它的度为几(后面会讲一个关于度有趣的定理)。

根节点至任何节点之间有唯一路径(path),路径经过了几条边,那么它的路径长度(length)就是多少,根节点到任何一个节点的路径长度称为该节点的深度(depth),所有叶子结点的最大深度称为这棵树的高度(height)。如果存在一条路径,可以让A->B,那么A就称为B的祖代(ancestor),B称为A的子代(descendant).任何节点的大小(size)都是指其所有子代(加自身)的节点总数

本模块引用自《STL源码剖析》,推荐对算法和数据结构有兴趣的同学去读,这是我找到的最佳实践


以上主要是为了让大家对每个名词有个概念,不至于后面我说出来大家满脸懵逼,让我和大家之间的沟通有个共同语言。

番外:
做过一道阿里研发面试题,具体的不谈,概括起来就是二叉树度为0的节点数总是比度为2的节点数大1,why?以下是个人理解:
个人理解:
如果一个完全二叉树,每当少一个叶子节点,也就是丢了一个度为0的节点,就会丢一个度为2的节点,这样相对数量没有变化,但是如果连续丢掉两个度为0的叶子节点,而且他们的父节点还是同一个,那么,他们的父节点就变成了度为0的节点,相当于少了两个多个一个,也就是少了一个,所以总是会有二叉树度为0的节点数总是比度为2的节点数大1。
公式证明:———引用自作业帮
因为二叉树所有结点滴个数都不大于2,所以结点总数n=n0+n1+n2 (1)又因为度为1和度为2的结点分别有1个子树和2个子树,所以,二叉树中子树结点就有n(子)=n1+2n2二叉树中只有根节点不是子树结点,所以二叉树结点总数n=n(子)+1 即 n=n1+2n2+1 (2)结合(1)式和(2)式就得n0=n2+1

为什么会引用自作业帮?我会告诉你这是高中数学题么?没错,高三学生通过阿里笔试的很可能超过大四,真是个悲伤的故事。

平衡二叉树

平衡二叉树(Self-balancing binary search tree),也就是大家说的AVL树,不等同与AVL算法,切记不要混淆,算法只是这个树的一种实现方法,红黑树也是实现方法之一。具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树,同时,平衡二叉树必定是二叉搜索树,反之则不一定。
这里写图片描述
为什么要用平衡二叉树,上图会有充分的演示,右图显示了一种极端,时间复杂度退化为n,那就和顺序查找没有了区别,何必费力不讨好。

二叉搜索树,可以在对数时间对元素进行插入和访问。 二叉搜索树的节点规则:任何一个节点的Key大于左子树上任何一个节点的Key,并且小于右子树上任何一个节点的Key。
根据这个性质,可以瞬间找到树中最大元素和最小元素,比如一直顺着左边的路径走,走到叶子节点,这个节点就是整个树中最小的元素。元素的增删改查其实都基于这个性质来进行的,不过在讲解增删改查之前,要对二叉树的旋转有一定的概念,否则一定头大。

为什么不选择AVL树,是因为既然你看到了这里,显然是已经选择了红黑树,多的就不谈了。

旋转-单旋/双旋

在了解单旋和双旋的概念前,首先要对插入点的位置有个概念。
这里写图片描述
如图,在虚线以上可以称为是一颗平衡的二叉树,但是如果在A,B,C,D这四个点插入一个节点,就会导致左右高度差距超过1,将不再平衡,所以需要将其重新调整成为一颗平衡的树。首先要明确一个概念,内侧外侧,其实也不难理解,图中,A,B的位置就是外侧,C,D的位置就是内侧,这仅是左边的情况,右边对称过去即可,当插入的节点在外侧时,需要进行单旋将其调整平衡,如果插入的节点在内侧,就需要进行双旋将其调整平衡,大家先记住这个概念,马上为大家讲解。

单旋

如上图,如果节点插入在了A/B点该如何调整。首先要明确,不平衡是因为 210 这个节点的左右子树的高度相差超过了1,才导致的不平衡,那么如果让他们高度差减少呢,有两个思路,一个是将左子树的高度降低,但是显然,左子树任何一个节点都不该删除,那么把右子树高度增加?没有新节点该怎么去增加?所以这个思路不成立,那么将树高度变小,既然不能从子树动手,那么只能去砍这颗树的root节点了,也就是210节点。

大家思考一个问题,当你背一个扁担,前面是个很沉包袱,后面是一个很轻的包袱,你该怎么做。

显然,应该把扁担往后挪,为什么?问牛顿吧。

以此类比,既然这颗树极度不平衡,那么就把他挪一挪,比如,把180节点当做root,210作为180的右子树,这样整棵树的左子树高度是不是下降了1,而右子树提高了1?结果如下
图2
为什么旋转成这样?既然这个文章是给新手读的,我当然会一点点分析给大家。
单旋又分为左旋右旋
首先,右旋,顾名思义,向右旋转,大家也都理解了。那么,不应该旋转成如下么?
这里写图片描述
这样才是大家理解的右旋,但是大家要明白这是二叉树,每个节点只能有两个子节点。一眼看去,谁不顺眼?肯定是200的节点啊,所以去掉它,但是也不能给他丢掉啊,把他插在哪里?210>200>180,有思路了么?既然大家知道了结果,那么我分析一下为什么这样,不能仅仅停留在感性层面上。
在原二叉树中,210的左子树中最大的是哪个点?如果你认真读了我的上文,一定会知道,200这个节点的地位,他一定是大于他的父节点,但是一定小于root节点,并且他的任何一个子树的任何一个节点都是这个性质!以200这个节点为根的树是不是都是这样?他该放在哪里?,是不是应该在旋转后210这个节点的左下角?所以这样大家理解为什么这么旋转了么。没错,不是一个节点,是将以200这个节点为根的一棵树整体移植到210左下角!
右旋大家一定是了解了,左旋呢?其实就是把开始的二叉树左右两个子树交换一个而已,一个向右边旋转,一个向左边旋转,但是都只旋转了一次,所以被称为单旋

双旋

讲完了单旋,想必大家对旋转这个也有了概念,讲去双旋也会简单一些。
这里写图片描述
还是这个图,如果节点的插入位置是C/D(内侧)呢?单次旋转看看?是不是发现依然不平衡?如下图
这里写图片描述
是不是发现旋转不管用了?

你会不会想?为什么不管用?能不能把C/D的情况转换成刚才的情况?实话告诉你,可以的。
首先,不考虑210节点和230节点,如下图,依然是一颗树:
这里写图片描述
想到了什么?是不是可以很容易转变成A/B的情况?首先把200节点作为root节点,C就成为了他的右节点,180成为了他的左节点,如下图:
这里写图片描述
这样是不是有思路了?是不是和刚才外侧插入的时候如出一辙?
因为进行了两次旋转,所以他叫做双旋,名字就是这样来的,哈哈

红黑树

说了以上这么多,其实仅仅是为了咱们的主角红黑树打基础,也许前面你没看懂,没关系,再看几遍,看懂了那就太好了,下面即将进入正文。
这里写图片描述

规则上图说的非常明白,请大家仔细阅读。大多博客中,对红黑树的理解和我有不一样的地方,将红黑树作为2-3树的延伸,也有说红黑树是2-3-4树,每种其实之前在学习的时候也都有看过,理解方式和实现方式都有很大不同,依我观看STL源码对红黑树的描述来看,以上图为准,只能说理解方式不同,不要说人家不对哦。有兴趣的同学可以去看看,开阔一下视野也是很好的啦。

下面我用自己的语言来组织一下红黑树:
红黑树(red-black tree),简称R-B树,他是一颗二叉平衡树的一种实现方式,每一个节点都有颜色,但是只有红色和黑色两种,黑色的节点可以有红色或者黑色的子节点,但是红色的节点只能有黑色的子节点,root节点是黑色的,每当插入一个新节点,旋转平衡前默认是红色的,没有满足任何一个条件,就需要对树进行颜色变换或旋转操作。一棵树的形成,是从根节点开始,一个个的添加节点,每次添加节点都会去调整树的颜色和结构,并且红黑树有一个特殊的性质,就是旋转操作绝不会超过三次就可以将这棵树调整平衡!

什么讲解都比不上一个经历了无数风雨的案例来的痛快,故我会在讲解的时候直接用STL源码中R-B树的实现进行讲解,希望能帮助到大家。具体R-B树能怎么用,其实STL源码中的SET/MAP已经是最好的演示了。
还有哦,实现语言并不是瓶颈,树是一种数据结构,是一种思想,大多是以空间换取时间效率的方式,主要了解思想,而不是纠结于我选择用了C++作为实现

节点定义

//首先是对节点颜色的定义,STL中采用了false表示红色,用true表示黑色
//个人觉得这个命名规范很漂亮,大家喜欢也可以参考一下[STL源码下载](http://www.sgi.com/tech/stl/download.html)
typedef bool _Rb_tree_Color_type;
const _Rb_tree_Color_type _S_rb_tree_red = false;
const _Rb_tree_Color_type _S_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 _M_color; 
  _Base_ptr _M_parent;
  _Base_ptr _M_left;
  _Base_ptr _M_right;
//寻找一棵树的最小节点
  static _Base_ptr _S_minimum(_Base_ptr __x)
  {
    while (__x->_M_left != 0) __x = __x->_M_left;
    return __x;
  }
//寻找一棵树的最大节点
  static _Base_ptr _S_maximum(_Base_ptr __x)
  {
    while (__x->_M_right != 0) __x = __x->_M_right;
    return __x;
  }
};
//在节点的身上加一个 值域 ,这个值可以是一个数字/字符串,也可以是自定义的类,相当于给每个节点身上加了尾巴
template <class _Value>
struct _Rb_tree_node : public _Rb_tree_node_base
{
  typedef _Rb_tree_node<_Value>* _Link_type;
  _Value _M_value_field;
};

红黑树旋转

STL中左旋右旋的实现,这里没有涉及颜色的变化,仅仅是逻辑的变化

//左旋
inline void 
_Rb_tree_rotate_left(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  _Rb_tree_node_base* __y = __x->_M_right;
  __x->_M_right = __y->_M_left;
  if (__y->_M_left !=0)
    __y->_M_left->_M_parent = __x;
  __y->_M_parent = __x->_M_parent;

  if (__x == __root)
    __root = __y;
  else if (__x == __x->_M_parent->_M_left)
    __x->_M_parent->_M_left = __y;
  else
    __x->_M_parent->_M_right = __y;
  __y->_M_left = __x;
  __x->_M_parent = __y;
}
//右旋
inline void 
_Rb_tree_rotate_right(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  _Rb_tree_node_base* __y = __x->_M_left;
  __x->_M_left = __y->_M_right;
  if (__y->_M_right != 0)
    __y->_M_right->_M_parent = __x;
  __y->_M_parent = __x->_M_parent;

  if (__x == __root)
    __root = __y;
  else if (__x == __x->_M_parent->_M_right)
    __x->_M_parent->_M_right = __y;
  else
    __x->_M_parent->_M_left = __y;
  __y->_M_right = __x;
  __x->_M_parent = __y;
}

插入节点

继续使用《STL源码分析》中案例
这里写图片描述

其中,3,8,35,75为新增节点,并且还未插入,只是准备插入,可以看出,这四个节点必为红色,不管哪一个插入都会导致违背R-B树的性质,所以都需要去调整树的结构和颜色。下面一一分析:
这里首先对一些概念进行定义:新插入的节点称为X,其父节点称为P,其爷爷节点称为G,其曾爷爷称为GG,其爷爷节点的另一个节点(叔叔节点)称为S。

插入的节点X默认是红色的,那么如果P节点是黑色,直接插入即可,如果P节点是红色,违反了性质3,所以需要调整,此时G节点一定是黑色,否则插入的这棵树本身就违反性质,而根据插入位置的不同,又分为以下四种情况:

  • S为黑色且X为外侧插入
  • S为黑色且X为内侧插入
  • S为红色且X为外侧插入
  • S为红色且X为内侧插入

第一种情况

也就是3节点插入的情况,如下图
这里写图片描述
第一时间想到右旋的同学,恭喜你答对了,哈哈。但是单纯的右旋不能解决问题,因为还有颜色的问题哦。结果就是下图啦
这里写图片描述

第二种情况

也就是8节点插入的情况,如下图
这里写图片描述
是不是想到了二旋?先将8,5进行旋转,将8改为黑色,这样是不是又回到了第一种情况呢?
这里写图片描述
前两种情况下,大家是不是发现,无论怎么调整,都影响15节点以及其他节点?所以一个单旋和一个二旋轻而易举的就搞定了,但是下面这两种情况,就不好说了哦

第三种和第四种情况

也就是75/35节点插入的情况,如下图
这里写图片描述
当然也可以一次次的旋转,但是有一个问题,转完之后上一层的颜色也得改变,但是上一层改变了,上上一层要不要变还得看情况。在这里我就不去讲解怎么通过一次次的旋转去调整树形了,因为看起来实在是太累了。这里我直接采用STL中采用的方式—由上而下:

假设新增节点为A,那么沿着A的路径,只要看到有某个节点是黑色,并且他的两个子节点都是红色,就把X变为红色,把两个子节点变为黑色,演示如下图:
这里写图片描述

这个时候,是不是就和前两种情况一样了呢?是不是注意到图中P与X节点均为红色,会不会觉得影响结果呢?
但是你可以想一下,在你插入A之后,通过单旋或者双旋,不仅仅是改变了形状,也会去改变转的那两个点的颜色哦,结果如下图:
这里写图片描述
是不是发现了一些规律呢?具体怎么去操作,STL中参考源码如下:

//大家仔细看一看源码,talk is cheap,show me the code
//multset/multmap使用的函数
template <class _Key, class _Value, class _KeyOfValue, 
          class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::insert_equal(const _Value& __v)
{
  _Link_type __y = _M_header;
  _Link_type __x = _M_root();
  while (__x != 0) {
    __y = __x;
    __x = _M_key_compare(_KeyOfValue()(__v), _S_key(__x)) ? 
            _S_left(__x) : _S_right(__x);
  }
  return _M_insert(__x, __y, __v);
}

//set/map 使用的函数
template <class _Key, class _Value, class _KeyOfValue, 
          class _Compare, class _Alloc>
pair<typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator, 
     bool>
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::insert_unique(const _Value& __v)
{
  _Link_type __y = _M_header;
  _Link_type __x = _M_root();
  bool __comp = true;
  while (__x != 0) {
    __y = __x;
    __comp = _M_key_compare(_KeyOfValue()(__v), _S_key(__x));
    __x = __comp ? _S_left(__x) : _S_right(__x);
  }
  iterator __j = iterator(__y);   
  if (__comp)
    if (__j == begin())     
      return pair<iterator,bool>(_M_insert(__x, __y, __v), true);
    else
      --__j;
  if (_M_key_compare(_S_key(__j._M_node), _KeyOfValue()(__v)))
    return pair<iterator,bool>(_M_insert(__x, __y, __v), true);
  return pair<iterator,bool>(__j, false);
}
template <class _Key, class _Value, class _KeyOfValue, 
          class _Compare, class _Alloc>
typename _Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>::iterator
_Rb_tree<_Key,_Value,_KeyOfValue,_Compare,_Alloc>
  ::_M_insert(_Base_ptr __x_, _Base_ptr __y_, const _Value& __v)
{
  _Link_type __x = (_Link_type) __x_;
  _Link_type __y = (_Link_type) __y_;
  _Link_type __z;

  if (__y == _M_header || __x != 0 || 
      _M_key_compare(_KeyOfValue()(__v), _S_key(__y))) {
    __z = _M_create_node(__v);
    _S_left(__y) = __z;               // also makes _M_leftmost() = __z 
                                      //    when __y == _M_header
    if (__y == _M_header) {
      _M_root() = __z;
      _M_rightmost() = __z;
    }
    else if (__y == _M_leftmost())
      _M_leftmost() = __z;   // maintain _M_leftmost() pointing to min node
  }
  else {
    __z = _M_create_node(__v);
    _S_right(__y) = __z;
    if (__y == _M_rightmost())
      _M_rightmost() = __z;  // maintain _M_rightmost() pointing to max node
  }
  _S_parent(__z) = __y;
  _S_left(__z) = 0;
  _S_right(__z) = 0;
  _Rb_tree_rebalance(__z, _M_header->_M_parent);
  ++_M_node_count;
  return iterator(__z);
}
inline void 
_Rb_tree_rebalance(_Rb_tree_node_base* __x, _Rb_tree_node_base*& __root)
{
  __x->_M_color = _S_rb_tree_red;
  while (__x != __root && __x->_M_parent->_M_color == _S_rb_tree_red) {
    if (__x->_M_parent == __x->_M_parent->_M_parent->_M_left) {
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_right;
      if (__y && __y->_M_color == _S_rb_tree_red) {
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {
        if (__x == __x->_M_parent->_M_right) {
          __x = __x->_M_parent;
          _Rb_tree_rotate_left(__x, __root);
        }
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_right(__x->_M_parent->_M_parent, __root);
      }
    }
    else {
      _Rb_tree_node_base* __y = __x->_M_parent->_M_parent->_M_left;
      if (__y && __y->_M_color == _S_rb_tree_red) {
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __y->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        __x = __x->_M_parent->_M_parent;
      }
      else {
        if (__x == __x->_M_parent->_M_left) {
          __x = __x->_M_parent;
          _Rb_tree_rotate_right(__x, __root);
        }
        __x->_M_parent->_M_color = _S_rb_tree_black;
        __x->_M_parent->_M_parent->_M_color = _S_rb_tree_red;
        _Rb_tree_rotate_left(__x->_M_parent->_M_parent, __root);
      }
    }
  }
  __root->_M_color = _S_rb_tree_black;
}

删除节点

删除节点,当你去学习插入节点的时候,是不是觉得有点难入手?没关系,别灰心,更难得在这里~

删除操作首先需要去找到这个节点,所以大家可以直接去看查找的讲解,很容易理解。
一切尽在代码里:

//代码量看难度

 //其实就是用X代替Z,用Y指向Z,代替了位置之后,通过Y这个指针删除掉Z这个节点
inline _Rb_tree_node_base*
_Rb_tree_rebalance_for_erase(_Rb_tree_node_base* __z,
                             _Rb_tree_node_base*& __root,
                             _Rb_tree_node_base*& __leftmost,
                             _Rb_tree_node_base*& __rightmost)
{
  _Rb_tree_node_base* __y = __z;
  _Rb_tree_node_base* __x = 0;
  _Rb_tree_node_base* __x_parent = 0;
  if (__y->_M_left == 0)     // 情况1 :Z的左子树为空
    __x = __y->_M_right;     // 将Y的右子树的根节点赋值给X
  else
    if (__y->_M_right == 0)  // 情况2:Z的左子树不为空,但是右子树为空
      __x = __y->_M_left;    // 将Y的左子树的根节点赋值给X
    else {                   // 情况3:Z有两个非空子树
      __y = __y->_M_right;   // 下面的操作就是找到Z的右子树中最小的那个节点,赋值给Y,并且将Y的右子树赋值给X
      while (__y->_M_left != 0)
        __y = __y->_M_left;
      __x = __y->_M_right;
    }
  if (__y != __z) {          // 其实是情况三的处理
    __z->_M_left->_M_parent = __y; //下面是替换操作,Y替换Z的操作
    __y->_M_left = __z->_M_left;   
    if (__y != __z->_M_right) {   //本质就是 Y不是Z的右子树的根节点
      __x_parent = __y->_M_parent;   
      if (__x) __x->_M_parent = __y->_M_parent;  //这几个操作将X节点上移一个位置,因为Y替换原来的Z了,因为Y没有左子树,所以直接向上移动就可以了
      __y->_M_parent->_M_left = __x;      
      __y->_M_right = __z->_M_right;
      __z->_M_right->_M_parent = __y;
    }
    else
      __x_parent = __y;   //如果Y是Z的右子树的根节点,那么直接上移就好了

    //至此,Z下面的移动操作完成了

    if (__root == __z)  //如果Z是树的根节点,那么让直接Y成为root节点
      __root = __y;
    else if (__z->_M_parent->_M_left == __z)  //如果Z是他父亲节点的左节点,就直接用之前的操作就可以了
      __z->_M_parent->_M_left = __y;
    else                                    //如果Z是他父亲节点的右节点
      __z->_M_parent->_M_right = __y;     
    __y->_M_parent = __z->_M_parent;   
    __STD::swap(__y->_M_color, __z->_M_color);  //交换,Z,Y的颜色
    //此时,Y完全替换了Z,Z已经被摘出来了,所以一个赋值操作,就可以得到Z
    __y = __z;
    // 现在,Y指向被删除的节点
  }
  else {                        // __y == __z  就是情况1 和 情况 2,左子树为空或者右子树为空
    __x_parent = __y->_M_parent;  //这种情况下的X相当于上一种情况下的Y,替换就成了
    if (__x) __x->_M_parent = __y->_M_parent;   
    if (__root == __z) 
      __root = __x;
    else 
      if (__z->_M_parent->_M_left == __z) //如果Z是他父亲节点的左节点
        __z->_M_parent->_M_left = __x;
      else                                  //如果Z是他父亲节点的右节点
        __z->_M_parent->_M_right = __x;  

  //下面又是一个知识点了哦!   STL中,采用了一个小技巧,用header表示整棵树,header与root互为父节点,也就是说root的父节点是header,header的父节点同样是root,下面是实现
  /*
  _Link_type header;
  _Link_type& _M_root() const 
    { return (_Link_type&) _M_header->_M_parent; }
  _Link_type& _M_leftmost() const 
    { return (_Link_type&) _M_header->_M_left; }
  _Link_type& _M_rightmost() const 
    { return (_Link_type&) _M_header->_M_right; }
  */
    if (__leftmost == __z)   //如果Z是树中最小的那个节点  这个的前提是Z的左子树为空,并且Z的父节点的左节点是Z,因为最小节点肯定是一直左左左走下来的呀
      if (__z->_M_right == 0)        // __z->_M_left 也是NULL
        __leftmost = __z->_M_parent;    //将Z的父节点的指针指向leftmost,为了将Z剔除出来,因为他没有子节点,所以直接向上靠就行了
    // makes __leftmost == _M_header if __z == __root
      else
        __leftmost = _Rb_tree_node_base::_S_minimum(__x);   //如果有右节点,并且节点是空,所以直接用他的右子树代替原来Z的位置

    if (__rightmost == __z)    //这个和上一个相反,但是目的都是在树中剔除Z,这样直接删除就不会出问题,我就不解释了哦
      if (__z->_M_left == 0)         // __z->_M_right 也必须是NULL
        __rightmost = __z->_M_parent;  
    // makes __rightmost == _M_header if __z == __root
      else                      // __x == __z->_M_left
        __rightmost = _Rb_tree_node_base::_S_maximum(__x);
  }


  //前面已经将Z节点在位置方面剔除干净,Y这个指针,指向的就是要剔除的节点
  //下面,就需要去改变树的颜色,保证红黑树的性质
  if (__y->_M_color != _S_rb_tree_red) {   //如果Y的颜色不是红色去执行下面得操作,如果他是红色,他的父节点和子节点一定是黑色,所以不会影响什么东西可以直接删除,如果是黑色,情况就很多了
    //X不是根节点  ,并且   X是NULL(如果Y是黑色的话,删除会导致这里少了一个黑色节点,违背性质4)或者X的颜色为黑色(根据性质4,Y一定有右子树)
    while (__x != __root && (__x == 0 || __x->_M_color == _S_rb_tree_black)) 
      if (__x == __x_parent->_M_left) {   //如果X是父节点的左节点
        _Rb_tree_node_base* __w = __x_parent->_M_right; //初始化W为X的伯父节点,注意W不可能为空,如果为空,直接违背第4原则
        if (__w->_M_color == _S_rb_tree_red) {   //如果X的伯父节点的颜色为红色
          __w->_M_color = _S_rb_tree_black; //将W的颜色设为黑色
          __x_parent->_M_color = _S_rb_tree_red;  //将X的父节点设置成红色 
          _Rb_tree_rotate_left(__x_parent, __root); //进行一次左旋操作
          __w = __x_parent->_M_right;  //将旋转后的X的父节点的右节点 ,重新赋值给W
        }
        if ((__w->_M_left == 0 ||  //W的左节点为空或者这个节点的颜色为黑色,二者满足其一
             __w->_M_left->_M_color == _S_rb_tree_black) &&
            (__w->_M_right == 0 || //W的右节点为空或者这个节点的颜色为黑色,二者满足其一
             __w->_M_right->_M_color == _S_rb_tree_black)) {   //上面两个条件需要同时满足  但是可以这样理解,默认将NULL节点当做黑色节点
          __w->_M_color = _S_rb_tree_red; //将W的颜色设置为红色
          __x = __x_parent; //将X再次上移
          __x_parent = __x_parent->_M_parent;
        } else {
          if (__w->_M_right == 0 ||   //W的右节点为空或者他的颜色是黑色,但是W的左节点不空并且他的颜色为红色
              __w->_M_right->_M_color == _S_rb_tree_black) {
            if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;  //如果W的左节点不为空  就将他的颜色设置为黑色
            __w->_M_color = _S_rb_tree_red;  //将W的颜色设置为红色
            _Rb_tree_rotate_right(__w, __root); //右旋
            __w = __x_parent->_M_right;  //将旋转后的X的父节点的右节点 ,重新赋值给W
          }
          __w->_M_color = __x_parent->_M_color;  //X父节点的颜色给W节点
          __x_parent->_M_color = _S_rb_tree_black;  //并且将X父节点的颜色设置为黑色
          if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black; //如果W的右子树不为空  那本将他的右子树的颜色设置为黑色
          _Rb_tree_rotate_left(__x_parent, __root); //左旋
          break;  //结束循环啦
        }
      } else {                  //如果X是父节点的右节点    类似,就不注释啦!
        _Rb_tree_node_base* __w = __x_parent->_M_left;
        if (__w->_M_color == _S_rb_tree_red) {
          __w->_M_color = _S_rb_tree_black;
          __x_parent->_M_color = _S_rb_tree_red;
          _Rb_tree_rotate_right(__x_parent, __root);
          __w = __x_parent->_M_left;
        }
        if ((__w->_M_right == 0 || 
             __w->_M_right->_M_color == _S_rb_tree_black) &&
            (__w->_M_left == 0 || 
             __w->_M_left->_M_color == _S_rb_tree_black)) {
          __w->_M_color = _S_rb_tree_red;
          __x = __x_parent;
          __x_parent = __x_parent->_M_parent;
        } else {
          if (__w->_M_left == 0 || 
              __w->_M_left->_M_color == _S_rb_tree_black) {
            if (__w->_M_right) __w->_M_right->_M_color = _S_rb_tree_black;
            __w->_M_color = _S_rb_tree_red;
            _Rb_tree_rotate_left(__w, __root);
            __w = __x_parent->_M_left;
          }
          __w->_M_color = __x_parent->_M_color;
          __x_parent->_M_color = _S_rb_tree_black;
          if (__w->_M_left) __w->_M_left->_M_color = _S_rb_tree_black;
          _Rb_tree_rotate_right(__x_parent, __root);
          break;
        }
      }
    if (__x) __x->_M_color = _S_rb_tree_black;    //将X的颜色设置为黑色
  }
  //返回要删除的Y节点,R-B树中已经没有任何节点和他关联了
  return __y;
}

感谢

对于源码的注释,我写的十分的不详细,如果想要看很详细的解析,可以去《stl源码剖析》中寻找答案。再次感谢作者写出这么好的一本书!
感谢STL的作者,不仅让大家写代码方便了许多,也让我学到了无数的东西,红黑树也仅仅是冰山一角。

第一次写博客,为了这个花了我零零散散3天时间,也第一次认真去扣一个源码的某个部分的细节,真心学到了很多东西,也许有很多不足之处,也许有些冒犯,请大家见谅,如果有疑问,随时提出来,大家讨论。

内容一周一更,大概内容都是数据结构,算法,服务器相关,协议相关的内容,如果感兴趣,或者有共同爱好的,可以一起讨论。

写博客是为了记录某时的思路,方便未来查阅,拿出来让大家去学习评判,错误也就暴露出来,希望不吝指正 ! 多的就不谈了,共同学习吧。

展开阅读全文

没有更多推荐了,返回首页