红黑树Java实现

本文介绍了红黑树的概念和特点,并对比了红黑树与AVL树的区别,重点阐述了红黑树在插入和删除操作时的处理,包括变色、左旋和右旋等维护操作,并给出了Java实现红黑树的部分代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 概念

红黑树是一种自平衡的二叉查找树,除了满足二叉查找树的性质外,还需要满足如下五个条件:

1. 节点是红色或黑色
2. 根节点为黑色
3. 所有叶子节点都是黑色
4. 每个红色节点都必须有两个黑色的子节点 也就是说在一条路径上不能出现相邻的两个红色结点。
5. 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点

2为什么要用红黑树

要想知道为啥就要有对比的对象吧(平衡二叉树)

2.1 AVL树(平衡二叉树)

简介

AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,AVL树是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差的绝对值不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入与删除次数比较少,但查找多的情况

问 :AVL的查找比红黑树还快 为啥用红黑树?

:由于维护这种高度平衡所付出的代价比从中获得的效率收益还大,故而实际的应用不多,更多的地方是用追求局部而不是非常严格整体平衡的红黑树。当然,如果应用场景中对插入删除不频繁,只是对查找要求较高,那么AVL还是较优于红黑树。

问 AVL 所有节点的左右子树高度差的绝对值不超过1 那红黑树的高度相差多少?

: 由 4. 每个红色节点都必须有两个黑色的子节点
5. 从任一节点到叶子节点的所有路径都包含相同数目的黑色节点
可以假设 一条子树的黑节点个数为N
如果最高得子树每个黑节点后面都有一个红节点 那高度为2N
最短的就为N
所以 最差情况下高度比是2:1。

3操作

红黑树的维护操作操作有三个,分别是变色、左旋和右旋,
红黑树的插入、删除 有时会破坏树的结构 需要用这三个基本操作维护树的结构

3.1 变色

变色操作比较简单,不改变该节点的值以及该节点在红黑树中的位置,仅改变该节点的颜色,红黑树颜色有两种,所以,变色分为两种,红色变为黑色和黑色变为红色(代码中用布尔类型数据表示)

  private static final boolean RED   = false;
    private static final boolean BLACK = true;

3.2 左旋

在这里插入图片描述
对值为4的节点做左旋操作,就是将4节点的右子节点6节点上升,4节点作为6节点的左子节点,原6节点的左子节点作为4节点的右节点
代码如下

 /*
     * 对红黑树的节点(x)进行左旋转
     *
     * 左旋示意图(对节点x进行左旋):
     *      px                              px
     *     /                               /
     *    x                               y
     *   /  \      --(左旋)-.             / \                #
     *  lx   y                          x  ry
     *     /   \                       /  \
     *    ly   ry                     lx  ly
     *
     *
     */
    private void leftRotate(RBTNode<T> x) {
   
        // 设置x的右孩子为y
        RBTNode<T> y = x.right;

        // 将 “y的左孩子” 设为 “x的右孩子”;
        // 如果y的左孩子非空,将 “x” 设为 “y的左孩子的父亲”
        x.right = y.left;
        if (y.left != null)
            y.left.parent = x;

        // 将 “x的父亲” 设为 “y的父亲”
        y.parent = x.parent;

        if (x.parent == null) {
   
            this.mRoot = y;            // 如果 “x的父亲” 是空节点,则将y设为根节点
        } else {
   
            if (x.parent.left == x)
                x.parent.left = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
            else
                x.parent.right = y;    // 如果 x是它父节点的左孩子,则将y设为“x的父节点的左孩子”
        }

        // 将 “x” 设为 “y的左孩子”
        y.left = x;
        // 将 “x的父节点” 设为 “y”
        x.parent = y;
    }

3.3 右旋

请添加图片描述
对值为6的节点做右旋操作,就是将6节点的左子节点4节点上升,6节点作为4节点的右子节点,原4节点的右子节点作为6节点的左子节点
代码如下

  /*
     * 对红黑树的节点(y)进行右旋转
     *
     * 右旋示意图(对节点y进行左旋):
     *            py                               py
     *           /                                /
     *          y                                x
     *         /  \      --(右旋)-.            /  \                     #
     *        x   ry                           lx   y
     *       / \                                   / \                   #
     *      lx  rx                                rx  ry
     *
     */
    private void rightRotate(RBTNode<T> y) {
   
        // 设置x是当前节点的左孩子。
        RBTNode<T> x = y.left;

        // 将 “x的右孩子” 设为 “y的左孩子”;
        // 如果"x的右孩子"不为空的话,将 “y” 设为 “x的右孩子的父亲”
        y.left = x.right;
        if (x.right != null)
            x.right.parent = y;

        // 将 “y的父亲” 设为 “x的父亲”
        x.parent = y.parent;

        if (y.parent == null) {
   
            this.mRoot = x;            // 如果 “y的父亲” 是空节点,则将x设为根节点
        } else {
   
            if (y == y.parent.right)
                y.parent.right = x;    // 如果 y是它父节点的右孩子,则将x设为“y的父节点的右孩子”
            else
                y.parent.left = x;    // (y是它父节点的左孩子) 将x设为“x的父节点的左孩子”
        }

        // 将 “y” 设为 “x的右孩子”
        x.right = y;

        // 将 “y的父节点” 设为 “x”
        y.parent = x;
    }

