红黑树(RBTree)

☆☆☆☆☆ 在学习红黑树之前先了解平衡二叉树: 平衡二叉树(AVL)

一、什么是红黑树?

知道 ALV 树是一种严格按照定义来实现的平衡二叉查找树,所以它查找的效率非常稳定,为 O(log n),由于其严格按照左右子树高度差不大于 1 的规则,插入和删除操作中需要大量且复杂的操作来保持 ALV 树的平衡(左旋和右旋),因此 ALV 树适用于大量查询,少量插入和删除的场景中

那么假设现在有这样一种场景:大量查询,大量插入和删除,现在使用 ALV 树就不太合适了,因为 ALV 树大量的插入和删除会非常耗时间,那么我们是否可以降低 ALV 树对平衡性的要求从而达到快速的插入和删除呢?

答案肯定是有的,红黑树这种数据结构就应运而生了(因为ALV树是高度平衡的,所以查找起来肯定比红黑树快,但是红黑树在插入和删除方面的性能就远远不是ALV树所能比的了),红黑树是一种接近平衡的二叉树(说它是接近平衡因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的5条性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡)。

红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1978年发明,在当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。

二、红黑树的特性

在讲解红黑树性质之前,先简单了解一下几个概念:

  • parent:父节点
  • sibling:兄弟节点
  • uncle:叔父节点( parent 的兄弟节点)
  • grand:祖父节点( parent 的父节点)

1)每个结点或红或黑
2)根结点是黑色
3)空叶子结点是黑色,注意这里的空叶子节点指的是最底层的空节点(外部节点),下图中的那些 NIL 节点才是叶子节点,NIL 节点的父节点在红黑树里不将其看作叶子节点
4)如果一个结点是红色,那么它的子节点是黑色
5)从任意一个结点出发到空的叶子结点经过的黑结点个数相同

红色属性 说明,红色节点的孩子,一定是黑色。 但是,RBTree 黑色节点的孩子,可以是红色,也可以是黑色,具体如下图。

叶子属性 说明, 叶子节点可以是空nil ,AVL的叶子节点不是空的,具体如下图。
在这里插入图片描述
基于上面的原则,我们一般在插入红黑树节点的时候,会将这个节点设置为红色,

原因参照最后一条原则: 红色破坏原则的可能性最小,如果是黑色, 很可能导致这条支路的黑色节点比其它支路的要多1,破坏了平衡。

记忆要点:
可以按照括号里边的分类,记住 红黑树的几个原则:

  • (颜色属性)性质1:节点非黑即红

  • (根属性)性质2:根节点一定是黑色

  • (叶子属性)性质3:叶子节点(NIL)一定是黑色

  • (红色属性)性质4:每个红色节点的两个子节点,都为黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

  • (黑色属性)性质5:从任一节点到其每个叶子的所有路径,都包含相同数目的黑色节点。

黑色属性,可以理解为平衡特征, 如果满足不了平衡特征,就要进行平衡操作。

根据上面的性质,来判断一下下面这课树是不是红黑树
在这里插入图片描述

上面这棵树首先很容易就能知道是满足性质1-4条的,关键在于第5条性质,可能乍一看好像也是符合第5条的,但实际就会陷入一个误区,直接将图上的最后一层的节点看作叶子节点,这样看的话每一条从根节点到叶子结点的路径确实都经过了3个黑节点。

但实际上,在红黑树中真正被定义为叶子结点的,是那些空节点,如下图。
在这里插入图片描述
这样一来,路径1有4个黑色节点(算上空节点),路径2只有3个黑色节点,这样性质5就不满足了,所以这棵树并不是一个红黑树节点。

三、黑色完美平衡

红黑树并不是一颗AVL平衡二叉搜索树,从图上可以看到,根节点 P 的左子树显然比右子树高

根据 红黑树的特性 5,从任一节点到其每个叶子的所有路径,都包含相同数目的黑色节点, 说明:

RBT 的 左子树和右子树的黑节点的层数是相等的
红黑树的平衡条件,不是以整体的高度来约束的,而是以黑色 节点的 高度,来约束的。

所以称红黑树这种平衡为黑色完美平衡。
在这里插入图片描述
看看黑色完美平衡的效果,

去掉 RBT 中的红色节点,会得到 一个四叉树, 从根节点到每一个叶子,高度相同,就是 RBT 的 root 到叶子的黑色路径长度。
在这里插入图片描述

四、红黑树的恢复平衡过程的三个操作

一旦红黑树5个原则有不满足的情况,我们视为平衡被打破,如何 恢复平衡?

靠它的三种操作:变色、左旋、右旋。

1.变色

节点的颜色由红变黑或由黑变红。(这个操作很好了解)

2.左旋

