RB-tree的性质

对于RB-tree,首先做一个了解,先看一张维基百科的RB-tree:

再看RB-tree的性质:

性质1. 节点是红色或黑色。

性质2. 根是黑色,所有叶子都是黑色(叶子节点指的是NIL节点)。。

性质3. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质4. 从任一节点到其每个叶子的所有简单路径 都包含相同数目的黑色节点。

二查搜索树的插入删除操作

在展开红黑树之前, 首先来看看普通二叉搜索树的插入和删除. 插入很容易理解, 比当前值大就往右走, 比当前值小就往左走。

这里详细展开的是删除操作:

二叉树的删除操作有一个技巧, 即在查找到需要删除的节点 X;

接着我们找到要么在它的左子树中的最大元素节点 M、要么在它的右子树中的最小元素节点 M, 并交换(M,X). 此时, M 节点必然至多只有一个孩子;

最后一个步骤就是用 M 的子节点代替 M 节点就完成了。

所以, 所有的删除操作最后都会归结为删除一个至多只有一个孩子的节点, 而我们删除这个节点后, 用它的孩子替换就好了. 将会看到 sgi stl map 就是这样的策略.

在红黑树删除操作讲解中, 我们假设代替 M 的节点是 N(下面的讲述不再出现 M).

RB-tree的插入操作

插入新节点总是红色节点, 因为不会破坏性质 5, 尽可能维持所有性质.

假设, 新插入的节点为 N, N 节点的父节点为 P, P 的兄弟(N 的叔父)节点为 U, P 的父亲(N 的爷爷)节点为 G. 所以有如下的印象图:

插入节点的关键是:

插入新节点总是红色节点
如果插入节点的父节点是黑色, 能维持性质
如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质

插入算法详解如下, 走一遍红黑树维持其性质的过程:

第 0.0 种情况, N 为根节点, 直接 N->黑. over
第 0.1 种情况, N 的父节点为黑色, 这不违反红黑树的五种性质. over

第 1 种情况, N,P,U 都红(G 肯定黑). 策略: G->红, N,P->黑. 此时, G 红, 如果 G 的父亲也是红, 性质又被破坏了, 这时,可以将 GPUN 看成一个新的红色 N 节点, 如此递归调整下去; 特殊的, 如果碰巧将根节点染成了红色, 可以在算法的最后强制 root->黑.

第 2 种情况, P 为红, N 为 P 右孩子, N 为红, U 为黑或缺少. 策略: 旋转变换, 从而进入下一种情况:(分N在P的左边还是右边)

第 3 种情况, 可能由第二种变化而来, 但不是一定: P 为红, N 为 P 左孩子, N 为红. 策略: 旋转, 交换 P,G 颜色, 调整后, 因为 P 为黑色, 所以不怕 P 的父节点是红色的情况. over

红黑树的插入就为上面的三种情况. 你可以做镜像变换从而得到其他的情况.


从代码实现的角度分析:

要真正理解红黑树的插入,还得先理解二叉查找树的插入。磨刀不误砍柴工,咱们再来了解一下二叉查找树的插入和红黑树的插入。如果要在二叉查找树中插入一个结点,首先要查找到结点要插入的位置,然后进行插入。假设插入的结点为z的话,插入的伪代码如下:

tree_insert(T, z)
y = NULL
x = T
while(x != NULL)
y = x
if(z->key < x->key)
x = x->left
else
x = x->right
end while z->parent = y; if(y == NULL)
T = z
else if(z->key < y->key)
y->left = z
else
y->right = z

红黑树的插入和插入修复

现在我们了解了二叉查找树的插入,接下来,咱们便来具体了解下红黑树的插入操作。红黑树的插入相当于在二叉查找树插入的基础上,为了重新恢复平衡,继续做了插入修复操作。

假设插入的结点为z,红黑树的插入伪代码具体如下所示:

rb_tree_insert
y = NULL
x = T
while(x != NIL)
if(x->key < z->key)
x = x->right
else
x = x->left
end while z->parent = y if(y == NULL)
T = z
else if(z->key < x->key)
y->left = z
else
y->right = z z->left = NIL
z->right = NIL
z->color = RED
rb_tree_insert_fix(T, z) end rb_tree_insert

