数据结构(五)红黑树

36 篇文章 0 订阅

讲红黑树之前,我们先来回顾一下 二叉查找树 (BST) ,先来看一下二叉查找树的特性:

  • 左子树上所有结点的值均小于或等于它的根结点的值。
  • 右子树上所有结点的值均大于或等于它的根结点的值。
  • 左、右子树也分别为二叉排序树。

我们来看一个典型的二叉查找树:

 这样的数据结构有什么好处呢?首先我们来测试一下,试着查找一下值为 10 的节点。

1. 查看根节点 9 。

2.由于10 > 9,因此查看右孩子13。

3.由于10 < 13,因此查看左孩子11。

4.由于10 < 11,因此查看左孩子10,发现10正是要查找的节点。

这种方式正是二分查找的思想,而且查找某个节点的最大次数等同于二叉查找树的高度。

在插入节点的时候,也是利用类似的方法,通过一层一层比较大小,最终找到新节点适合插入的位置的。

但是,二叉树仍然还存在一个缺陷:就是在插入新节点的时候,可能出现下面的这种情况:

 这种形态的二叉查找树,虽然也符合二叉查找树的特性,但是查找的性能大打折扣,几乎变成了线性的了。

那么这种缺陷有办法解决吗? 当然,那就是红黑树。

1 红黑树定义

红黑树 (Red Black Tree),是一种自平衡的二叉查找树。除了符合二叉查找树的基本特性之外,它还具备下列的附加特性

  1. 节点是红色或黑色。
  2. 根节点永远是黑色。
  3. 每个叶子节点都是黑色的空节点(NULL节点)。
  4. 如果节点是红色的,则它的子节点必须是黑色的。
  5. 从每个叶子到根的所有路径上不能有两个连续的红色节点,当然黑色的没说不可以。
  6. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
  7. 红黑树从根到叶子的最长路径不会超过最短路径的两倍。

我们来看一个典型的红黑树:

 大家可能觉得规则还挺多的,当然也正是这些规则的束缚,才保证了红黑树的自平衡。

2 调整红黑树

当执行插入或删除节点的时候,红黑树的这些规则可能会被打破。那么这个时候我们就需要作出一些调整,以保证规则的持续有效。

首先我们需要了解一下,什么情况下会破环红黑树的规则,什么情况下不会破坏红黑树规则呢?

2.1 不会破坏红黑树规则的情况

我们向原红黑树插入值为14的新节点:

由于父节点15是黑色节点,因此这种情况并不会破坏红黑树的规则,无需做任何调整。

2.2 会破坏红黑树规则的情况

向原红黑树插入值为21的新节点:

由于父节点22是红色节点,因此这种情况打破了红黑树的 规则4每个红色节点的两个子节点都是黑色的),必须进行调整,使之重新符合红黑树的规则。 

2.3 红黑树的调整方法

  • 变色
  • 左旋转 (逆时针)
  • 右旋转(顺时针)

注意,往往在实际的调整过程中,会综合用到多种方式,并且是重复执行。本小结最后,会介绍这几种方式在什么情况下如何选择的技巧。

2.4 变色

变色方式:为使得插入操作之后不满足规则的红黑树再次符合规则,我们可以尝试把红色节点变为黑色,黑色节点变为红色。

下图所表示的是红黑树的一部分,需要注意节点 25 并非根节点。因为节点 21 和节点 22 连续出现了红色,不符合规则4,所以把节点 22 从红色变成黑色:

然后我们发现 22 变为黑色之后,又导致了 规则 6,所以我们还需要继续进行调整,将 25 从黑色变成红色:

 此时还是不行,因为节点 25 和节点 27 又形成了两个连续的红色节点,需要继续把节点 27 从红色变成黑色:

 以上对于 25 为跟节点的这个子树,就调整完成了。

2.5 左旋转

左旋转 即逆时针旋转红黑树的两个节点,使得父节点被自己的右孩子节点取代,而父节点自己却成为了原父节点的左孩子节点,来看下面这张图:

图中,身为右孩子的 Y 节点取代了 身为父节点 X 节点的位置,而 X 变成了自己的左孩子。此为左旋转。

2.5 右旋转

右旋转 即顺时针旋转红黑树的两个节点,使得父节点被自己的左孩子节点取代,而父节点自己成为原父节点的右孩子。大家看下图:

图中,身为左孩子的 Y 取代了 X 的位置,而 X 变成了自己的右孩子,此为右旋转。 

2.6 调整红黑树的经典例子

我们将要在下面的红黑树中插入一个节点 21 :

首先,我们需要做的是变色,即 把节点 25 及其下方的节点变色: 

此时 17 和 25 是连续的两个红色节点,那么把 17 变成黑色节点可以吗?当然是不可以,这样就打破了规则4,而且根据规则2(根节点总是黑色),也不可能把节点 13 变成红色节点。 

因此,变色的方式,已经无法继续解决该问题了,需要借助 左旋转 对 13 和 17 两个节点进行一下调整:

那么由左旋转的机制可知,我们需要将 17 作为红黑树的根节点 ,13 不再是根结点,而是需要调整为 17 根结点的左孩子节点。而 15 这个子树则需要调整为 13 节点的右孩子节点。调整之后的红黑树就像下面这样:

 由于根节点 17 必须是黑色节点,所以需要变色,同时 13 节点下的左子树也都要进行变换 (注意:叶节点不动,始终为黑色),变色后的结果如下:

那么现在的红黑树,是不是就OK了呢?当然没有,因为 (17 -> 8 -> 6 -> Null) 的黑色节点数是 4,而其他路径下的黑色节点个数是 3,明显违背了 规则 6。所以我们还要继续调整。

接下来我们需要采用 右旋转 的方式, 对 13 和 8 两个节点进行转换:

转换细节为:我们需要将 8 作为 根节点 17 的左孩子节点,13 作为 8 的右孩子节点,并且需要将 11 作为 13 的左孩子节点。变换之后的红黑树就变成了下面这样:

 最后我们还需要再做一次 颜色转换的操作,所以最终的红黑树变成了下面的这样:

如此一来,我们的红黑树变得重新符合规则。这一个例子的调整过程比较复杂,经历了如下步骤:变色 -> 左旋转 -> 变色 -> 右旋转 -> 变色 。

红黑树中换色、左旋转、有旋转技巧

其实红黑树的关键玩法就是弄清楚这三种调整方式的规则。

假设我们插入的新节点为 X:

  1. 将新插入的节点标记为红色
  2. 如果 X 是根结点(root),则标记为黑色
  3. 如果 X 的 parent 不是黑色,同时 X 也不是 root:
  • 3.1 如果 X 的 uncle (叔叔) 是红色
    • 3.1.1 将 parent 和 uncle 标记为黑色
    • 3.1.2 将 grand parent (祖父) 标记为红色
    • 3.1.3 让 X 节点的颜色与 X 祖父的颜色相同

           就像下面这张图:

跟着上面的公式走:

  1. 将新插入的 X 节点标记为红色
  2. 发现 X 的 parent (P) 同样为红色,这违反了红黑树的第三条规则「不能有两个连续相邻的红色节点」
  3. 发现 X 的 uncle (U) 同样为红色
  4. 将 P 和 U 标记为黑色
  5. 将 X 和 X 的 grand parent (G) 标记为相同的颜色,即红色,继续重复公式 2、3
  6. 发现 G 是根结点,标记为黑色
  7. 结束

刚刚说了 X 的 uncle 是红色的情况,接下来要说是黑色的情况 

  • 3.2 如果 X 的 uncle (叔叔) 是黑色,我们要分四种情况处理
    • 3.2.1 左左 (P 是 G 的左孩子,并且 X 是 P 的左孩子)
    • 3.2.2 左右 (P 是 G 的左孩子,并且 X 是 P 的右孩子)
    • 3.2.3 右右 (和 3.2.1 镜像过来,恰好相反)
    • 3.2.4 右左 (和 3.2.2 镜像过来,恰好相反)

当出现 uncle 是黑色的时候我们第一步要考虑的是 旋转 ,这里先请小伙伴不要关注红黑树的第 6 条规则,主要是为了演示如何旋转的:

左左情况

这种情况很简单,想象这是一根绳子,手提起 P 节点,然后变色即可

左右情况

左旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的左孩子,然后再应用 左左情况 

右右情况

与左左情况一样,想象成一根绳子

右左情况

右旋: 使 X 的父节点 P 被 X 取代,同时父节点 P 成为 X 的右孩子,然后再应用 右右情况 

 

4 红黑树的应用

TreeMap、TreeSet以及JDK1.8的HashMap底层都用到了红黑树。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值