zoukankan      html  css  js  c++  java
  • 红黑树

    红黑树(Red Black Tree)是一种自平衡的二叉搜索树(Self-balancing Binary Search Tree)。以前也叫平衡二叉B树(Symmetric Binary B-Tree)

    1、前言

    树的结构

    1.1 平衡二叉搜索树

    平衡二叉搜索树(Balanced Binary Search Tree),简称BBST,常见的平衡二叉搜索树是AVL和红黑树

    • 二叉搜索树

    二叉搜索树(Binary Search Tree)是二叉树的一种,简称BST。又称为二叉查找树、二叉排序树

    他的特点是任何一个结点的值都大于其左子树的所有结点的值,任何一个结点的值都小于其右子树的所有结点

    • 平衡

    平衡(Balance):就是当结点数量固定时,左右子树的高度越接近,这颗二叉树越平衡(高度越低)。最理想的平衡就是完全二叉树/满二叉树,高度最小的二叉树。

    一颗二叉搜索树平均时间复杂度可以认为是树的高度O(h)。

    像左边这棵树,结点左右子树的高度相差1,属于平衡二叉搜索树,O(h)=O(logn)

    右边的高度达到了最大,已经退化为链表,O(h)=O(n)

    • 改进二叉搜索树

    当二叉树退化成链表时,性能很低,所以我们需要在结点插入、删除操作之后,想办法让二叉搜索树恢复平衡(减小树的高度)

    但是如果为了追求最理想的平衡,而增加了时间复杂度也不是很有必要,因此比较合理的方案就是:用尽量少的调整次数达到适度平衡。

    由此引申出 AVL 树的概念。

    1.2 AVL树

    AVL树是最早发明的自平衡二叉搜索树之一,它取名字两位发明家的名字:G.M.Adelson-Velsky 和 E.M.Landis

    平衡因子(Balance Factor):某结点的左右子树的高度差

    每个叶子结点的平衡因子都是0、看这棵树,红色数据标注了每个结点对应的平衡因子

    举例:

    • 8的左子树高度为2,右子树高度为1,因此平衡因子为1
    • 5的左子树高度0,右子树高度为3,因此平衡因子为-3
    • 4的左子树高度为2,右子树高度4,因此平衡因子为-2

    这颗AVL树和它每个结点对应的平衡因子

    可以看出AVL树具有以下特点:

    1. 每个结点的平衡因子只可能是-1、0、1(如果绝对值超过1,则认为是失衡)
    2. 每个结点的左右子树高度差不超过1
    3. 搜索、插入、删除的时间复杂度为O(logn)

    1.3 B树

    B树(Balanced Tree)是一种平衡的多路搜索树,多用于文件系统、数据库的实现

    一个简单的3阶B树:

    特点:

    1. 1个结点可以存储超过2个元素,可以拥有超过2个子节点
    2. 拥有二叉搜索树的一些特性
    3. 平衡,每个结点的所有子树高度一致

    m阶B树的性质(m>=2)

    m阶B树指的是一个结点最多拥有m个子结点。假设一个结点存储的元素个数为x,那么如果这个结点是:

    • 根节点:1 ≤ x ≤ m - 1
    • 非根结点:┌ m / 2 ┐ - 1 ≤ x ≤ m - 1

    果有子结点,子结点个数为 y = x + 1,那么如果这个结点是:

    • 根结点:2 ≤ y ≤ m
    • 非根结点:┌ m / 2 ┐ ≤ y ≤ m

    向上取整(Ceiling),指的是取比自己大的最小整数,用数学符号 ┌ ┐ 表示;
    向下取整(Floor),指的是取比自己小的最大整数,用数学符号 └ ┘ 表示。

    比如 m=3,子结点个数 2≤y≤3,这个 B 树可以称为(2,3)树、2-3 树。
    比如 m=4,子结点个数 2≤y≤4,这个 B 树可以称为(2,4)树、2-3-4 树。比如 m=5,子结点个数 3≤y≤4,这个 B 树可以称为(3,5)树、3-4-5 树

    1.4 B树VS二叉搜索树

    这是一棵二叉搜索树,通过某些父子结点合并,恰好能与上面的 B 树对应。我们可以得到结论:

    • B 树和二叉搜索树,在逻辑上是等价的
    • 多代结点合并,可以获得一个超级结点,且 n 代合并的超级结点,最多拥有 (2^n) 个子结点 (至少是 (2^n) 阶 B 树)

    2、红黑树的定义和性质

    红黑树是一种含有红黑结点并能自平衡的二叉搜索树

    为了保证平衡,红黑树必须满足一下性质:

    • 每个结点要么是红色、要么是黑色
    • 根节点必须是黑色
    • 叶结点(外部结点、空结点)是黑色
    • 红色结点不能连续(红色结点的孩子和父亲都是黑色)
    • 对于每个结点,从该点到nil(数尾端,java中为null的结点)的任何路径都包含所相同个数的黑色结点

    3、红黑树与B树的等价变换

    对红黑树做等价变换,即将所有的红色结点上升一层与它的父节点放在同一行,这就很像一颗4阶B树,转换效果如下:

    得出结论:

    • 红黑树与4阶B树(2-3-4树)具有等价性
    • 黑色结点与红色子节点融合在一起,形成1个B树结点
    • 红黑树的黑色结点个数与4阶B树的结点总个数相等

    4、红黑树的基本操作

    当我们对一棵平衡二叉搜索树进行插入、删除的时候,很可能会让这棵树变得失衡(最坏可能导致所有祖先结点失衡,但是父结点和非祖先结点都不可能失衡)。

    为了达到平衡,需要对树进行旋转。而红黑树能够达到自平衡,靠的也就是左旋、右旋和变色。

    旋转操作是局部的。当一侧子树的结点少了,向另一侧“借”一些结点;当一侧子树的结点多了,则“租”一些结点给另一侧。

    • N-node:当前结点
    • P-parent:父节点
    • S-sibling:兄弟结点
    • U-uncle:叔叔结点(p的兄弟结点)
    • G-grand:爷爷结点(p的父节点)

    4.1 左旋

    左旋指的是以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。

    4.2 右旋

    右旋指的是以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。

    4.3 变色

    变色指的是结点的颜色由红色变为黑色或由黑色变为红色

    4.4 变换规则

    将左旋、右旋和变色结合起来,得到一套变换规则

    变色:如果当前结点的父结点和叔叔结点是红色,那么:

    • 把父结点和叔叔结点变为黑色
    • 把祖父结点变为红色
    • 把指针定义到祖父结点

    左旋:当前结点是右子树,且父结点是红色,叔叔结点是黑色,对它的父结点左旋

    右旋:点前结点是左子树,且父结点是红色,叔叔结点是黑色,那么:

    • 把父结点变为黑色
    • 把爷爷结点变为红色
    • 对爷爷结点右旋

    5、红黑树搜索

    由于红黑树本来就是平衡二叉搜索树,并且搜索也不会破坏树的平衡,所以搜索算法也与平衡二叉树搜索一致

    具体步骤如下:

    • 从根结点开始检索,把根结点设置为当前结点。
    • 若当前结点为空,返回 nil。
    • 若当前结点不为空,比较当前结点 key 与搜索 key 的大小。
    • 若当前结点 key 等于搜索 key,那么该 key 就是搜索目标,返回当前结点。
    • 若当前结点 key 大于搜索 key,把当前结点的左子结点设置为当前结点,重复步骤 2。
    • 若当前结点 key 小于搜索 key,把当前结点的右子结点设置为当前结点,重复步骤 2。

    6、红黑树插入

    具体步骤如下:

    • 从根结点开始检索。
    • 若根结点为空,那么插入结点设为根结点,结束。
    • 若根结点不为空,那么把根结点设为当前结点。
    • 若当前结点为 nil,返回当前结点的父结点,结束。
    • 若当前结点 key 等于搜索 key,那么该 key 所在结点就是插入结点,更新结点的值,结束。
    • 若当前结点 key 大于搜索 key,把当前结点的左子结点设置为当前结点,重复步骤 4。
    • 若当前结点 key 小于搜索 key,把当前结点的右子结点设置为当前结点,重复步骤 4

    7、插入后实现自平衡

    建议新添加的结点默认为红色,因此这样能够让红黑树的性质尽快满足。不过如果添加的结点是根结点,设为黑色即可。

    红黑树插入可能出现的场景:

    ①场景 1:红黑树为空树
    红黑树的性质 2:根结点必须是黑色。处理:直接把插入结点设成黑色并作为根结点。②场景 2:插入结点的 key 已存在二叉搜索树中不能插入相同元素,既然结点的 key 已经存在,红黑树也已平衡,无需重复插入。
    处理:

    • 将插入结点设为将要替换结点的颜色
    • 更新当前结点的值为插入结点的值

    ③场景 3:插入结点的父结点为黑色插入的结点默认是红色的,当它的父结点是黑色时,并不会破坏平衡。
    处理:直接插入。
    ④场景 4:插入结点的父结点为红色如果插入结点的父结点为红色,那么父结点不可能为根结点,所以插入结点总是存在祖父结点。这点很重要,后续的旋转操作需要祖父结点的参与。
    场景 4.1:存在叔父结点,且为红色

    由红黑树性质 4 可知:红色结点不能连续。那么此时该插入子树的红黑层数的情况是:黑-红-红。显然最简单的处理方式就是将其改为:红-黑-红。

    处理:

    • 将父结点和叔父结点变为黑色
    • 将祖父结点变为红色
    • 将祖父结点设置为当前插入结点

    场景 4.2:叔父结点不存在或为黑色,插入结点的父结点是祖父结点的左子结点这种场景下,叔父结点所在的子树的黑色结点就比父结点所在子树的多,不满足红黑树的性质 5。

    场景 4.2.1:插入结点是左子树

    处理:

    • 将父结点变为黑色
    • 将祖父结点变为红色
    • 将祖父结点右旋

    场景 4.2.2:插入结点是左子树

    这种场景显然可以转换为 4.2.1。

    处理:

    • 将父结点进行左旋
    • 将父结点设为插入结点,得到场景 4.2.1
    • 进行场景 4.2.1 的处理

    场景 4.3:叔父结点不存在或为黑色,插入结点的父结点是祖父结点的右子结点相当于场景 4.2 的方向反转,直接看图。

    场景 4.3.1:插入结点是左子树

    处理:

    • 将父结点变为黑色
    • 将祖父结点变为红色
    • 对祖父结点进行左旋

    场景 4.3.2:插入结点是右子树

    处理:

    • 将父结点进行右旋
    • 将父结点设置为插入结点,得到场景 4.3.1
    • 进行场景 4.3.1 的处理

    下面举个例子,往一棵红黑树中插入元素,整棵树的变换如下图所示:

    8、红黑树删除

    红黑树删除操作也分为两步:

    定位删除的位置
    定位删除位置可以复用红黑树搜索的操作。如果不存在目标结点,忽略本次操作;如果找到目标结点,删除后进行自平衡处理。

    删除后实现自平衡二叉搜索树删除的时候可能出现三种场景:

    • 若删除结点无子结点,直接删除即可。
    • 若删除结点只有一个子结点,用子结点替换删除结点。
    • 若删除结点有两个子结点,用**后继结点(大于删除结点的最小结点)**替换删除结点。

    另外两种二叉树的删除场景都可以通过相互转换变为场景一。
    在场景二情况下:删除结点用其唯一的子结点替换,子结点替换为删除结点后,可以认为删除的是子结点,若子结点又有两个子结点,那么相当于转换为场景三,一直自顶向下转换,总是能转换为场景一。

    在场景三情况下:删除结点用后继结点,如果后继结点有右子结点,那么相当于转换为场景二,否则转为场景一。

    综上所述,删除的结点可以看作删除替换结点,且替换结点最后总是在树末。

    红黑树删除可能出现的所有场景:

    为了方面理解,我们先约定一下结点的叫法:

    • R:替换结点
    • P:替换结点的父结点
    • S:替换结点的兄弟结点
    • SL:兄弟结点的左子结点
    • SR:兄弟结点的右子结点
    • 灰色:结点颜色可能是红色,也可能是黑色

    R是即将被替换到删除结点的位置的替换结点,在删除前,它还在原来所在位置参与树的子平衡;平衡后再替换到删除结点的位置,才算删除完成

    ①场景 1:替换结点为红色
    我们把替换结点换到了删除结点的位置时,由于替换结点为红色,删除也了不会影响红黑树的平衡,只要把替换结点的颜色变为删除的结点的颜色即可重新平衡。

    处理:替换结点颜色变为删除结点的颜色。

    ②场景 2:替换结点为黑色当替换结点是黑色时,就必须进行自平衡处理了,我们可以通过区分替换结点是其父结点的左子结点还是右子结点,来做不同的旋转,使树重新平衡。

    场景 2.1:替换结点是左子树

    场景 2.1.1:替换结点的兄弟结点为红色

    若兄弟结点是红结点,那么根据红黑树性质 4,兄弟结点的父结点和子结点肯定为黑色,按照下图方式处理,得到删除场景 2.1.2.3。

    处理:

    • 将兄弟结点变为黑色
    • 将父结点变为红色
    • 对父结点进行左旋,得到场景 2.1.2.3
    • 进行场景 2.1.2.3 的处理

    场景 2.1.2:替换结点的兄弟结点为黑色
    当兄弟结点为黑时,其父结点和子结点的具体颜色也无法确定,此时又得考虑多种子场景。

    场景 2.1.2.1:替换结点的兄弟结点的右子结点为红色,左子结点任意颜色即将删除的左子树的一个黑色结点,显然左子树的黑色结点少 1 了,然而右子结点又是红色,那么我们直接向右子树“借”个红结点来补充黑结点,并进行旋转处理。

    处理:

    • 将兄弟结点的颜色变为父结点的颜色
    • 将父结点变为黑色
    • 将兄弟结点的右子结点变为黑色
    • 对父结点进行左旋

    场景 2.1.2.2:替换结点的兄弟结点的右子结点为黑色,左子结点为红色
    兄弟结点所在的子树有红结点,又可以向兄弟子树“借”个红结点过来,这就转换回了场景 2.1.2.1。

    处理:

    • 将兄弟结点变为红色
    • 将兄弟结点的左子结点变为黑色
    • 对兄弟结点进行右旋,得到场景 2.1.2.1
    • 进行场景 2.1.2.1 的处理

    场景 2.1.2.3:替换结点的兄弟结点的子结点都为黑色

    兄弟子树没有红结点可以“借”了,再向父结点“借”。如果父结点是黑色,为了让父结点在所在的子树中保证平衡(替换结点即将删除,少了一个黑色结点,子树也需要少一个)先把兄弟结点变为红色,再让父结点成为新的替换结点。

    处理:

    • 如果父结点为黑色:将兄弟结点变为红色;将父结点作为新的替换结点;重新进行删除结点的场景处理。
    • 如果父结点为红色:替换结点的父结点和替换结点的兄弟结点颜色交换;删除结点和替换结点的值交换后,删除替换结点。

    场景 2.2:替换结点是右子树实际上是场景 2.1 的镜像操作。

    场景 2.2.1:替换结点的兄弟结点为红色

    处理:

    • 将兄弟结点变为黑色
    • 将父结点变为红色
    • 对父结点进行右旋,得到场景 2.2.2.3
    • 进行场景 2.2.2.3 的处理

    场景 2.2.2:替换结点的兄弟结点为黑色

    场景 2.2.2.1:替换结点的兄弟结点的左子结点为红色,右子结点任意颜色

    处理:

    • 将兄弟结点的颜色变为父结点的颜色
    • 将父结点变为黑色
    • 将兄弟结点的左子结点变为黑色
    • 对父结点进行右旋

    场景 2.2.2.2:替换结点的兄弟结点的左子结点为黑色,右子结点为红色

    处理:

    • 将兄弟结点变为红色
    • 将兄弟结点的右子结点设为黑色
    • 对兄弟结点进行左旋,得到场景 2.2.2.1
    • 进行场景 2.2.2.1 的处理

    场景 2.2.2.3:替换结点的兄弟结点的子结点都为黑色

    处理:

      • 如果父结点为黑色:将兄弟结点变为红色;将父结点作为新的替换结点;重新进行删除结点的场景处理。
      • 如果父结点为红色:替换结点的父结点和替换结点的兄弟结点颜色交换;删除结点和替换结点的值交换后,删除替换结点。
  • 相关阅读:
    springboot配置视图控制器
    springboot测试的方法
    cookie之sameSite
    docker 安装Elasticsearch +kibana
    find + xargs 删除文件名中含有空格的文件
    git,composer 代理
    Composer更新依赖报错Fatal error解决方案
    compose 设置代理 SET HTTP_PROXY="http://192.168.1.103:8080"
    php使用composer常用问题及解决办法集:zlib_decode():data error......
    ERROR: .FileNotFoundError: [Errno 2] No such file or directory: '.\docker-compose.yml:docker-compose.apache.yml'
  • 原文地址:https://www.cnblogs.com/hyunbar/p/13224458.html
Copyright © 2011-2022 走看看