3.增加时存在的情况

红黑树在新增节点过程中比较复杂,复杂归复杂它同样必须要依据上面提到的五点规范,同时由于规则1、2、3基本都会满足,下面我们主要讨论规则4、5。假设我们这里有一棵最简单的树,我们规定新增的节点为N、它的父节点为P、P的兄弟节点为U、P的父节点为G。

在这里插入图片描述
对于新节点的插入有如下三个关键地方:

1、插入新节点总是红色节点 。
2、如果插入节点的父节点是黑色, 能维持性质 。
3、如果插入节点的父节点是红色, 破坏了性质. 故插入算法就是通过重新着色或旋转, 来维持性质

3.1、为跟节点
若新插入的节点N没有父节点,则直接当做根据节点插入即可,同时将颜色设置为黑色。
如下图
在这里插入图片描述

3.2、父节点为黑色

   这种情况新节点N同样是直接插入,同时颜色为红色,由于根据规则四它会存在两个黑色的叶子节点,
   值为null。同时由于新增节点N为红色,所以通过它的子节点的路径依然会保存着相同的黑色节点数,
   同样满足规则5。
   如图

在这里插入图片描述

3.3、若父节点P和P的兄弟节点U都为红色

   对于这种情况若直接插入肯定会出现不平衡现象。怎么处理?P、U节点变黑、G节点变红。
   这时由于经过节点P、U的路径都必须经过G所以在这些路径上面的黑节点数目还是相同的。
   但是经过上面的处理,可能G节点的父节点也是红色,
   这个时候我们需要将G节点当做新增节点递归处理。

直接插入的时候 (4是新插入的)
在这里插入图片描述
对树进行维护后(只牵扯到一次转换的)
在这里插入图片描述
对树进行维护后 (维护后的节点 与他的父节点不满足规则4)修改后4和6为红色相当于插入的是红色(如条件3.4 )对246左旋
在这里插入图片描述

   3.4、若父节点P(图的6节点)为红色,叔父节点U(图上为空)为黑色或者缺少,
   且新增节点N(图的7节点)为P节点的右孩子
   对于这种情况我们先改变父节点和祖父的颜色 在对新增节点N、P进行一次左旋转。

在这里插入图片描述

   3.5、父节点P为红色,叔父节点U为黑色或者缺少,新增节点N为父节点P左孩子

先对(父亲节点)7进行进行右旋 这个时候就变成3.4的情况了
在这里插入图片描述

4. 删除时存在的情况

  针对于红黑树的增加节点而言,删除显得更加复杂,使原本就复杂的红黑树变得更加复杂。     
  删除节点和增加节点一样,同样是找到删除的节点,删除之后调整红黑树。
  但是这里的删除节点(当节点左右子树都存在时)并不是直接删除,
  而是通过走了“弯路”通过一种捷径来删除的:
  找到被删除的节点D的子节点C,用C来替代D,不是直接删除D,因为D被C替代了,直接删除C即可。
  所以这里就将删除父节点D的事情转变为了删除子节点C的事情,这样处理就将复杂的删除事件简单了。
  子节点C的规则是:右分支最左边,或者左分支最右边的。

在这里插入图片描述

本文用的是 右分支最左边 代码如下

     // 获取后继节点
            replace = replace.right;
            while (replace.left != null)
                replace = replace.left; //取右节点的最小值

红-黑二叉树删除节点,最大的麻烦是要保持 各分支黑色节点数目相等。
因为是删除,所以不用担心存在颜色冲突问题——插入才会引起颜色冲突。

红黑树删除节点同样会分成几种情况,这里是按照待删除节点有几个儿子的情况来进行分类:

   1、没有儿子,即为叶结点。直接把父结点的对应儿子指针设为NULL,删除儿子结点就OK了。

   2、只有一个儿子。那么把父结点的相应儿子指针指向儿子的独生子,删除儿子结点也OK了。

   3、有两个儿子。这种情况比较复杂,但还是比较简单。上面提到过用子节点C替代代替待删除节点D,
   然后删除子节点C即可。 这个样问题就转化为只有一个儿子了,因为找到的C不能存在左儿子

