红黑树解析

一、红黑树定义

红黑树是一种特殊的平衡二叉树。
因为是平衡二叉树,所以红黑树有以下性质:
对于任意节点,其左子树的值都小于该节点,其右子树的值都大于该节点

除了上述特点之外红黑树还特有五条性质:
(1)每个节点要么是黑色,要么是红色
(2)根节点是黑色
(3)叶节点是黑色(规定只有NULL才能成为叶节点)
(4)红色节点的两个子节点都是黑色
(5)任意一个节点到叶节点的路径都包含数量相同的黑色节点

下图是一棵示例红黑树:

如果我们把叶子节点补上的话如下图:

红黑树定义最重要的是第5条,指的是任意一个节点,无论红黑,从它出发到达任意一个叶子节点(也就是NULL)所形成的路径,都是包含相同数量的黑色节点的。我们举个例子来理解第5条:

比如节点6,从6出发到达叶子节点一共有五条路:

  • 6—>3—>1—>1.left=NULL
  • 6—>3—>1—>1.right=NULL
  • 6—>3—>3.right=NULL
  • 6—>9—>9.left=NULL
  • 6—>9—>9.right=NULL

这五条路都包含相同数量的黑色节点数:2个

二、红黑树查询

因为红黑树是二叉平衡树,所以查找过程和普通的二叉平衡树一样。
红黑树查找的平均复杂度是O(logn)。

三、红黑树插入

3.1 插入的步骤

红黑树插入数据分为三步:查找、插入、修正
(1)查找:依照查找步骤找到要插入的位置
(2)插入:在找到的位置处插入一个红色节点
(3)修正:利用变色、旋转等方法对红黑树进行调整,使其依然满足定义

你肯定会问,为什么要插入红色节点而不是黑色节点?

这是因为红黑树第5条严格规定了黑色节点数量的要求,所以插入黑色节点一定会让插入节点那条路比其他的路多一个黑色节点出来,这样每插入一次就要调整一次树,太麻烦了。插入红色节点在某些时刻就不会破坏这个平衡,调整的次数也会少一些。

3.2 修正方式

红黑树的修正调整分为两种:变色、旋转。
变色很好理解:黑色变红色、红色变黑色。
旋转分为左旋和右旋。
(1)左旋
左旋你可以理解为扯着这两个节点逆时针转,顺带着这两个节点的子树也要进行调整。
在旋转过程中A.left和B.right的位置随着旋转是不受影响的,但是B.left旋转的时候会和A撞到一起去,所以为了协调,就把B.Left放到A的右子树。

举个例子,以2位中心左旋:

(2)右旋
右旋你可以理解为扯着这两个节点顺时针转,顺带着这两个节点的子树也要进行调整。
在旋转过程中A.right和B.left的位置随着旋转是不受影响的,但是B.right旋转的时候会和A撞到一起去,所以为了协调,就把B.right放到A的左子树。

举个例子,以5为中心右旋:

3.3 插入情况总结—5类

红黑树插入具体有5种情况,这里先给出总结图,然后我们再具体描述每一种情况。
其中图中N(New)表示新插入的节点,F(Father)表示插入位置的父节点,U(Uncle)表示插入节点的叔叔节点,也就是父节点的对称节点,G(Grandfather)表示插入节点的祖父节点,也就是父节点的父节点)。
插入节点统一标记为绿色描边。

3.4 5类插入情况详解

——————————————————————————————————————
情况1:

红黑树是一棵空树,插入的节点是根节点。
因为我们规定根节点必须是黑色,所以只需要把节点变色即可。

——————————————————————————————————————
情况2:
插入节点的父节点是黑色。
因为插入节点是红色,不影响线路上具体的黑色节点数,所以可以直接插入,无需任何更改。

举个例子:
比如在节点15的左子树处插入一个新节点14,因为15是黑色节点,所以可以直接插入。

——————————————————————————————————————
情况3:
插入节点的父节点是红色,且叔叔节点存在。

首先,如果如果父节点是红色,那叔叔节点也一定是红色。因为如果叔叔节点是黑色,那说明通过叔叔节点的路径比通过父节点的路径多了一个黑色节点,这样就不满足定理第5条了。
而由红黑树定理第4条可知红色节点的子节点只能是黑色,所以祖父节点一定是黑色,而我们新添加的节点也是红色,和父节点冲突了,所以需要进行调整。
调整的策略如下:

这里注意的是,我们调整的策略很简单,把父节点和叔叔节点变成黑色,把祖父节点变成红色,这样能够保证通过父节点和叔叔节点的路径和原来一样,只包含一个黑色节点。
但是把祖父节点变成红色,可能会和上面的节点继续冲撞,所以我们要以祖父节点为中心,继续向上调整,直到不再冲突为止。

下面的动图展示了实际例子:

——————————————————————————————————————
情况4:
插入节点的父节点是红色,叔叔节点不存在,且插入节点和父节点同向。

什么叫同向?
父节点在祖父节点的左子树,新节点在父节点的左子树;或者
父节点在祖父节点的右子树,新节点在父节点的右子树。


调整策略如下:

下面的动图展示了实际例子:

——————————————————————————————————————
情况5:
插入节点的父节点是红色,叔叔节点不存在,且插入节点和父节点同异向。