以某个结点作为支点(pivot),其父节点(子树的 root)旋转为自己的左子树(左旋),pivot 的原左子树变成 原 root 节点的 右子树,pivot 的原右子树保持不变。
在这里插入图片描述


在这里插入图片描述

3. 右旋

以某个结点作为支点(pivot),其父节点(子树的root)旋转为自己的右子树(右旋),pivot的原右子树变成 原root节点的 左子树,pivot的原左子树保持不变。
在这里插入图片描述
在这里插入图片描述

红黑树的左旋、右旋操作,AVL树的左旋,右旋操作 差不多

五、红黑树插入节点情景分析

红黑树的节点结构

先看看红黑树的节点结构

以HashMap中的红黑树的结构定义为例子:

  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
        }


/**
 * Nodes for use in TreeBins
 */
static final class TreeNode<K,V> extends Node<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        super(hash, key, val, next);
        this.parent = parent;
    }

默认新插入的节点为红色:

因为父节点为黑色的概率较大,插入新节点为红色,可以避免颜色冲突

场景1:红黑树为空树

直接把插入结点作为根节点就可以了

另外:根据红黑树性质 2根节点是黑色的。还需要把插入节点设置为黑色。

场景2:插入节点的Key已经存在

更新当前节点的值,为插入节点的值。
在这里插入图片描述

情景3:插入节点的父节点为黑色

由于插入的节点是红色的,当插入节点的父节点是黑色时,不会影响红黑树的平衡,

所以: 直接插入无需做自平衡。
在这里插入图片描述

情景4:插入节点的父节点为红色

根据性质2:根节点是黑色。

如果插入节点的父节点为红色节点,那么该父节点不可能为根节点,所以插入节点总是存在祖父节点(三代关系)。

根据性质4:每个红色节点的两个子节点一定是黑色的。不能有两个红色节点相连。

此时会出现两种状态:

  • 父亲和叔叔为红色
  • 父亲为红色,叔叔为黑色

如图
在这里插入图片描述

场景4.1:父亲和叔叔为红色节点

根据性质4:红色节点不能相连 ==> 祖父节点肯定为黑色节点:

父亲为红色,那么此时该插入子树的红黑树层数的情况是:黑红红。

因为不可能同时存在两个相连的红色节点,需要进行 变色, 显然处理方式是把其改为:红黑红

变色 处理:黑红红 ==> 红黑红

  1. 将F和V节点改为黑色
  2. 将P改为红色
  3. 将P设置为当前节点,进行后续处理
    在这里插入图片描述
    可以看到,将P设置为红色了,

如果P的父节点是黑色,那么无需做处理;

但如果P的父节点是红色,则违反红黑树性质了,所以需要将P设置为当前节点,继续插入操作, 作自平衡处理,直到整体平衡为止。

场景4.2: 叔叔为黑色,父亲为红色,并且插在父亲的左节点

分为两种情况

  • LL 红色插入

叔叔为黑色,或者不存在(NIL)也是黑节点,并且节点的父亲节点是祖父节点的左子节点

注意: 单纯从插入来看,叔叔节点非红即黑(NIL节点),否则破坏了红黑树性质5,此时路径会比其他路径多一个黑色节点。
在这里插入图片描述

场景4.2.1 LL型失衡

细分场景 1: 新插入节点,为其父节点的左子节点(LL红色情况), 插入后 就是LL 型失衡
在这里插入图片描述

自平衡处理

  1. 变颜色:将 F 设置为黑色,将 P 设置为红色
  2. 对 F 节点进行右旋

在这里插入图片描述

场景4.2.2 LR型失衡

细分场景 2: 新插入节点,为其父节点的右子节点(LR红色情况), 插入后 就是LR 型失衡
在这里插入图片描述

自平衡处理:

  1. 对F进行左旋
  2. 将F设置为当前节点,得到 LL 红色情况
  3. 按照LL红色情况处理(1. 变色 2. 右旋P节点)

在这里插入图片描述

情景4.3:叔叔为黑节点,父亲为红色,并且父亲节点是祖父节点的右子节点

在这里插入图片描述

情景4.3.1:RR型失衡

新插入节点,为其父节点的右子节点(RR红色情况)
在这里插入图片描述
自平衡处理:

  1. 变色:将F设置为黑色,将P设置为红色
  2. 对P节点进行左旋

在这里插入图片描述

情景4.3.2:RL型失衡

新插入节点,为其父节点的左子节点(RL红色情况)
在这里插入图片描述
自平衡处理:

  1. 对F进行右旋
  2. 将F设置为当前节点,得到RR红色情况
  3. 按照RR红色情况处理(1.变色 2.左旋 P节点)

在这里插入图片描述