在这里插入图片描述

既然删除节点比较复杂,那么在这里我们就约定一下规则:

   1、下面要讲解的删除节点一定是实际要删除节点的后继节点(N),如前面提到的C。
   2、下面提到的删除节点的树都是如下结构,该结构所选取的节点是待删除节点的右树的最左边子节
   点。这里我们规定真实删除节点为N、真实删除节点的子节点S ,父节点为P、兄弟节点为W兄弟节点的
   两个子节点为X1、X2。如下图(2.1)。

在这里插入图片描述

现在我们就上面提到的三种情况进行分析、处理。

4.1 情况一、无子节点(红色节点)
   这种情况对该节点(如果为红色)直接删除即可,不会影响树的结构。
   如果为黑色就转化成情况二了;
4.2 情况二、有一个子节点
 这种情况先去判断子节点的颜色 当子节点为红色时子节点代替当前节点 ,
 当子节点为黑色且删除节点为黑色 要去判断被删除节点的兄弟节点的颜色和兄弟节点子节点的颜色
  有一下几种情况
    *1.W是黑色 且其子节点都是黑色
    * 2.W是黑色 其右节点是红色
    * 3.W是黑色 其子节点左红右黑
    * 4.W是红色
4.3.1、N的兄弟w是黑色的,且w的俩个孩子都是黑色的。
   这种情况其父节点可红可黑,由于W为黑色,这样导致N子树相对于其兄弟W子树少一个黑色节点,
   这时我们可以将W置为红色。这样,N子树与W子树黑色节点一致,保持了平衡。如下
   
 将W由黑转变为红,这样就会导致新节点new N相对于它的兄弟节点会少一个黑色节点。
 但是如果new N(原来N的父节点)为红色,
 我们直接将new N(原来N的父节点)转变为黑色,
 保持整棵树的平衡。否则情况3.1 会转变为情况3.2、3.3、3.4中的一种。

在这里插入图片描述

4.3.1、N的兄弟节点W为红色
W为红色,那么其子节点X1、X2必定全部为黑色,父节点P也为黑色。
处理策略是:改变W、P的颜色,然后进行一次左旋转。这样处理就可以使得红黑性质得以继续保持。
操作:
   将W由黑转变为红,这样就会导致新节点new N相对于它的兄弟节点会少一个黑色节点。
   但是如果new x为红色,我们直接将new x转变为黑色,保持整棵树的平衡。
   否则情况3.2 会转变为情况3.1、3.3、3.4中的一种。(因为x1的子树不知道)

在这里插入图片描述

情况3.3、N的兄弟w是黑色的,w的左孩子是红色,w的右孩子是黑色。
   针对这种情况是将节点W和其左子节点进行颜色交换,然后对W进行右旋转处理。
   此时N的新兄弟X1(new w)是一个有红色右孩子的黑结点,于是将情况3转化为情况4.

在这里插入图片描述

情况3.4、N的兄弟w是黑色的,且w的右孩子是红色的。
   交换W和父节点P(p的颜色没有要求)的颜色,同时对P进行左旋转操作。
   这样就把左边缺失的黑色节点给补回来了。
   同时将W的右子节点X2置黑。这样左右都达到了平衡。

在这里插入图片描述

4.3 情况三、有两个子节点

可以用找后继节点的方式把两个子节点转化为一个子节点 ;

整体代码

/**
 * @author nwk
 * 红黑树
 * @version 1.0
 * @date 2021/9/13 10:24
 */

public class RBTree<T extends Comparable<T>> {
   

    private RBTNode<T> mRoot;    // 根结点

    private static final boolean RED   = false;
    private static final boolean BLACK = true;

    public class RBTNode<T extends Comparable<T>> {
    //红黑树的节点
        boolean color;        // 颜色
        T key;                // 关键字(键值)
        RBTNode<T> left;    // 左孩子
        RBTNode<T> right;    // 右孩子
        RBTNode<T> parent;    // 父结点

        public RBTNode(T key, boolean color, RBTNode<T> parent, RBTNode<T> left, RBTNode<T> right) {
   
            this.key = key;
            this.color = color;
            this.parent = parent;
            this.left = left;
            this.right = right;
        }

        public T getKey() {
   
            return key;
        }

        public String toString() {
   
            return ""+key+(this.color==RED?"(R)":"B");
        }
    }

    public RBTree() {
   
        mRoot=null;
    }

    private RBTNode<T> parentOf(RBTNode<T> node) {
   
        return node!=null 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一叶一菩提魁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值