初学者初探红黑树算法
因为前些时间看了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?结果如下
为什么旋转成这样?既然这个文章是给新手读的,我当然会一点点分析给大家。
单旋又分为左旋和右旋
首先,右旋,顾名思义,向右旋转,大家也都理解了。那么,不应该旋转成如下么?
这样才是大家理解的右旋,但是大家要明白这是二叉树,每个节点只能有两个子节点。一眼看去,谁不顺眼?肯定是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天时间,也第一次认真去扣一个源码的某个部分的细节,真心学到了很多东西,也许有很多不足之处,也许有些冒犯,请大家见谅,如果有疑问,随时提出来,大家讨论。
内容一周一更,大概内容都是数据结构,算法,服务器相关,协议相关的内容,如果感兴趣,或者有共同爱好的,可以一起讨论。
写博客是为了记录某时的思路,方便未来查阅,拿出来让大家去学习评判,错误也就暴露出来,希望不吝指正 ! 多的就不谈了,共同学习吧。