什么叫异向?
父节点在祖父节点的左子树,新节点在父节点的右子树;或者
父节点在祖父节点的右子树,新节点在父节点的左子树。


调整策略如下:


情况5只需要旋转一次就会变成和情况4相同的情况,这时利用情况4的调整策略即可。

下面的动图展示了实际例子:

四、红黑树删除

4.1 删除的步骤

红黑树删除步骤依然分为三步:查找、删除、修正。
(1)查找:依照查找步骤找到要删除的节点位置
(2)删除:直接删除掉节点
(3)修正:利用变色、旋转等方法对红黑树进行调整,使其依然满足定义
我们下面讲解为了清楚,是先修正再删除的,事实上这个不影响最终结果。

4.1 删除情况总结—5大类10小类

删除节点按颜色分有2种:黑色、红色。
删除节点按状态分有3种:该节点有2个子节点、1个子节点、0个子节点。

正常删除的节点分类应该分为2x3=6大类,但是按照红黑树的定义,红色且只有一个子节点这种状态是不存在的。这是因为红色节点的子节点只能是黑色,如果一个红色节点左子树有黑色节点,右子树为空,那就说明进入左子树的路径比右子树多了一个黑色节点,违背了红黑树原理,所以这种状态不存在。

所以正常删除的节点分为5大类:黑色0个子节点、黑色1个子节点、黑色2个子节点、红色0个子节点、红色2个子节点。
这5大类中唯独黑色0个子节点是比较复杂的,我们将其细分成6小种情况。
所以对于红黑树删除,一共分为5大类,10小类。其中前6种小类是删除黑色0个子节点,前8种小类是删除黑色节点,后2种小类是删除红色节点。
如下图所示,其中D(Delete)代表即将被删除的节点,F(Father)代表删除节点的父节点,B(Brother)代表删除节点的兄弟节点。
删除节点统一标记为黄色描边。

我们接下来下图的示例来说明这10种情况:

4.2 删除黑色节点之0个子节点

——————————————————————————————————————
情况1:
兄弟节点是黑色,且有同向的红色子节点。(同向、异向已经在插入时解释过了)
这里父节点的颜色是无所谓的。

调整策略如下,一共4种只展示了2种,剩下的是一样的:

下面的动图展示了实际例子:

——————————————————————————————————————
情况2:
兄弟节点是黑色,且有异向的红色子节点。(同向、异向已经在插入时解释过了)
这里父节点的颜色是无所谓的。

调整策略如下:

可以看到,只要先把兄弟节点和兄弟节点的子节点换色,然后以兄弟节点为中心进行旋转,就会变成第1种情况,然后采取第1种情况的策略就行。

下面的动图展示了实际例子:

——————————————————————————————————————
情况3:
兄弟节点是黑色,且没有子节点,同时父节点是红色。

调整策略如下:

下面的动图展示了实际例子:

——————————————————————————————————————
情况4:
兄弟节点是红色(这里依照红黑树的定义,父节点一定是黑色,并且兄弟节点一定有两个黑色子节点)。

调整策略如下:

下面的动图展示了实际例子:

——————————————————————————————————————
情况5:
父节点、兄弟节点、删除节点都是黑色,且兄弟节点没有子节点。

调整策略如下:

这里需要注意的是:
可以看到调整之前,由F进入到D或B终止的路径包括了两个黑色节点(F和D、F和B),但是我们调整完了之后通过F的路径只包含了一个黑色节点。这就会造成通过F的路径比不通过F的路径少一个黑色节点,破坏了红黑树定义。
解决的方案是:把F假装成一个待删除的新节点,继续向上调整(但不删除F),直到调整完成。

下面的动图展示了实际例子:

——————————————————————————————————————
情况6:
我们要删除的节点是根节点,直接删除即可。

4.3 删除黑色节点之1个子节点

——————————————————————————————————————
情况7:
待删除的节点是黑色节点,且有一个子节点(那这个子节点一定是红色)。
父节点的颜色无所谓。

调整策略如下:

下面的动图展示了实际例子:

4.4 删除黑色节点之2个子节点

——————————————————————————————————————
情况8:
待删除的节点是黑色,且有两个子节点。
这两个子节点的颜色是什么搭配都无所谓。

两个子节点的处理方式是:找出后继节点,和当前节点交换。

什么是后继节点?
就是一个节点左子树的最大值,或者右子树的最小值。

我们找到后继节点和根节点交换之后,根节点就变到了后继节点的位置。后继节点因为是左子树最大的(或者是右子树最小的),所以后继节点最多只会有1个子节点,这样我们就可以用其他情况来处理了。

下图展示了挑选左子树最大值为后继节点。

下图展示了挑选右子树最小值为后继节点。

下面的动图展示了实际例子:

4.5 删除红色节点之0个子节点

——————————————————————————————————————
情况9:
待删除的节点是红色节点,且没有子节点。

因为红色节点删除掉是不影响黑色节点数量的,所以直接删除即可。

下面的动图展示了实际例子:

4.6 删除红色节点之2个子节点

——————————————————————————————————————
情况10:
待删除的节点是红色节点,且有2个子节点。

删除有2个子节点的红色节点和第8种情况是一样的,找到一个后继节点和根节点交换,然后问题就转换成了处理后继节点的位置。

下面的动图展示了实际例子:

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值