深度解析红黑树
Posted 程序世界的王子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深度解析红黑树相关的知识,希望对你有一定的参考价值。
写在前面
红黑树是比较难的数据结构之一,在JDK1.8中,红黑树已经广泛应用到HashMap、TreeMap等基础集合类之中。笔者之前也研究过一段时间的红黑树,现将自己的学习和研究所得整理出来,供广大同仁参考。
本文分为3部分:
第一部分为“红黑树筑基:二叉查找树、AVL树、B树和2-3-4树”,对于没有数据结构基础的人直接学习红黑树比较困难,需要先阅读此部分。如果读者先前学过AVL树、B树等数据结构,请跳过此部分,直接从第二部分为“红黑树起步:性质与原理”读起。
第二部分为“红黑树起步:性质与原理”,主要讲述红黑树的历史演变过程和红黑树的五大性质,以及为什么红黑树会有五大性质,读者阅读之后可以做到不需要死记硬背也能一辈子也忘不了红黑树的性质。
第三部分为:“红黑树成长:操作与自平衡”,讲述红黑树的插入和删除等操作以及插入或删除后实现自平衡的方式,又为什么能使用这些方式实现自平衡,读者阅读之后既可以自己玩转红黑树了。
红黑树筑基:二叉查找树、AVL树、B树和2-3-4树
红黑树是一种比较高级的数据结构,要学习红黑树,首先需要一定的数据结构基础,务必掌握二叉查找树、AVL树和B树等数据结构,否则直接学习红黑树的话将如水中捞月、雾里看花。对于有一定数据结构基础的人,可以直接跳过此节内容。
二叉查找树
二叉查找树的定义
回忆一下,我们小时候经常玩的一个猜字游戏,给一个数字范围,让参与者猜谜底是哪个字。很多人都是一点点的提高数值来猜,但是这样猜会很没有效率。因此,有些聪明人都知道需要利用折半查找的思想去猜测。假定某个产品在100元的范围内,那么可以在7次之内猜出结果来。由于是100以内的正整数,所以我们先猜50(100的一半),被告之“大了”,于是再猜25(50的一半),被告之“小了”,再猜37(25与50的中间数),小了,于是猜43,大了,40,大了,38,小了,39,完全正确。
在计算机中,我们要在一组数字中查找其中一个,如果这组数字是无序存储,则需要逐个遍历。但是采用二叉树的结构存储的话,在查找时就可以实现每次遍历后都能把剩余数字的范围缩小一半的效果。
二叉查找树是一种特殊的二叉树,其改善了二叉树结点查找的效率,二叉查找树具有以下性质:
对于任意一个节点n:
1、其左子树下的每个后代节点的值都小于节点n的值。
2、其右子树下的每个后代节点的值都大于节点n的值。
3、左右子树也均为二叉查找树。
所谓节点 n 的子树,可以将其看作是以节点 n 为根节点的树。子树的所有节点都是节点 n 的后代,而子树的根则是节点 n 本身。
如下图为典型的二叉查找树:
二叉查找树的类型
二叉查找树按照其结构特征可以分为以下几种类型。
一、斜树
所有的结点都只有左子树的二叉树叫左斜树。所有结点都是只有右子树的二叉树叫右斜树。这两者统称为斜树。
如下为右斜树:
二、满二叉树
在一棵二叉树中。如果所有分支结点都存在左子树和右子树,并且所有叶子节点(即没有子节点的节点)都在同一层上,这样的二叉树称为满二叉树。
满二叉树的特点有:
a.叶子节点只能出现在最下一层。出现在其它层就不可能达成平衡。
b.非叶子结点的度(结点拥有的子树数目称为结点的度)一定是2。
c.在同样深度的二叉树中,满二叉树的结点个数最多,叶子数最多。
如下为满二叉树
三、完全二叉树
对一颗具有n个结点的二叉树按层编号(按从上至下、从左到右的顺序进行编号),如果编号为i(1<=i<=n)的结点与同样深度的满二叉树中编号为i的结点在二叉树中位置完全相同,则这棵二叉树称为完全二叉树。
完全二叉树的特点:
a.叶子结点只能出现在最下层和次下层。
b.最下层的叶子结点集中在树的左部。
c.倒数第二层若存在叶子结点,一定在右部连续位置。
d.如果结点度为1,则该结点只有左子节点,没有右子节点。
e.同样结点数目的二叉树,完全二叉树深度最小。
注:满二叉树一定是完全二叉树,但反过来不一定成立。
下图展示一棵完全二叉树
前驱节点和后继节点
前驱和后继节点在树的删除操作中有很重要的应用,
仍然以下面的二叉树为例来说明:
(1)前驱节点
定义:中序遍历的上一个节点(中序遍历方法后面介绍),就是比当前节点的值小的最大的节点。
查找方法:
①若一个节点有左子树,那么该节点的前驱节点是其左子树中val值最大的节点(也就是左子树中所谓的rightMostNode),例如12的前驱节点是11,因为11是其左子树中的最大值。
②若一个节点没有左子树,那么判断该节点和其父节点的关系
a. 若该节点是其父节点的右子节点,那么该节点的前驱结点即为其父节点。例如15的前驱节点是14,因为15是14的右子节点。
b. 若该节点是其父节点的左边孩子,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到第一个节点P,P节点是其父节点Q的右子节点,那么Q就是该节点的后继节点。例如13的前驱节点是12,因为14是12的右子节点,因此12是13的前驱节点。
(2)后继节点
定义:中序遍历的下一个节点,就是比当前节点的值大的最小的节点。
查找方法:
①若一个节点有右子树,那么该节点的后继节点是其右子树中val值最小的节点(也就是右子树中所谓的leftMostNode),例如12的后继节点是13,因为12是其右子树的最小值。
②若一个节点没有右子树,那么判断该节点和其父节点的关系。
a. 若该节点是其父节点的左子节点,那么该节点的后继结点即为其父节点 。例如13的后继结点为14,因为13是14的左子节点。
b. 若该节点是其父节点的右子节点,那么需要沿着其父亲节点一直向树的顶端寻找,直到找到第一个节点P,P节点是其父节点Q的左子节点,那么Q就是该节点的后继节点。例如11的后继节点为12,因为10是12的左子节点,则12是11的后继节点。
前驱与后继节点的另一个查找方法是投射法,此方法在红黑树中通用,具体做法为:将二叉查找树的所有节点投射到一条X轴上,则每个节点的左右相邻的两个节点即为其前驱节点和后继节点。
如下图所示,可以轻易看出13的前驱节点是12,后继结点是13。
二叉查找树的操作
一、查找
查找(红黑树通用):查找每个节点我们从根节点开始查找查找值比当前值大,则搜索右子树查找值等于当前值,停止查找,返回当前节点查找值比当前值小,则搜索左子树,具体步骤为:
(1)如果二叉查找树为空,查找失败,返回null
(2)如果根节点的键等于要查找的键,返回根节点的值
(3)否则,继续在子树中查找。如果要查找的键小于根节点的键,则在左子树中查找,反之在右子树中查找,如果根节点已经没有即将需要查找的子树,则查找失败。
(4)重复以上步骤,直到查找成功或失败为止。
对于 二叉查找树的查找算法来说,其十分依赖于树中节点的拓扑结构,也就是节点间的布局关系。如果二叉查找树n个节点构成高度为log2n+1的二叉查找树,其查找时间复杂度最小为 O(log2n)。当二叉树是斜二叉树时,二叉查找树已经退化为一个链表,此时查找的时间复杂度最大为O(n)。
因为在二叉查找树是满二叉树的情况下,每一次经过数值比对之后,我们都能确定在左子树中还是右子树中继续查找,然后需要查找的节点范围就减少了一半,这样不断的减半,使得查找的时间复杂度为O(log2n)。
二、遍历
二叉查找树的遍历方式有三种,分别为:
(1)前序遍历:先访问根节点,再访问左节点,最后访问右节点(根、左、右)。
(2)中序遍历:先访问左节点,再访问根节点,最后访问右节点(左、根、右)。
(3)后序遍历:先访问左节点,再访问右节点,最后访问根节点(左、右、根)。
以本文中的示例二叉查找树为例,其如果按照中序遍历的方式来进行遍历的话:
①以4为根节点,按照按照先左再根后右的顺序为246;
②以2为根节点,按照以上顺序遍历为123,则总顺序为12346
③以6为根节点,按照以上顺序遍历为567,则总顺序为1234567
三、插入
在二叉搜索树中插入新元素时,必须先检测该元素是否在树中已经存在。如果已经存在,则不进行插入;否则将新元素加入到搜索停止的地方。
二叉搜索树的插入很简单,有一个核心点,不管是什么树,最后都可以插入到叶节点的下面,不必在中间插入。因此,本质还是搜索,用二叉查找树的搜索算法,一直搜索到搜索失败的叶子节点,最后比较要插入的值和叶节点的值大小决定是成为该叶节点的左节点还是右节点即可。
四、删除
删除操作的本质就是找前驱或者后继节点来替代被删除的节点
(1)叶子节点直接删除(没有前驱或后继节点)
(2)只有一个子节点的用子节点替代(本质上就是找的前驱节点或者是后继节点,左节点就是前驱节点,右节点就是后继节点)
(3)有两个子节点的,需要找到替代节点(替代节点就是前驱节点或者后继节点),用前驱节点或者后继节点来替代被删除的节点。
二叉查找树的删除操作举例,以下图的二叉查找树为基础操作:
在以上的这个二叉查找树中,如果删除节点15,因为15为叶子节点,所以可以直接删除:
继续删除节点14,因为14已经只有1个子节点13,所以直接用13节点代替14节点即可。
然后再删除4,因为4有两个子节点,所以需要使用其前驱节点或者后继结点替代,使用后继节点(4的后继节点为5)替代则为:
二叉查找树的局限性
BST存在的问题是,树在插入的时候会导致倾斜,不同的插入顺序会导致数的高度不一样,而树的高度直接影响了树的查找效率。最坏的情况所有的节点都在一条斜线上,这样树的高度为N,此时的查查找效率最差。
基于BST存在的问题,平衡查找二叉树(Balanced BST)产生了。平衡二叉树在插入和删除的时候,会通过旋转等操作将高度保持在LogN。其中两种具有代表性的平衡二叉树分别为AVL树和红黑树。
AVL树
AVL树的概念
平衡二叉搜索树(Self-balancing binary search tree)又被称为AVL树,是一种具有自平衡功能的二叉查找树。其具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。因此AVL树规定了任意节点的两个子树的高度查不能超过1,所以AVL树不会一直向一个方向生长为斜树。
例如,以下是AVL树:
AVL树的自平衡
AVL树本质上就是一种有限制条件的二叉查找树(二叉查找树是空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树)。因此其查找、插入和删除的方法和二叉查找树是一样的。但是因为AVL树需要维持自身高度差绝对值不超过1的性质,所以在每次进程插入或者删除操作后需要对其结构进行调整,调整的过程就是AVL树的自平衡,其自平衡的方法有两种:左旋和右旋。
由于AVL树的左旋、右旋方法和红黑树相同,所以将此部分内容放入红黑树部分讲述,在此先展示以下左旋和右旋
如下为AVL树左旋
如下为右旋
AVL树的缺点
AVL树严格规定了每一个节点的两个子树的高度差不能超过1,是一种强平衡的二叉树,所以在每次新增或者删除节点中都需要通过旋转来进行自平衡操作,其维持平衡性的成本太高。对于经常要进行插入和删除的数据,使用AVL树来存储并不合适。
B树
B树的概念
由于AVL树的强平衡性,每次新增或者删除节点时都有进行自平衡操作。但是如果让AVL树的每个节点能包含多个元素,那么每次向AVL树插入或删除元素时,就并不一定会引起其节点的变化,也就是不一定需要进行自平衡操作,则维护平衡性的成本就会大大降低,但是这样做也会降低其查找效率。
按照这个解决思路,B树应运而生。
B树(B-tree)是一种树状数据结构,它能够存储数据、对其进行排序并允许以O(log n)的时间复杂度运行进行查找、顺序读取、插入和删除的数据结构。B树,概括来说是一个节点可以拥有多于2个子节点的二叉查找树。每个节点的子树的数量成为B树的阶数,对于M阶B树,其其具有以下特点:
1、根节点和叶子节点至少有2个分支(子树),其它节点至少有M/2向上取整个子节点。
2、每个节点最多有M个分支。
3、有n(k<=n<=M)个分支的节点有n-1个元素,它们按照递增顺序排列,其中k=2(根节点)或M/2向上取整(非根节点)。
4、节点内的关键字互不相等。
5、叶子节点处于同一层,可以用空指针表示,代表查找失败的位置。
如下为四阶B-树:
B树的插入和删除
一、插入
针对M阶高度为h的B树,插入一个元素时,首先查找该元素在B树中是否存在。如果不存在,即在叶子结点处结束,然后在叶子结点中插入该新的元素。
B树插入的规则如下:
(1)若该节点元素个数小于m-1,直接插入;
(2)若该节点元素个数等于m-1,引起节点分裂;以该节点中间元素为分界,取中间元素(偶数个数,中间两个随机选取)插入到父节点中;
(3)重复上面动作,直到所有节点符合B树的规则;最坏的情况一直分裂到根节点,生成新的根节点,高度增加1;
二、删除
B树的删除规则为:首先查找B树中需删除的元素,如果该元素在B树中存在,则将该元素在其结点中进行删除;删除该元素后,首先判断该元素是否有左右孩子结点,如果有,则上移孩子结点中的某相近元素(“左孩子最右边的节点”或“右孩子最左边的节点”)到父节点中,然后是移动之后的情况;如果没有,直接删除。
(1)某结点中元素数目大于等于(m/2)-1,(m/2)向上取整,直接删除;
如以下4阶B-树中进行删除元素16:
(2)某结点中元素数目不大于(m/2)-1,(m/2)向上取整,但是兄弟节点的元素数量大于此值,则可以向兄弟节点借一个元素;
注意,如果B-树的借元素只能间接的借,让父节点中最近的元素下来与该元素所在的节点合并,兄弟节点最近的元素上去并入父节点,否则将破坏B-树中元素分布的顺序性。
如下图的B-树需要删除8,8元素所在的节点元素数量不够,需要从兄弟节点借一个元素。此时需要把兄弟节点中与其最近的元素12上移与父节点合并,父节点中与其最近的元素10下移与8元素所在的节点合并,最后删除元素8。
(3)如果其相邻兄弟不丰满,即其结点中元素数量小于或等于(m/2)-1,则在该结点的父节点取出与该节点相连的元素,让该元素下移,合并该节点与其相邻的兄弟结点成一个结点;
例如要在下面的B-树中删除10,兄弟节点和父节点都没有多余的元素可以借,所以将父节点中与该节点相连的元素12下移,让该节点和兄弟节点合并,然后删除:
2-3-4树
2-3-4树就是一种阶为4的B-树,因为其每个节点可能含有2、3、4个子节点,所以叫做2-3-4树,在此不再介绍。
如下为2-3-4树:
红黑树起步:性质与原理
前世今生
2-3-4树的局限性
2-3-4树的查询操作像普通的二叉搜索树一样,非常简单,并且因为2-3-4树是一种弱平衡的二叉树,所以每次进行插入和删除操作时,维护其平衡性的成本较低。但由于其结点元素数不确定,在一些编程语言中实现起来并不方便。
因为2-3-4树是一种4阶B树,每个节点可以包含最多3个元素,所以在2-3-4树进行插入和删除操作后,不一定每次都需要都进行自平衡操作,维护其平衡性的代价比AVL树小的多。但是也因为其每个节点包含多个元素,所以其查找性能不如AVL树,2-3-4树实际上是在二叉树的查找性能和维护平衡性的代价之间做出的权衡。
如果用编程语言来实现2-3-4树,节点之间可以用指针来表达其关系,但是节点内部又包含多个元素,又要用内部的指针来表达,因此用编程语言来实现2-3-4树非常繁琐。
红黑树的由来
为了解决2-3-4树用编程语言不易实现的缺点,最简单的方法就是将包含多个元素的节点重新拆分为只包含一个元素的多个节点,形成一种新的二叉树。拆分之后,原2-3-4树的一个节点将变成多个只包含一个元素的节点,则需要给原2-3-4树的每个节点做一个标记,以便能掌控拆分之后的二叉树的高度,不让其生长为斜树。标记的方法为原2-3-4树的一个节点中的多个元素拆成多个节点后,其中有且仅有1个节点使用黑色着色,其他节点均使用红色。这种新的二叉树就叫做红黑树。
因为2-3-4树从其任一节点到它所能到达得叶子节点的所有简单路径包含的节点数相同,而且2-3-4树的每个节点都会拆分为红黑树的一个黑色节点和多个红色节点,所以在红黑树中从其任一节点到它所能到达得叶子节点的所有简单路径包含的相同的黑色节点,这就是红黑树的重要性质“黑色完美平衡”的由来。
2-3-4树与红黑树的等价关系
红黑树就是由2-3-4树的每个节点按照关键字进行拆分,然后进行重新组合而来的数据结构。
1、2-3-4树的包含一个元素的节点等价红黑树中的黑色节点;
2、2-3-4树的包含两个元素的节点等价红黑树中的上黑下红的两个节点;
2、2-3-4树的包含三个元素的节点等价红黑树中的上黑下两红的三个节点;
2-3-4树上的3种节点拆分方法如下图:
按照这种等价关系,我们可以将下面的2-3-4树拆分重组成红黑树:
红黑树的五大性质
五大性质的内容
最初学习红黑树时,笔者首先看到的就是红黑树的五大性质。
(1)每个节点或者是黑色,或者是红色。
(2)根节点是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
(5)从一个节点到该叶子节点的所有路径上包含相同数目的黑节点。
五大性质的原理
如果不懂红黑树的由来,就根本无法知道为什么需要有这五个性质。现在我们已经知道红黑树由2-3-4树按照一定的规则拆分而来,就可以对这五个性质进行解释:
红黑树的特性与原理:
(1)每个节点或者是黑色,或者是红色。
红黑树由2-3-4树转变而来,2-3-4树的每个节点按照关键字来拆分后,拆分而且的节点中需要选一个使用唯一颜色标记,代表原2-3-4树上的一个节点,以便掌握红黑树的高度。这个唯一颜色就是黑色,其他节点用红色区分。因此,2-3-4树到红黑树的转变方式决定了红黑树只要红色或者黑色。
(2)根节点是黑色。
2-3-4树到红黑树的转变方式中,如果节点只有1个元素,则只拆分成1个黑色节点;如果节点有多个元素,则拆分后的节点都是上黑下红的结构;无论2-3-4树的根节点有几个元素,拆分成红黑树后最上面的节点都是黑色,因此红黑树的根节点一定是黑色。
(3)每个叶子节点(NIL)是黑色。 [注意:这里叶子节点,是指为空(NIL或NULL)的叶子节点!]
这个是人为规定的,不需要解释。
(4)如果一个节点是红色的,则它的子节点必须是黑色的。
在拆分2-3-4树时,节点拆分之后的节点只有单个黑色,上黑下红两种形式,拼接在一起时不可能出现红色相连。
(5)从一个节点到叶子节点的所有路径上包含相同数目的黑节点。
因为2-3-4树从其任一节点到它所能到达得叶子节点的所有简单路径包含的节点数相同,而且2-3-4树的每个节点都会拆分为红黑树的一个黑色节点和多个红色节点,可以说红黑树中的一个黑色节点即可代表2-3-4树上的单个节点,所以在红黑树中从其任一节点到它所能到达得叶子节点的所有简单路径包含的相同的黑色节点。
红黑树成长:操作与自平衡
自平衡的方法与原理
红黑树要求达到黑色完美平衡,即从一个节点到叶子节点的所有路径上包含相同数目的黑节点。如果我们对红黑树进行插入或者删除操作,可能会破坏这种平衡性,这时候就需要进行自平衡操作。
旋转和变色
红黑树实现自平衡的方式有两种,即旋转和变色。其中变色很简单,顾名思义就是将红色节点变为黑色,或者将黑色节点变为红色。旋转的操作就稍显复杂,会造成节点之间指针的变化。
红黑树的旋转分为左旋和右旋,下面以右旋来论述旋转的操作方法。
因为只讨论旋转,不涉及变色,所以此处的节点都定为无色。
如有以下的树,有3个节点,该树就是不平衡的。如果想让该树平衡,最简单的办法就是让2和8分别成为5的左右子节点。
如下图所示,2和8分别成为5的左右子节点,则该树恢复平衡。
观察从左到右的变化,相当于节点8向右顺时针旋转了大约90度,这种旋转方式就是右旋,8节点叫做旋转的支点。
到此,我们可以将右旋定义为:以P为支点,其左子节点为Q。将P向右下方旋转,使得P变为Q的右子节点,这种旋转方式为右旋。(P和Q为图中标识的节点)。
但实际上,我们需要做右旋的情况远比这个要复杂,主要有以下情况需要处理
1、旋转的支点P有父节点N
在完成右旋之后,P会变为其原左子节点Q的右子节点,因此P已经有了新的父节点,那么原有的父节点则需要作为Q的父节点。
2、旋转节点的左子结点Q如果有右子结点
旋转之后,支点P将会变成其左子节点P的右子节点,那么如果Q原本就有右子节点M,则M需要变更父节点。因为P在右旋时已经失去了左子节点Q(其左子节点Q变为P的父节点),所以M可以作为P的左子节点。
综上,我们可以总结出,右旋:
以P为支点,其左子节点为Q。将P向右下方旋转,使得P变为Q的右子节点,这种旋转方式为右旋。如果支点P原本有父节点N,那么N在旋转后变为Q的父节点;如果Q原本有右子结点N,那么N在旋转后作为P的左子节点。
同样的方法,我们也可以推导出左旋:
以P为支点,其右子节点为Q。将P向左下方旋转,使得P变为Q的左子节点,这种旋转方式为左旋。
如果支点P原本有父节点N,那么N在旋转后变为Q的父节点;如果Q原本有左子结点M,那么N在旋转后作为P的右子节点。
下图为左旋:
旋转和变色在2-3-4树中的等价效果
红黑树由2-3-4树转化而来,那么红黑树的变色和旋转在2-3-4树中都会对应一个等价效果。如果掌握这种等价效果,就能知道红黑树自平衡的方法及原理。
一、2-3-4树节点分裂对应红黑树的节点变色
在2-3-4树中,如果插入一个元素使得节点的元素数量大于3个则需要进行节点分裂,我们来看分裂前的2-3-4树和分裂后的2-3-4树分别对应的红黑树是什么样子。
(注:?表示任意满足要求的数字)
在上图中,红黑树的变化是6的两个子节点由黑边红。
如果最后6是根节点,还需要将6变为黑色,此时黑红色高度增长了1。
在上图中,红黑树的变化同样是6的两个子节点由黑边红,不同的是6依然是黑色。
由此,我们可以总结出:
规律一:2-3-4树的节点分裂等价于红黑树的节点变色,2-3-4树中包含4个元素的节点需要进行向上分裂,其中上移的元素为P;等价于在红黑树中将黑色节点P的两个红色子节点变为黑色。如果P是根节点,则最终需要变黑色,否则为红色。
二、2-3-4树的兄弟节点互借元素对应红黑树的旋转加变色
1、向右边兄弟节点借元素
在2-3-4树中,如果需要从一个节点删除元素,这个节点中包含的元素不够的话,首先要看其兄弟节点是否有多余的元素,有的话向兄弟节点借一个元素再删除自身的。借元素的方法为,兄弟节点与自己最近的元素上移与父节点合并,父节点与自己最近的元素下移与自己合并。
上图的红黑树变化是以10为支点进行左旋,并且8由黑变红色,12变为红色。
如果变化后的12最终为根节点,则需要维持黑色:
上图的红黑树变化依然是以10为支点进行左旋,并且8由黑变红色,不同的是12最终维持黑色。
由此我们可以总结出:
规律二:2-3-4树中某节点P需要从右边的兄弟节点Q借关键字的操作方法,等价于红黑树中以该节点P的父节点M为支点进行左旋,并且将P变为红色。如果旋转后M为根节点则最终为黑色,否则为红色。
2、向左兄弟节点借元素
如果2-3-4树中的某个节点在删除时需要向左兄弟节点借一个元素,对应的红黑树变化又是什么情况呢,请看下图:
在下图中,我们需要从2-3-4中删除12,则需要从左兄弟节点中借一个元素:
上图中的红黑树变化是以10为支点进行右旋,并且12由黑变红,8变为红色;
同样的,如果红黑树中8最后是根节点,需要变黑色:
上图中的红黑树变化同样是以10为支点进行右旋,并且12由黑变红,不同的是8维持黑色;
由此我们可以总结出:
规律三:2-3-4树中某节点P需要从左边的兄弟节点Q借关键字的操作方法,等价于红黑树中以该节点P的父节点M为支点进行右旋,并且将P变为红色。如果旋转后M为根节点则最终为黑色,否则为红色。
三、2-3-4树中节点合并对应红黑树的变色
当2-3-4树需要删除一个元素时,如果兄弟节点没有多余的元素,则需要父节点的元素下来与其合并:
上图中红黑树的变化是10的两个子节点由黑变红,10变为黑色。
从以上我们可以看出,
规律四:2-3-4树中父节点的某元素P下来与子节点合并等价于红黑树中的将P的黑色子节点变红色,P本身变黑色。
总结:通过以上我们可以看出:红黑树通过旋转和变色可以实现对应的2-3-4树的节点分裂、合并和节点互借元素等自平衡方式。
红黑树的插入
红黑树的插入和AVL树相似,都是在叶子节点上进行操作;不同的是红黑树在插入新的节点后,需要用过旋转和变色这两种方式来实现“黑色完美平衡”
因为红黑树需要维持黑色平衡,所以新插入的节点需要默认为红色,以便减少自平衡的成本,当插入红色的节点破坏了树的平衡之后再进行调整。
各种插入场景
1、如果插入节点的父节点是黑色
相当于2-3-4树中只包含1个元素的节点新增一个元素,不需要做任何自平衡的操作,只需要将红色节点直接插入即可。
2、如果插入节点的父节点是红色,并且父节点没有兄弟节点或者兄弟节点是黑色
这个时候相当于在2-3-4树中包含2个元素的节点内新增一个元素(因为兄弟节点是黑色,所以在2-3-4树中是一个单独的节点)
如下图为插入之前:
此时我们只需要看插入的节点,其叔叔节点没有变化不需要处理。
新增后的红黑树节点可能会得到以下四种结果:
其中的①和④显然违反了红黑树的性质,即红色节点不能相连。
其中对①的调整方式为先以1为支点进行右旋,再将1变为红色,2变为黑色,则恢复黑色平衡
同样的,对于④也是先旋转再变色
3、如果插入节点的父节点是红色,并且父节点的兄弟节点也是红色
这种情况实际上对应2-3-4树是在包含3个元素的节点上新增元素,在新增之前两种分别为:
2-3-4树的性质规定了每个节点只能含有最多3个关键字,如果多出3个,则需要向上分裂。在此,需要复习一下B树的分裂方法:取出节点的中间元素,如果该节点没有父节点,则中间元素向上分裂成为根节点;如果该节点有父节点,则中间元素向上与父节点合并。
我们在“自平衡的方法和原理”一节中已经得到规律一:“2-3-4树的节点分裂等价于红黑树的节点变色,2-3-4树中包含4个元素的节点需要进行向上分裂,其中上移的元素为P;等价于在红黑树中将黑色节点P的两个红色子节点变为黑色。如果P是根节点,则最终需要变黑色,否则为红色”,现在可以按照此规律实现插入节点后的自平衡。
下面请看两个插入关键字的操作:
(1)如果向叶子节点插入关键字3,则2-3-4树会发生以下分裂动作,分两种情况
①2-3-4树中的叶子节点有父节点,则中间元素取出后需要与父节点合并
备注:其中的“?”号代表改叶子节点原父节点中的关键字
2-3-4树分裂:
根据规律一:6需要向上分裂则将6的子节点变为黑色,如果6变换后非根节点则变为红色。
因此,对应红黑树的变化为:
②如果2-3-4树的叶子节点没有父节点,则中间元素向上成为根节点,按照规律一,需要再把6节点变为黑色。红黑树的高度增长了1。
2-3-4树分裂:
对应红黑树的变化为:
(2)如果向叶子节点插入关键字12
①2-3-4树的叶子节点有父节点,则中间元素取出后需要与父节点合并
2-3-4树分裂:
同样我们利用规律一来实现红黑树插入后的自平衡,6需要向上移动分裂,则6的子节点由红色变为黑色,对应红黑树的变化为:
②如果2-3-4树的叶子节点没有父节点,则中间元素向上成为根节点,因为红黑树的性质规定根节点必须是黑色,所以此时需要再把6节点变为黑色。红黑树的高度增长了1。
2-3-4树分裂:
对应红黑树的变化为:
综上,往2-3-4树的四节点插入关键字时,节点需要向上分裂;对应的红黑树的节点需要变色。如果该节点没有父节点,则红黑树的高度会增长1。
案例演示
在掌握了红黑树的新增操作后,我们来做一个小练习:
在以下2-3-4树和等价的红黑树中分别新增13,并且实现自平衡:
首先我们来看2-3-4树,13插入后首先会与10-11-12这个节点合并,然后该节点的关键字数量大于3,则需要向上分裂,其变化过程如图所示:
在此2-3-4树中,先后有两个节点发生了变化,
①节点分裂:10-11-12这个节点新增13关键字后发生了分裂;
②节点新增关键字:7-9节点随后新增了11关键字;
现在我们把两步对应的红黑树操作分别进行一次,观察效果:
1、节点分裂
在“自平衡方法和原理”一节中,我们得出规律:规律一:2-3-4树的节点分裂等价于红黑树的节点变色,2-3-4树中包含4个元素的节点需要进行向上分裂,其中上移的元素为P;等价于在红黑树中将黑色节点P的两个红色子节点变为黑色。如果P是根节点,则最终需要变黑色,否则为红色。;因此在红黑树中我们首先把11的两个红色子节点变黑色,11非根节点因此本身变红色;
2、节点新增关键字
在本节中我们得出,2-3-4树的三节点新增关键字对应的红黑树操作是先旋转再变色,因此我们要以7为支点进行左旋,再变色
这样,我们就完成了红黑树的自平衡。
红黑树的删除
删除操作是红黑树最难的一环节,坚持就是胜利!
红黑树的删除方式
AVL树、B树、2-3-4树等数据结构的新增都是在叶子节点上进行的,删除也是。即使删除的不是叶子节点的元素,也可不断的使用其前驱和后继节点来替代需要删除的节点,直到删除的是叶子节点为止。
1、如果要删除的替代节点是叶子节点,直接删除
2、如果删除的节点有唯一子节点,则使用子节点替代被删除的节点。
3、如果删除的节点有两个子节点,则使用其前驱节点或者后继节点来替代被删除的节点
另外:红黑树的删除在使用替代节点对删除节点进行替代之后,相当于删除了替代节点的那个位置。而且不论删除的是哪个节点,最终都可以转化为删除叶子节点。即一个节点即将被删除,如果其前驱节点P和后继节点Q均非叶子节点,那么可以使用P或Q的前驱或后继节点再次对P/Q进行替代,最终转化为删除叶子节点。
例如以下红黑树中删除5,5的后继节点是6,则需要使用6来替代5,但是6并不是叶子节点。然而6也有自己的后继节点6.5,则可以使用6.5再替代6的位置,这个时候就相当于删除了叶子节点6.5的位置了。
删除后的自平衡
因为红黑树由2-3-4树演变而来,所以下面依然结合2-3-4树来解析红黑树删除节点后的各种自平衡方法。
一、删除节点的替代节点为红色
因为替代节点是红色,所以删除以后不会影响红黑树的平衡性,直接删除即可,无需做任何自平衡操作。
二、替代节点是黑色
如果替代节点是黑色,那么删除以后红黑树将直接失去平衡,此时我们需要做自平衡了。
最终删除的替代节点都是叶子节点,如果这个叶子节点为黑色,说明在原始2-3-4树中对应的是包含一个关键字的节点。
在2-3-4中,如果只包含一个关键字的节点被删除的化,也需要进行平衡操作。下面,将两种树的操作方法结合起来看。
1、替代节点的兄弟节点有红色子节点
(1)替代节点的兄弟节点有1个红色子节点
替代节点的兄弟节点有1个红色子节点相当于在2-3-4中,删除节点的兄弟节点有多余的1个元素可以借,在红黑树中也可以使用借节点的方式来实现平衡。
以下图为例,需要删除元素8所在的节点,其兄弟节点有12、14两个关键字,其对应的红黑树如右图(红黑树中的8为需要删除的替代节点)。
注:其中“?”表示所以符合条件的元素。
首先看2-3-4树的操作方法为:
①借关键字
父节点的下来与需要删除的节点合并,然后从兄弟节点选最近的一个关键是向上替代原父节点。使用的是这种间接借的方法。
②删除关键字8
回顾一下我们在“自平衡的方法与原理”中得到的规律二“2-3-4树中某节点P需要从右边的兄弟节点Q借关键字的操作方法,等价于红黑树中以该节点P的父节点M为支点进行左旋,并且将P变为红色。如果旋转后M为根节点则最终为黑色,否则为红色”。
根据规律二,对应的红黑树的操作为,先以8的父节点10为支点左旋,再将8变为红色,M如果最后不是根节点则为红色。
如果M在旋转后为根节点,则需要为黑色,此时对应的2-3-4树和红黑树变化为:
(2)替代节点的兄弟节点有2个红色子节点
替代节点的兄弟节点有2个红色子节点相当于在2-3-4中,删除节点的兄弟节点有多余的2个元素可以借。但是因为2-3-4树向兄弟节点借元素必须借的是兄弟节点中最近的一个元素,为了在红黑树中实现等价效果,需要多一次旋转让2-3-4中里替代节点最近的元素被借走。
例如有下面的2-3-4树和红黑树,需要删除节点8。
因为再2-3-4树中,应当将10上移与父节点合并,9下移与8元素的节点合并,但是此时在红黑树中10并不是8的相邻节点,直接以11左旋,无法实现2-3-4树中等价的借元素效果,所以以下为错误的做法:
正确的做法为:先在红黑树中以11为支点右旋,让10与8相邻,再依据我们以上总结的规律二,以9为支点进行左旋,8变为红色:
同样的,10最后为根节点,需要是黑色:
2、替代节点的父节点为红色或者有红色子节点
替代节点的父节点为红色或者有红色子节点,则相当于在2-3-4树中,父节点有多余的元素,此时可以向父节点借一个元素。两者在删除之前如下图,最后需要删除的替代节点是8:
2-3-4树的删除操作为将10元素下移与8、12节点合并成新的节点,然后从新节点中删除8元素:
根据规律四:2-3-4树中父节点的某元素P下来与子节点合并等价于红黑树中的将P的黑色子节点变红色,P本身变为黑色。因此,我们将下移的10节点的子节点变成红色,10节点变为黑色,即可进行删除。
对应红黑树操作为:
3、替代节点的父节点为黑色并且无红色子节点
替代节点的父节点为黑色并且无红色子节点,对应的2-3-4树为父节点和兄弟节点均只含有1个元素。
这个时候,我们依然利用“自平衡方法和原理”中得到的根据规律四:2-3-4树中父节点的某元素P下来与子节点合并等价于红黑树中的将P的黑色子节点变红色,P本身变为黑色。将6和12均变为红色,10依然为黑色即可。
案例实战
在以下红黑树及其对应的2-3-4树中删除8:
直接给出答案,笔者累了,不再解释
2-3-4树的删除方式为:
对应的红黑树的删除方式为:
以上是关于深度解析红黑树的主要内容,如果未能解决你的问题,请参考以下文章