把上面这段红黑树的插入代码,跟之前看到的二叉查找树的插入代码比较一下可以看出,RB-INSERT(T, z)前面的第1~13行代码基本上就是二叉查找树的插入代码,然后第14~16行代码把z的左孩子和右孩子都赋为叶结点nil,再把z结点着为红色,最后为保证红黑性质在插入操作后依然保持,调用一个辅助程rb_tree_insert_fix来对结点进行重新着色,并旋转。


下面紧接着调整程序:

换言之,如果插入的是根结点,由于原树是空树,此情况只会违反rb_tree根节点是黑色的这一个性质,因此直接把此结点涂为黑色;如果插入的结点的父结点是黑色,由于此不会违反rb_tree性质,红黑树没有被破坏,所以此时什么也不做。

但当遇到下述3种情况时又该如何调整呢?

● 插入修复情况1:如果当前结点的父结点是红色且祖父结点的另一个子结点(叔叔结点)是红色

● 插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

● 插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左子

答案就是根据红黑树插入代码RB-INSERT(T, z)最后一行调用的RB-INSERT-FIX(T, z)函数所示的步骤进行操作,具体如下所示:

//循环递归调整
rb_tree_insert_fix(T, z)
while(z->parent->color == RED)
if(z->parent == z->parent->parent->left)//父节点是祖父节点的左孩子
y = z->parent->parent->right//y是z的叔叔
if(y->color == RED)//红色叔叔
z->parent->color = BLACK
y->color = BLACK
z->parent->parent->color = RED
z = z->parent->parent
else if(z = z->parent->right)//黑色叔叔
z = z->parent
L_rotate(T, z)
else
z->parent->color = BLACK//这里会退出while循环
z->parent->parent->color = RED
R_Rotate(T, z->parent->parent)
else
//把rb_tree做对称处理
end while
T->color = BLACK
end rb_tree_insert_fix

下面,咱们来分别处理上述3种插入修复情况。

插入修复情况1:当前结点的父结点是红色,祖父结点的另一个子结点(叔叔结点)是红色(这时的祖父节点一定是黑色的)。

此时父结点的父结点一定存在,否则插入前就已不是红黑树。与此同时,又分为父结点是祖父结点的左孩子还是右孩子,根据对称性,我们只要解开一个方向就可以了。这里只考虑父结点为祖父左孩子的情况,如下图所示。

对此,我们的解决策略是:将当前节点的父节点和叔叔节点涂黑,祖父结点涂红,把当前结点指向祖父节点,从新的当前节点重新开始算法。即如下代码所示:

如下代码:

//循环递归调整
while(z->parent->color == RED)
if(z->parent == z->parent->parent->left)//父节点是祖父节点的左孩子
y = z->parent->parent->right//y是z的叔叔
if(y->color == RED)//红色叔叔
z->parent->color = BLACK
y->color = BLACK
z->parent->parent->color = RED
z = z->parent->parent

所以,变化后如下图所示:

于是,插入修复情况1转换成了插入修复情况2。

插入修复情况2:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的右子

此时,解决对策是:当前节点的父节点做为新的当前节点,以新当前节点为支点左旋。即如下代码所示:

else if(z = z->parent->right)//黑色叔叔
z = z->parent
L_rotate(T, z)

所以红黑树由之前的:

变化成:

从而插入修复情况2转换成了插入修复情况3。

插入修复情况3:当前节点的父节点是红色,叔叔节点是黑色,当前节点是其父节点的左孩子

解决对策是:父节点变为黑色,祖父节点变为红色,在祖父节点为支点右旋,操作代码为:

z->parent->color = BLACK
z->parent->parent->color = RED
R_Rotate(T, z->parent->parent)

最后,把根结点涂为黑色,整棵红黑树便重新恢复了平衡。所以红黑树由之前的:

变化成:

总结:经过上面情况1、情况2、情况3等三种插入修复情况的操作示意图,读者自会发现,后面的情况2、情况3都是针对情况1插入节点4以后,进行的一系列插入修复情况操作,不过,指向当前节点N指针一直在变化。
所以,你可以想当然的认为:整个下来,情况1、2、3就是一个完整的插入修复情况的操作流程。