六、RBT面试题:

问:有了二叉搜索树,为什么还需要平衡二叉树?

二叉搜索树容易退化成一条链

这时,查找的时间复杂度从O ( log n)也将退化成O ( N )

引入对左右子树高度差有限制的平衡二叉树 AVL,保证查找操作的最坏时间复杂度也为O ( log n)

问:有了平衡二叉树,为什么还需要红黑树?

AVL的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡

在频繁进行插入/删除的场景中,频繁的旋转操作使得AVL的性能大打折扣

红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,

整体性能优于AVL

  • 红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决
  • 红黑树的红黑规则,保证最坏的情况下,也能在O ( log n)时间内完成查找操作。

问:红黑树那几个原则,你还记得么?

可以按照括号里边的分类,记住 红黑树的几个原则:

  • (颜色属性)节点非黑即红
  • (根属性)根节点一定是黑色
  • (叶子属性)叶子节点(NIL)一定是黑色
  • (红色属性)每个红色节点的两个子节点,都为黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
  • (黑色属性)从任一节点到其每个叶子的所有路径,都包含相同数目的黑色节点。

问:红黑树写入操作 ,是如何找到它的父节点的?

红黑树的节点 TreeNode它就是继承Node结构,

先看看红黑树的节点结构

以HashMap中的红黑树的结构定义为例子:

  static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        volatile V val;
        volatile Node<K,V> next;
        }


/**
 * Nodes for use in TreeBins
 */
static final class TreeNode<K,V> extends Node<K,V> {
    TreeNode<K,V> parent;  // red-black tree links
    TreeNode<K,V> left;
    TreeNode<K,V> right;
    TreeNode<K,V> prev;    // needed to unlink next upon deletion
    boolean red;

    TreeNode(int hash, K key, V val, Node<K,V> next,
             TreeNode<K,V> parent) {
        super(hash, key, val, next);
        this.parent = parent;
    }

TreeNode在Node基础上加了几个字段,分别指向父节点parent,然后指向左子节点left,还有指向右子节点的right,

然后还有表示颜色red属性

红黑树的插入操作:

  • 首先是找到一个合适的插入点,就是找到插入节点的父节点,
  • 由于红黑树 它又满足BST二叉查找树的 有序特性,这个找父节点的操作和二叉查找树是完全一致的。
  • 二叉查找树,左子节点小于当前节点,右子节点大于当前节点,
  • 然后每一次向下查找一层就可以排除掉一半的数据,查找的效率在log(N)
  • 最终查找到nil节点或者 key一样的节点。
  • 如果最终查找到 key一样的节点,进行更新操作。这个TreeNode.key 与当前 put.key 完全一致。这就不需要插入,替换value就可以了,父节点就是当前节点的父节点
  • 如果最终查找到nil节点,进行插入操作。nil节点的父节点,就是当前节点的父节点,把插入的节点替换nil节点。然后进行红黑树的 平衡处理。

问:红黑树的有那些内部操作

变色 把一个红色的节点变成黑色,或者把一个黑色的节点变成红色,就是对这个节点的变色。

旋转 与平衡二叉树的旋转操作类似。

红黑树与AVL树区别

1、调整平衡的实现机制不同

红黑树根据路径上黑色节点数目一致,来确定是否失衡,如果失衡,就通过变色和旋转来恢复

AVL根据树的平衡因子(所有节点的左右子树高度差的绝对值不超过1),来确定是否失衡,如果失衡,就通过旋转来恢复

2、红黑树的插入效率更高

红黑树是用非严格的平衡来换取增删节点时候旋转次数的降低,任何不平衡都会在三次旋转之内解决,

红黑树并不追求“完全平衡”,它只要求部分地达到平衡要求,降低了对旋转的要求,从而提高了性能

而AVL是严格平衡树(高度平衡的二叉搜索树),因此在增加或者删除节点的时候,根据不同情况,旋转的次数比红黑树要多。

所以红黑树的插入效率更高

3、红黑树统计性能比AVL树更高

红黑树能够以O(log n) 的时间复杂度进行查询、插入、删除操作。

AVL树查找、插入和删除在平均和最坏情况下都是O(log n)。

红黑树的算法时间复杂度和AVL相同,但统计性能比AVL树更高,

4、适用性:AVL查找效率高

如果你的应用中,查询的次数远远大于插入和删除,那么选择AVL树,如果查询和插入删除次数几乎差不多,应选择红黑树。

即,有时仅为了排序(建立-遍历-删除),不查找或查找次数很少,R-B树合算一些。

References

目前最详细的红黑树原理分析(大量图片+过程推导!!!)

【数据结构】史上最好理解的红黑树讲解,让你彻底搞懂红黑树

红黑树

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值