【重难点】【数据结构 02】红黑树
一、红黑树
1.介绍
红黑树(Red Black Tree)是平衡二叉树的其中一种实现方式,解决了二叉排序树多次插入新结点导致的不平衡问题
除了二叉排序树的基本特性外,它还具有下列附加规则:
- 结点是红色或黑色
- 根结点是黑色
- 每个叶子结点都是黑色的空结点(NIL 结点)
- 每个红色结点的两个子结点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色结点)
- 从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点
- 平衡二叉树是严格平衡的二叉树,要求每个结点的左右子树高度差不超过 1。而红黑树则要宽松一些,只要求任何一条路径的长度不超过其他路径长度的 2 倍
当插入或删除结点的时候,红黑树的规则可能被打破。这时候就需要做出一些调整,从而继续维持我们的规则
向红黑树插入值为 14 的新结点
由于父结点 15 是黑色结点,因此这种情况并不会破坏红黑树的规则,无需做任何调整
向原红黑树插入值为 21 的新结点
由于父结点 22 是红色结点,因此这种情况打破了红黑树的规则 4(每个红色结点的两个子结点都是黑色),必须进行调整,使之重新复合红黑树的规则
调整的方法有变色和旋转,和常规的平衡二叉树一样,旋转包含左旋转和右旋转
变色:
为了重新符合红黑树的规则,尝试把红色结点变为黑色,或者把黑色结点变为红色
下图所表示的是红黑树的一部分(子树),新插入的结点 Y 是红色结点,它的父结点 X 也是红色的,不符合规则 4(每个红色结点的两个子结点都是黑色),因此我们可以把结点 X 从红色变为黑色
但是,仅仅把一个结点变色,会导致相关路径凭空多出一个黑色结点,这样就打破了规则 5.因此,我们需要对其它结点做进一步的调整,后文会详细说明
2.插入结点
红黑树插入新结点的时候可以分为 5 种不同的局面,每一种局面有不同的调整方案
局面1:新结点(A)位于树根,没有父结点
空心三角形代表结点下面的子树
这种局面,直接让新结点变色为黑色,规则 2 得到满足。同时,黑色的根结点使得每条路径上的黑色结点数目都增加了 1,所以并没有打破规则 5
局面 2:新结点(B)的父结点是黑色
这种局面,新插入的红色结点 B 并没有打破红黑树的规则,所以不需要做任何调整
局面 3:新结点(D)的父结点和叔叔结点都是红色
这种局面,两个红色结点 B 和 D 连续,违反了规则 4,因此我们先让结点 B 变为黑色
这样一来,结点 B 所在路径凭空多了一个黑色结点,打破了规则 5,因此我们让结点 A 变为红色:
这时候,结点 A 和 C 又成为了连续的红色结点,我们再让结点 C 变为黑色
经过上面的调整,这一局部重新符合了红黑树的规则
局面 4:新结点(D)的父结点是红色,叔叔结点是黑色或者没有叔叔,且新结点是父结点的右子结点,父结点(B)是祖父结点的左子结点
我们以结点 B 为轴,做一次左旋转,使得新结点 D 成为父结点,原来的父结点 B 称为 D 的左子结点
这样一来,进入了局面 5
局面 5:新结点(D)的父结点是红色,叔叔结点是黑色或者没有叔叔(下面的图片有误,C 应该为黑色),且新结点是父节点的左子结点,父结点(B)的祖父结点的左子结点
我们以结点 A 为轴,做一次右旋转,使得结点 B 成为祖父结点,结点 A 成为结点 B 的右子结点:
接下来,我们让结点 B 变为黑色,结点 A 变为红色
经过上面的调整,这一局部重新符合了红黑树的规则
如果局面 4 中的结点 B 是结点 A 的右子结点,则成为了局面 5 的镜像,原本的右旋转改为左旋转
如果局面 5 中的结点 B 是结点 A 的右子结点,则成为了局面 4 的镜像,原本的左旋转改为右旋转
我们不用刻意记住这些局面的处理方式,只需要有个大概了解即可
实例演示
给定下面这棵红黑树,新插入的结点是 21
显然,新结点 21 和它的父结点 22 是连续的红色结点,违背了规则 4,我们应该如何调整呢?
让我们回顾一下刚才讲的 5 种局面,当前的情况符合局面 3:新结点的父结点和叔叔结点都是红色
于是我们经过三次变色,22 变为黑色,25 变为红色,27 变为黑色
经过上面的调整,以结点 25 为根的子树符合了红黑树规则,但结点 25 和结点 17 成为了连续的红色结点,违背了规则 4
于是,我们把结点 25 看作一个新结点,正好符合局面 5 的镜像:新结点的父结点是红色,叔叔结点是黑色或者没有叔叔,且新结点是父结点的右子结点,父结点是祖父结点的右子结点
于是我们以根结点 13 为轴进行左旋转,使得结点 17 成为了新的根结点
接下来,让结点 17 变为黑色,结点 13 变为红色
如此一来,我们的红黑树变得重新符合规则
3.删除结点
第一步:如果待删除结点有两个非空的子结点,转化成待删除结点只有一个子结点(或者没有子结点)的情况
上面的例子是一棵红黑树的局部,标数字的三角形代表任意形态的子树,假设 8 是待删除结点
根据上文讲解的二叉查找树的删除流程,由于结点 8 有两个子结点,我们选择仅大于 8 的结点 10 复制到 8 的位置,结点颜色变成待删除结点的颜色
接下来我们需要删除红色的结点 10
红色结点 10 能成为仅大于 8 的结点,必定没有左子结点,所以问题转换成了待删除结点只有一个右子结点(或者没有子结点)的情况,接下来我们进入第二步
第二步:根据待删除结点和其唯一子结点的颜色,分情况处理
情况 1,自身是红色,子结点是黑色
这种情况最简单,按照二叉排序树的删除操作,删除结点 1 即可
情况 2,自身是黑色,子结点是红色:
这种情况也很简单,首先按照二叉排序树的删除操作,删除结点 1
此时,这条路径凭空减少了一个黑色结点,那么我们需要再把结点 2 变成黑色
情况 3,自身是黑色,子结点也是黑色,或者子结点是空叶子结点
这种情况最复杂,涉及到很多变化。首先我们还是按照二叉排序树的删除操作,删除结点 1
显然,这条路径上减少了一个黑色结点,而且结点 2 再怎么变色也解决不了
这时候我们进入第三步,专门解决父子双黑的情况
第三步:遇到双黑结点,在子结点顶替父结点之后,分成 6 种情况处理
情况 1,结点 2 为红黑树的根结点,删除后,子结点顶替成为根结点
此时所有路径都减少了一个黑色结点,并未打破规则,不需要调整
情况 2,结点 2 的父亲、兄弟、侄子结点都是黑色
此时,我们直接把结点 2 的兄弟结点 B 改为红色
这样依赖,原本结点 2 所在的路径少了一个黑色结点,现在结点 B 所在的路径也少了一个黑色结点,两边 “扯平” 了
可是,结点 A 以下的每一条路径都减少了一个黑色结点,与结点 A 之外的其它路径又造成了新的不平衡呢?
没关系,我们让结点 A 扮演原先结点 2 的角色,进行递归操作,重新判断各种情况
情况 3,结点 2 的兄弟结点是红色
首先以结点 2 的父结点 A 为轴,进行左旋转
然后将结点 A 变成红色、结点 B 变成黑色
这样的意义是什么呢?结点 2 所在的路径仍然少一个黑色结点呀?
别急,这样的变化有可能转换成情况 4、5、6 中的任意一种,在情况 4、5、6 中会进一步解决
情况 4,结点 2 的父结点是红色,兄弟和侄子结点是黑色
这种情况,我们直接让结点 2 的父结点 A 变成黑色,兄弟结点 B 变成红色
这样一来,结点 2 的路径补充了黑色结点,而结点 B 的路径并没有减少黑色结点,重新符合了红黑树的规则
情况 5,结点 2 的父结点随意,兄弟结点 B 是黑色右子结点,左侄子结点是红色,右侄子结点是黑色
这种情况下,首先以结点 2 的兄弟结点 B 为轴进行右旋转
接下来结点 B 变为红色,结点 C 变为黑色
这样的变化转换成了情况 6
情况 6,结点 2 的父结点随意,兄弟结点 B 是黑色右子结点,右侄子结点是红色
首先以结点 2 的父结点 A 为轴左旋转
接下来让结点 A 和结点 B 的颜色交换,并且结点 D 变为黑色
经过结点 2 的路径由(随意+黑)变成了(随意+黑+黑),补充了一个黑色结点
经过结点 D 的路径由(随意+黑+红)变成了(随意+黑),黑色结点并没有减少
所以,这时候重新符合了红黑树的规则
实例演示
给定下面这棵红黑树,待删除的结点是 17
第一步,由于结点 17 有两个子结点,子树当中仅大于 17 的结点是 25,所以把结点 25 复制到 17 的位置,保持黑色
接下来,我们需要删除原本的结点 25
这个情况正好对应于第二步的情况三,即待删除结点是黑色,子结点是空叶子结点
于是我们删除框框中结点 25,进入第三步
此时,框框中的结点虽然是空叶子结点,但仍然可以用于判断局面,当前局面符合情况 5 的镜像
于是我们通过左旋和变色,把子树转换成情况 6 的镜像
再经过右旋和变色,子树最终成为了下面的样子
这样一来,整棵二叉树又重新符合了红黑树的规则
4.与平衡二叉树的对比
平衡二叉树是严格平衡的二叉树,要求每个结点的左右高度差不超过 1.而红黑树则要宽松一些,只要求任何一条路径的长度不超过其它路径长度的 2 倍
正因为这个差别,二叉平衡树的查找效率更高,但平衡调整的成本也更高,在需要频繁查找时,选用二叉平衡树更合适,在需要频繁插入删除时,选用红黑树更合适