[STL源码剖析]RB-tree的插入操作的更多相关文章

  1. STL"源码"剖析-重点知识总结

    STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略多 :) 1.STL概述 STL提供六大组件,彼此可以组合 ...

  2. 【转载】STL"源码"剖析-重点知识总结

    原文:STL"源码"剖析-重点知识总结 STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点 ...

  3. STL"源码"剖析

    STL"源码"剖析-重点知识总结   STL是C++重要的组件之一,大学时看过<STL源码剖析>这本书,这几天复习了一下,总结出以下LZ认为比较重要的知识点,内容有点略 ...

  4. STL源码剖析 - RB-tree

    在我看来,看源码是一件既痛苦又兴奋的事.当我们在推敲其中的难点时,是及其痛苦的,但当发现实现代码是那么丝滑简洁时,“wc, nb!”. 1. 导语 如果我们去看关联式容器map.set.multima ...

  5. STL源码剖析读书笔记之vector

    STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...

  6. STL源码剖析 迭代器(iterator)概念与编程技法(三)

    1 STL迭代器原理 1.1  迭代器(iterator)是一中检查容器内元素并遍历元素的数据类型,STL设计的精髓在于,把容器(Containers)和算法(Algorithms)分开,而迭代器(i ...

  7. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  8. STL源码剖析之序列式容器

    最近由于找工作需要,准备深入学习一下STL源码,我看的是侯捷所著的<STL源码剖析>.之所以看这本书主要是由于我过去曾经接触过一些台湾人,我一直觉得台湾人非常不错(这里不涉及任何政治,仅限 ...

  9. 《STL源码剖析》读书笔记

    转载:https://www.cnblogs.com/xiaoyi115/p/3721922.html 直接逼入正题. Standard Template Library简称STL.STL可分为容器( ...

  10. 面试题总结(三)、《STL源码剖析》相关面试题总结

    声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...

随机推荐

  1. ok6410驱动usb摄像头

    为了做图像处理,须要用摄像头,搜到实验室仅仅有一个摄像头,是国安的.详细參数在终端中看到: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGluZzEyM ...

  2. BestCoder Round #3 A,B

    A.预处理出来,0(1)输出. Task schedule Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K ...

  3. JavaScript创建类的方式

    一些写类工具函数或框架的写类方式本质上都是 构造函数+原型.只有理解这一点才能真正明白如何用JavaScript写出面向对象的代码,或者说组织代码的方式使用面向对象方式.当然用JS也可写出函数式的代码 ...

  4. Basic Concepts of Block Media Recovery

    Basic Concepts of Block Media Recovery Whenever block corruption has been automatically detected, yo ...

  5. 从一个非开发人员转行silverlight满一年的工作总结(第一次发帖)

    自2013年3月进入公司到现在已整整一年.这一年,让我从一个大学毕业就去参军并且专业还不对口的大学生步入了软件开发这个高门槛行业.说实话,我真的很庆幸,庆幸遇到了两位赏识自己的领导从很多专业对口.能力 ...

  6. springmvc jstl

    springmvc运用maven的jetty插件运行成功,部署在tomcat6报错:ClassNotFoundException: javax.servlet.jsp.jstl.core.Config ...

  7. SQL中数据类型转换

    CAST 和 CONVERT 将某种数据类型的表达式显式转换为另一种数据类型.CAST 和 CONVERT 提供相似的功能. 语法 使用 CAST: CAST ( expression AS data ...

  8. MAVEN项目,Eclipse启动报错:java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener

    由于是maven项目,tomcat在发布项目的时候没有同时发布maven依赖所添加的jar包,所以报此错误: 解决办法: 项目右键 —> 属性 -> Deployment Assembly ...

  9. c# 集合的交集、并集、差集

    , , , , }; , , , , , }; var jiaoJi = oldArray.Intersect(newArray).ToList();//2,4,5 var oldChaJi = ol ...

  10. SSH框架中一些技巧、处理办法

    1.使用jstree插件时,操作成功直接刷新jstree 该页面(index.jsp)本身使用iframe框架jstree在leftFrame,操作页(add_input.jsp.add_succes ...