瑞_数据结构与算法_红黑树

news2024/10/6 2:28:35

文章目录

    • 1 什么是红黑树
      • 1.1 红黑树的背景
      • 1.2 红黑树的特性 ★★★
    • 2 红黑树的Java实现
      • 2.1 红黑树颜色枚举类Color
      • 2.2 红黑树节点类Node
        • 2.2.1 实现判断是否是左孩子方法isLeftChild()
        • 2.2.2 实现查找叔叔节点方法uncle()
        • 2.2.3 实现查找兄弟节点方法sibling()
      • 2.3 红黑树类RedBlackTree
        • 2.3.1 实现判断是否为红色节点方法isRed(Node node)
        • 2.3.2 实现判断是否为黑色节点方法isBlack(Node node)
        • 2.3.3 实现右旋方法rightRotate(Node pink)
        • 2.3.4 实现左旋方法leftRotate(Node pink)
        • 2.3.5 实现新增或更新方法put(int key, Object value) ★
        • 2.3.6 实现删除方法remove(int key) ★
    • 3 红黑树Java实现代码完整版(复制粘贴用)
    • 二叉搜索树小结

🙊前言:本文章为瑞_系列专栏之《数据结构与算法》的红黑树篇。由于博主是从B站黑马程序员的《数据结构与算法》学习到的相关知识,所以本系列专栏主要针对该课程进行笔记总结和拓展,文中的部分原理及图解也是来源于黑马提供的资料。本文仅供大家交流、学习及研究使用,禁止用于商业用途,违者必究!

在这里插入图片描述

1 什么是红黑树

1.1 红黑树的背景

  红黑树是一种自平衡二叉查找树,最早由一位名叫Rudolf Bayer的德国计算机科学家于1972年发明。然而,最初的树形结构不是现在的红黑树,而是一种称为B树的结构,它是一种多叉树,可用于在磁盘上存储大量数据。

  在1980年代早期,计算机科学家Leonard Adleman和Daniel Sleator推广了红黑树,并证明了它的自平衡性和高效性。从那时起,红黑树成为了最流行的自平衡二叉查找树之一,并被广泛应用于许多领域,如编译器、操作系统、数据库等。

  红黑树的名字来源于红色节点和黑色节点的交替出现,它们的颜色是用来维护树的平衡性的关键。它们的颜色具有特殊的意义,黑色节点代表普通节点,而红色节点代表一个新添加的节点,它们必须满足一些特定的规则才能维持树的平衡性。

  红黑树也是一种自平衡的二叉搜索树,较之 AVL树,插入和删除时旋转次数更少。所以红黑树和AVL的区别主要在于判断平衡的依据不同。无论是AVL树还是红黑树,都是为了确保树的平衡性,从而提高搜索、插入和删除操作的效率
  关于AVL树的相关知识,可以参考《瑞_数据结构与算法_AVL树》

1.2 红黑树的特性 ★★★

  1. 所有节点都有两种颜色:红🔴、黑⚫️
  2. 所有 null 视为黑色⚫️
  3. 红色🔴节点不能相邻
  4. 根节点是黑色⚫️
  5. 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样(黑色完美平衡)

  第3条、第5条很重要,是判断平衡的主要依据。且当一个叶子节点没有兄弟的时候,就需要考虑null值(null 视为黑色)


  判断1:❌如下二叉树不是平衡的红黑树❌,违反了第3条(红色🔴节点不能相邻)
在这里插入图片描述


  判断2:❌如下二叉树不是平衡的红黑树❌,违反了第5条,根节点6到7和9路径中的黑色节点数为3,而根节点6到1和3路径中的黑色节点数为2,所以右边重,左边轻
在这里插入图片描述


  判断3:✅如下二叉树是一颗平衡的红黑树✅5个特性均满足
在这里插入图片描述


  判断4:❌如下二叉树不是平衡的红黑树❌,注意区别判断3。其实当一个叶子节点没有兄弟的时候,就需要考虑null值(null 视为黑色)
在这里插入图片描述
  上图加上null值(视为黑色)后,如下所示,根节点6到节点1的左右孩子(null)的路径中的黑色节点有3个,而根节点6到节点2的右孩子路径中的黑色节点只有2个,所以违反了特性5(从根到任意一个叶子节点,路径中的黑色⚫️节点数一样)

在这里插入图片描述

  瑞:所以在判断是否为红黑树的时候,当一个叶子节点没有兄弟的时候,就需要考虑null值(null 视为黑色),得出以下经验

  1️⃣叶子节点如果为红色🔴,是可以单独存在的(可以没有兄弟),但是如果叶子节点为黑色⚫️没有兄弟的,就是不平衡的。
:  two:红色🔴节点的孩子肯定是黑色⚫️,而且如果红色节点有孩子一定是两个黑色节点,因为如果只有一个黑色节点孩子意味着违反了特性5




2 红黑树的Java实现


1️⃣内部颜色枚举类Color中含有:

  • RED
  • BLACK

2️⃣➖1️⃣内部节点类Node中含有属性:

  • 索引
  • 存储值
  • 左孩子
  • 右孩子
  • 父节点
  • 颜色Color枚举类

2️⃣➖2️⃣内部节点类Node中含有内部工具方法:

  • 判断是否是左孩子isLeftChild()
  • 查找叔叔节点uncle()
  • 查找兄弟节点sibling()

3️⃣➖1️⃣红黑树类RedBlackTree中含有属性:

  • 根节点(Node)

3️⃣➖2️⃣红黑树类RedBlackTree中含有方法:

  • 判断是否为红色节点isRed(Node node)
  • 判断是否为黑色节点isBlack(Node node)
  • 右旋rightRotate(Node pink)
  • 左旋leftRotate(Node pink)
  • 新增或更新put(int key, Object value)
  • 修复新增中不平衡的情况fixRedRed(Node x)
  • 删除remove(int key)
  • 判断节点是否存在contains(int key)
  • 查找删除节点find(int key)
  • 查找剩余节点findReplaced(Node deleted)
  • 处理双黑fixDoubleBlack(Node x)

2.1 红黑树颜色枚举类Color

    enum Color {
        RED, BLACK;
    }

2.2 红黑树节点类Node

    static class Node {
        /**
         * 索引
         */
        int key;
        /**
         * 存储值
         */
        Object value;
        /**
         * 左孩子
         */
        Node left;
        /**
         * 右孩子
         */
        Node right;
        /**
         * 父节点
         */
        Node parent;
        /**
         * 颜色(新节点初始值默认红色)
         */
        Color color = Color.RED;

        public Node(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public Node(int key) {
            this.key = key;
        }

        public Node(int key, Color color) {
            this.key = key;
            this.color = color;
        }

        public Node(int key, Color color, Node left, Node right) {
            this.key = key;
            this.color = color;
            this.left = left;
            this.right = right;
            if (left != null) {
                left.parent = this;
            }
            if (right != null) {
                right.parent = this;
            }
        }

        // 判断是否是左孩子
        boolean isLeftChild() {
            return parent != null && parent.left == this;
        }

        // 查找叔叔节点(父节点的兄弟节点)
        Node uncle() {
            // 如果父节点为null或者爷爷节点为null,是没有叔叔节点的
            if (parent == null || parent.parent == null) {
                return null;
            }
            // 如果父亲是爷爷的左孩子,叔叔则是爷爷的右孩子
            if (parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                // 如果父亲是爷爷的右孩子,叔叔则是爷爷的左孩子
                return parent.parent.left;
            }
        }

        // 查找兄弟节点
        Node sibling() {
            // 如果父亲为null,一定没有兄弟节点
            if (parent == null) {
                return null;
            }
            // 如果当前节点是父亲的左孩子,兄弟节点是父亲的右孩子
            if (this.isLeftChild()) {
                return parent.right;
            } else {
                // 如果当前节点是父亲的右孩子,兄弟节点是父亲的左孩子
                return parent.left;
            }
        }
    }
2.2.1 实现判断是否是左孩子方法isLeftChild()
        // 判断是否是左孩子
        boolean isLeftChild() {
            return parent != null && parent.left == this;
        }
2.2.2 实现查找叔叔节点方法uncle()

  叔叔节点即父节点的兄弟节点

        // 查找叔叔节点(父节点的兄弟节点)
        Node uncle() {
            // 如果父节点为null或者爷爷节点为null,是没有叔叔节点的
            if (parent == null || parent.parent == null) {
                return null;
            }
            // 如果父亲是爷爷的左孩子,叔叔则是爷爷的右孩子
            if (parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                // 如果父亲是爷爷的右孩子,叔叔则是爷爷的左孩子
                return parent.parent.left;
            }
        }
2.2.3 实现查找兄弟节点方法sibling()
        // 查找兄弟节点
        Node sibling() {
            // 如果父亲为null,一定没有兄弟节点
            if (parent == null) {
                return null;
            }
            // 如果当前节点是父亲的左孩子,兄弟节点是父亲的右孩子
            if (this.isLeftChild()) {
                return parent.right;
            } else {
                // 如果当前节点是父亲的右孩子,兄弟节点是父亲的左孩子
                return parent.left;
            }
        }

2.3 红黑树类RedBlackTree

2.3.1 实现判断是否为红色节点方法isRed(Node node)
    // 判断是否为红色节点
    boolean isRed(Node node) {
        return node != null && node.color == Color.RED;
    }
2.3.2 实现判断是否为黑色节点方法isBlack(Node node)
    // 判断是否为黑色节点
    boolean isBlack(Node node) {
        return node == null || node.color == Color.BLACK;
    }
2.3.3 实现右旋方法rightRotate(Node pink)

  类似AVL树的右旋方法,思想基本一致,但是要多处理父节点属性,同时意味着旋转后的新根的父子关系需要处理。关于AVL树的右旋,可以参考《瑞_数据结构与算法_AVL树》

  假设一颗红黑树向右旋转前,如下图所示:
在这里插入图片描述

  • 粉红色节点,旧根(失衡节点)
  • 黄色节点,旧根的左孩子,将来作为新根,旧根是它右孩子
  • 绿色节点,新根的右孩子,将来要换爹作为旧根的左孩子

  右旋后,如下图所示:

在这里插入图片描述

  实现代码如下:

    // 右旋 1. parent 的处理 2. 旋转后新根的父子关系
    private void rightRotate(Node pink) {
        Node parent = pink.parent;
        Node yellow = pink.left;
        Node green = yellow.right;
        if (green != null) {
            green.parent = pink;
        }
        yellow.right = pink;
        yellow.parent = parent;
        pink.left = green;
        pink.parent = yellow;
        if (parent == null) {
            root = yellow;
        } else if (parent.left == pink) {
            parent.left = yellow;
        } else {
            parent.right = yellow;
        }
    }
2.3.4 实现左旋方法leftRotate(Node pink)

  与右旋方法思想一样,代码如下:

    // 左旋
    private void leftRotate(Node pink) {
        Node parent = pink.parent;
        Node yellow = pink.right;
        Node green = yellow.left;
        if (green != null) {
            green.parent = pink;
        }
        yellow.left = pink;
        yellow.parent = parent;
        pink.right = green;
        pink.parent = yellow;
        if (parent == null) {
            root = yellow;
        } else if (parent.left == pink) {
            parent.left = yellow;
        } else {
            parent.right = yellow;
        }
    }
2.3.5 实现新增或更新方法put(int key, Object value) ★

  与AVL树的put方法的思想类似,但要多考虑红红不平衡的情况,需要进行调整(红黑树特性3 红色🔴节点不能相邻)

瑞:红黑树恢复平衡一般通过两种情况:变色、旋转。关于失衡的四种情况LL、LR、RL、RR,以及解决失衡的左旋(RR)、右旋(LL)、先左旋再右旋(LR)、先右旋再左旋(RL)可以参考《瑞_数据结构与算法_AVL树》(建议先学会AVL树再学习红黑树)

  插入情况分为4种,具体如下:

  • 默认插入节点均视为红色🔴

  1️⃣ 插入节点为根节点,将根节点变黑⚫️


  2️⃣ 插入节点的父亲若为黑色⚫️,树的红黑性质不变,无需调整


  • 插入节点的父亲为红色🔴,触发红红相邻,违反特性3

  3️⃣ 叔叔为红色🔴

    3️⃣➖1️⃣ 父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️

    3️⃣➖2️⃣祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色🔴

    3️⃣➖3️⃣ 祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整


  4️⃣ 叔叔为黑色⚫️

    4️⃣➖1️⃣ 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡

      4️⃣➖1️⃣➖1️⃣ 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色

      4️⃣➖1️⃣➖2️⃣ 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻

    4️⃣➖2️⃣ 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡

      4️⃣➖2️⃣➖1️⃣ 父亲左旋,变成 LL 情况,按 1. 来后续处理

    4️⃣➖3️⃣ 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡

      4️⃣➖3️⃣➖1️⃣ 让父亲变黑⚫️,为了保证这颗子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色

      4️⃣➖3️⃣➖1️⃣ 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻

    4️⃣➖4️⃣ 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡

      4️⃣➖4️⃣➖1️⃣ 父亲右旋,变成 RR 情况,按 3. 来后续处理

  实现代码如下:

    /**
     * 新增或更新
     * <br>
     * 正常增、遇到红红不平衡进行调整
     *
     * @param key   键
     * @param value 值
     */
    public void put(int key, Object value) {
        Node p = root;
        Node parent = null;
        while (p != null) {
            parent = p;
            if (key < p.key) {
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            } else {
                p.value = value; // 更新
                return;
            }
        }
        Node inserted = new Node(key, value);
        if (parent == null) {
            root = inserted;
        } else if (key < parent.key) {
            parent.left = inserted;
            inserted.parent = parent;
        } else {
            parent.right = inserted;
            inserted.parent = parent;
        }
        fixRedRed(inserted);
    }

    /**
     * 修复新增中不平衡的情况
     *
     * @param x 新增节点
     **/
    void fixRedRed(Node x) {
        // case 1 插入节点是根节点,变黑即可
        if (x == root) {
            x.color = Color.BLACK;
            return;
        }
        // case 2 插入节点父亲是黑色,无需调整
        if (isBlack(x.parent)) {
            return;
        }
        /* case 3 当红红相邻,叔叔为红时
            需要将父亲、叔叔变黑、祖父变红,然后对祖父做递归处理
        */
        Node parent = x.parent;
        Node uncle = x.uncle();
        Node grandparent = parent.parent;
        if (isRed(uncle)) {
            parent.color = Color.BLACK;
            uncle.color = Color.BLACK;
            grandparent.color = Color.RED;
            fixRedRed(grandparent);
            return;
        }

        // case 4 当红红相邻,叔叔为黑时
        if (parent.isLeftChild() && x.isLeftChild()) { // LL
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (parent.isLeftChild()) { // LR
            leftRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (!x.isLeftChild()) { // RR
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        } else { // RL
            rightRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        }
    }
2.3.6 实现删除方法remove(int key) ★

  与AVL树的remove方法的思想类似,但要多考虑黑黑不平衡的情况,需要进行调整

瑞:红黑树恢复平衡一般通过两种情况:变色、旋转。关于失衡的四种情况LL、LR、RL、RR,以及解决失衡的左旋(RR)、右旋(LL)、先左旋再右旋(LR)、先右旋再左旋(RL)可以参考《瑞_数据结构与算法_AVL树》(建议先学会AVL树再学习红黑树)

  删除情况(case)分为6种,具体如下:

  0️⃣ 如果删除节点有两个孩子,化简成只有一个孩子或没有孩子

    0️⃣➖1️⃣ 交换删除节点和后继节点的 key,value,递归删除后继节点,直到该节点没有孩子或只剩一个孩子

瑞:使用这个技巧,就可以将两个孩子的问题转换为一个孩子或没有孩子的情况


  • 如果删除节点没有孩子或只剩一个孩子

  1️⃣ 删的是根节点

    1️⃣➖1️⃣ 删完了,直接将 root = null

    1️⃣➖2️⃣ 用剩余节点替换了根节点的 key,value,根节点孩子 = null,颜色保持黑色⚫️不变


  • 删黑色会失衡,删红色不会失衡,但删黑色有一种简单情况

  2️⃣ 删的是黑⚫️,剩下的是红🔴,剩下这个红节点变黑⚫️

瑞:只要是删除黑色节点,都会失衡。解决方法:兄弟变为红色,父亲变为黑色。中间的红色节点的删除不用考虑失衡,因为删中间的红色节点最终都会转化为删黑色节点,因为红色节点如果有孩子,就只能有两个黑色节点孩子(一个黑孩子意味着违反特性5),就会转化为情况0️⃣,变为一个孩子或者没有孩子的情况,就会和后继进行交换,此时其实删除的就是黑色节点。


  • 删除节点和剩下节点都是黑⚫️,触发双黑,双黑意思是,少了一个黑(整个路径上缺少一个黑导致失衡)

  3️⃣ 被调整节点的兄弟为红🔴,此时两个侄子定为黑 ⚫️

    3️⃣➖1️⃣ 删除节点是左孩子,父亲左旋

    3️⃣➖2️⃣ 删除节点是右孩子,父亲右旋

    3️⃣➖3️⃣ 父亲和兄弟要变色,保证旋转后颜色平衡

    3️⃣➖4️⃣ 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入 case 4️⃣ 或 case 5️⃣

case3️⃣相当于是一种过渡情况,并不能通过调整自己恢复平衡,需要将case3️⃣转换为case4️⃣或case5️⃣,进一步调整


瑞:由于调整,全部黑色节点的红黑树是可能出现的情况,通过变色可以解决情况4

  4️⃣ 被调整节点的兄弟为黑⚫️,两个侄子都为黑 ⚫️

    4️⃣➖1️⃣ 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少 1

    4️⃣➖2️⃣ 如果父亲是红🔴,则需将父亲变为黑,避免红红,此时路径黑节点数目不变

    4️⃣➖3️⃣ 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑


情况5通过变色解决不了平衡问题,还需要通过旋转才能平衡

  5️⃣ 被调整节点的兄弟为黑⚫️,至少一个红🔴侄子

    5️⃣➖1️⃣ 如果兄弟是左孩子,左侄子是红🔴,LL 不平衡

      5️⃣➖1️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️

      5️⃣➖1️⃣➖2️⃣ 原来兄弟要成为父亲,需要保留父亲颜色

    5️⃣➖2️⃣ 如果兄弟是左孩子,右侄子是红🔴,LR 不平衡

      5️⃣➖2️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️

      5️⃣➖2️⃣➖2️⃣ 右侄子会取代原来父亲,因此它保留父亲颜色

      5️⃣➖2️⃣➖3️⃣ 兄弟已经是黑了⚫️,无需改变

    5️⃣➖3️⃣ 如果兄弟是右孩子,右侄子是红🔴,RR 不平衡

      5️⃣➖3️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️

      5️⃣➖3️⃣➖1️⃣ 原来兄弟要成为父亲,需要保留父亲颜色

    5️⃣➖4️⃣ 如果兄弟是右孩子,左侄子是红🔴,RL 不平衡

      5️⃣➖4️⃣➖1️⃣ 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️

      5️⃣➖4️⃣➖2️⃣ 左侄子会取代原来父亲,因此它保留父亲颜色

      5️⃣➖4️⃣➖3️⃣ 兄弟已经是黑了⚫️,无需改变

  实现代码如下:

/**
     * 删除
     * <br>
     * 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整
     *
     * @param key 键
     */
    public void remove(int key) {
        Node deleted = find(key);
        if (deleted == null) {
            return;
        }
        doRemove(deleted);
    }
    
    // 查找删除节点
    private Node find(int key) {
        Node p = root;
        while (p != null) {
            if (key < p.key) {
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            } else {
                return p;
            }
        }
        return null;
    }

    // 判断节点是否存在
    public boolean contains(int key) {
        return find(key) != null;
    }

    // 查找剩余节点
    private Node findReplaced(Node deleted) {
        if (deleted.left == null && deleted.right == null) {
            return null;
        }
        if (deleted.left == null) {
            return deleted.right;
        }
        if (deleted.right == null) {
            return deleted.left;
        }
        Node s = deleted.right;
        while (s.left != null) {
            s = s.left;
        }
        return s;
    }

    // 处理双黑 (case3、case4、case5)
    private void fixDoubleBlack(Node x) {
        if (x == root) {
            return;
        }
        Node parent = x.parent;
        Node sibling = x.sibling();
        // case 3 兄弟节点是红色
        if (isRed(sibling)) {
            if (x.isLeftChild()) {
                leftRotate(parent);
            } else {
                rightRotate(parent);
            }
            parent.color = Color.RED;
            sibling.color = Color.BLACK;
            fixDoubleBlack(x);
            return;
        }
        if (sibling != null) {
            // case 4 兄弟是黑色, 两个侄子也是黑色
            if (isBlack(sibling.left) && isBlack(sibling.right)) {
                sibling.color = Color.RED;
                if (isRed(parent)) {
                    parent.color = Color.BLACK;
                } else {
                    fixDoubleBlack(parent);
                }
            }
            // case 5 兄弟是黑色, 侄子有红色
            else {
                // LL
                if (sibling.isLeftChild() && isRed(sibling.left)) {
                    rightRotate(parent);
                    sibling.left.color = Color.BLACK;
                    sibling.color = parent.color;
                }
                // LR
                else if (sibling.isLeftChild() && isRed(sibling.right)) {
                    sibling.right.color = parent.color;
                    leftRotate(sibling);
                    rightRotate(parent);
                }
                // RL
                else if (!sibling.isLeftChild() && isRed(sibling.left)) {
                    sibling.left.color = parent.color;
                    rightRotate(sibling);
                    leftRotate(parent);
                }
                // RR
                else {
                    leftRotate(parent);
                    sibling.right.color = Color.BLACK;
                    sibling.color = parent.color;
                }
                parent.color = Color.BLACK;
            }
        } else {
            // @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null
            fixDoubleBlack(parent);
        }
    }

    // 删除递归
    private void doRemove(Node deleted) {
        Node replaced = findReplaced(deleted);
        Node parent = deleted.parent;
        // 没有孩子
        if (replaced == null) {
            // case 1 删除的是根节点
            if (deleted == root) {
                root = null;
            } else {
                if (isBlack(deleted)) {
                    // 复杂调整
                    fixDoubleBlack(deleted);
                } else {
                    // 红色叶子, 无需任何处理
                }
                if (deleted.isLeftChild()) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
                deleted.parent = null;
            }
            return;
        }
        // 有一个孩子
        if (deleted.left == null || deleted.right == null) {
            // case 1 删除的是根节点
            if (deleted == root) {
                root.key = replaced.key;
                root.value = replaced.value;
                root.left = root.right = null;
            } else {
                if (deleted.isLeftChild()) {
                    parent.left = replaced;
                } else {
                    parent.right = replaced;
                }
                replaced.parent = parent;
                deleted.left = deleted.right = deleted.parent = null; // help gc
                if (isBlack(deleted) && isBlack(replaced)) {
                    // 复杂处理 @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑
                    fixDoubleBlack(replaced);
                } else {
                    // case 2 删除是黑,剩下是红
                    replaced.color = Color.BLACK;
                }
            }
            return;
        }
        // case 0 有两个孩子 => 有一个孩子 或 没有孩子
        int t = deleted.key;
        deleted.key = replaced.key;
        replaced.key = t;

        Object v = deleted.value;
        deleted.value = replaced.value;
        replaced.value = v;
        doRemove(replaced);
    }

3 红黑树Java实现代码完整版(复制粘贴用)

/**
 * <h3>红黑树</h3>
 */
public class RedBlackTree {

    enum Color {
        RED, BLACK;
    }

    /**
     * 根节点
     */
    Node root;

    static class Node {
        /**
         * 索引
         */
        int key;
        /**
         * 存储值
         */
        Object value;
        /**
         * 左孩子
         */
        Node left;
        /**
         * 右孩子
         */
        Node right;
        /**
         * 父节点
         */
        Node parent;
        /**
         * 颜色(新节点初始值默认红色)
         */
        Color color = Color.RED;

        public Node(int key, Object value) {
            this.key = key;
            this.value = value;
        }

        public Node(int key) {
            this.key = key;
        }

        public Node(int key, Color color) {
            this.key = key;
            this.color = color;
        }

        public Node(int key, Color color, Node left, Node right) {
            this.key = key;
            this.color = color;
            this.left = left;
            this.right = right;
            if (left != null) {
                left.parent = this;
            }
            if (right != null) {
                right.parent = this;
            }
        }

        // 判断是否是左孩子
        boolean isLeftChild() {
            return parent != null && parent.left == this;
        }

        // 查找叔叔节点(父节点的兄弟节点)
        Node uncle() {
            // 如果父节点为null或者爷爷节点为null,是没有叔叔节点的
            if (parent == null || parent.parent == null) {
                return null;
            }
            // 如果父亲是爷爷的左孩子,叔叔则是爷爷的右孩子
            if (parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                // 如果父亲是爷爷的右孩子,叔叔则是爷爷的左孩子
                return parent.parent.left;
            }
        }

        // 查找兄弟节点
        Node sibling() {
            // 如果父亲为null,一定没有兄弟节点
            if (parent == null) {
                return null;
            }
            // 如果当前节点是父亲的左孩子,兄弟节点是父亲的右孩子
            if (this.isLeftChild()) {
                return parent.right;
            } else {
                // 如果当前节点是父亲的右孩子,兄弟节点是父亲的左孩子
                return parent.left;
            }
        }
    }

    // 判断是否为红色节点
    boolean isRed(Node node) {
        return node != null && node.color == Color.RED;
    }

    // 判断是否为黑色节点
    boolean isBlack(Node node) {
//        return !isRed(node);
        return node == null || node.color == Color.BLACK;
    }

    // 右旋 1. parent 的处理 2. 旋转后新根的父子关系
    private void rightRotate(Node pink) {
        Node parent = pink.parent;
        Node yellow = pink.left;
        Node green = yellow.right;
        if (green != null) {
            green.parent = pink;
        }
        yellow.right = pink;
        yellow.parent = parent;
        pink.left = green;
        pink.parent = yellow;
        if (parent == null) {
            root = yellow;
        } else if (parent.left == pink) {
            parent.left = yellow;
        } else {
            parent.right = yellow;
        }
    }

    // 左旋
    private void leftRotate(Node pink) {
        Node parent = pink.parent;
        Node yellow = pink.right;
        Node green = yellow.left;
        if (green != null) {
            green.parent = pink;
        }
        yellow.left = pink;
        yellow.parent = parent;
        pink.right = green;
        pink.parent = yellow;
        if (parent == null) {
            root = yellow;
        } else if (parent.left == pink) {
            parent.left = yellow;
        } else {
            parent.right = yellow;
        }
    }

    /**
     * 新增或更新
     * <br>
     * 正常增、遇到红红不平衡进行调整
     *
     * @param key   键
     * @param value 值
     */
    public void put(int key, Object value) {
        Node p = root;
        Node parent = null;
        while (p != null) {
            parent = p;
            if (key < p.key) {
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            } else {
                p.value = value; // 更新
                return;
            }
        }
        Node inserted = new Node(key, value);
        if (parent == null) {
            root = inserted;
        } else if (key < parent.key) {
            parent.left = inserted;
            inserted.parent = parent;
        } else {
            parent.right = inserted;
            inserted.parent = parent;
        }
        fixRedRed(inserted);
    }

    /**
     * 修复新增中不平衡的情况
     *
     * @param x 新增节点
     **/
    void fixRedRed(Node x) {
        // case 1 插入节点是根节点,变黑即可
        if (x == root) {
            x.color = Color.BLACK;
            return;
        }
        // case 2 插入节点父亲是黑色,无需调整
        if (isBlack(x.parent)) {
            return;
        }
        /* case 3 当红红相邻,叔叔为红时
            需要将父亲、叔叔变黑、祖父变红,然后对祖父做递归处理
        */
        Node parent = x.parent;
        Node uncle = x.uncle();
        Node grandparent = parent.parent;
        if (isRed(uncle)) {
            parent.color = Color.BLACK;
            uncle.color = Color.BLACK;
            grandparent.color = Color.RED;
            fixRedRed(grandparent);
            return;
        }

        // case 4 当红红相邻,叔叔为黑时
        if (parent.isLeftChild() && x.isLeftChild()) { // LL
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (parent.isLeftChild()) { // LR
            leftRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            rightRotate(grandparent);
        } else if (!x.isLeftChild()) { // RR
            parent.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        } else { // RL
            rightRotate(parent);
            x.color = Color.BLACK;
            grandparent.color = Color.RED;
            leftRotate(grandparent);
        }
    }

    /**
     * 删除
     * <br>
     * 正常删、会用到李代桃僵技巧、遇到黑黑不平衡进行调整
     *
     * @param key 键
     */
    public void remove(int key) {
        Node deleted = find(key);
        if (deleted == null) {
            return;
        }
        doRemove(deleted);
    }

    // 查找删除节点
    private Node find(int key) {
        Node p = root;
        while (p != null) {
            if (key < p.key) {
                p = p.left;
            } else if (p.key < key) {
                p = p.right;
            } else {
                return p;
            }
        }
        return null;
    }

    // 判断节点是否存在
    public boolean contains(int key) {
        return find(key) != null;
    }

    // 查找剩余节点
    private Node findReplaced(Node deleted) {
        if (deleted.left == null && deleted.right == null) {
            return null;
        }
        if (deleted.left == null) {
            return deleted.right;
        }
        if (deleted.right == null) {
            return deleted.left;
        }
        Node s = deleted.right;
        while (s.left != null) {
            s = s.left;
        }
        return s;
    }

    // 处理双黑 (case3、case4、case5)
    private void fixDoubleBlack(Node x) {
        if (x == root) {
            return;
        }
        Node parent = x.parent;
        Node sibling = x.sibling();
        // case 3 兄弟节点是红色
        if (isRed(sibling)) {
            if (x.isLeftChild()) {
                leftRotate(parent);
            } else {
                rightRotate(parent);
            }
            parent.color = Color.RED;
            sibling.color = Color.BLACK;
            fixDoubleBlack(x);
            return;
        }
        if (sibling != null) {
            // case 4 兄弟是黑色, 两个侄子也是黑色
            if (isBlack(sibling.left) && isBlack(sibling.right)) {
                sibling.color = Color.RED;
                if (isRed(parent)) {
                    parent.color = Color.BLACK;
                } else {
                    fixDoubleBlack(parent);
                }
            }
            // case 5 兄弟是黑色, 侄子有红色
            else {
                // LL
                if (sibling.isLeftChild() && isRed(sibling.left)) {
                    rightRotate(parent);
                    sibling.left.color = Color.BLACK;
                    sibling.color = parent.color;
                }
                // LR
                else if (sibling.isLeftChild() && isRed(sibling.right)) {
                    sibling.right.color = parent.color;
                    leftRotate(sibling);
                    rightRotate(parent);
                }
                // RL
                else if (!sibling.isLeftChild() && isRed(sibling.left)) {
                    sibling.left.color = parent.color;
                    rightRotate(sibling);
                    leftRotate(parent);
                }
                // RR
                else {
                    leftRotate(parent);
                    sibling.right.color = Color.BLACK;
                    sibling.color = parent.color;
                }
                parent.color = Color.BLACK;
            }
        } else {
            // @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null
            fixDoubleBlack(parent);
        }
    }

    // 删除递归
    private void doRemove(Node deleted) {
        Node replaced = findReplaced(deleted);
        Node parent = deleted.parent;
        // 没有孩子
        if (replaced == null) {
            // case 1 删除的是根节点
            if (deleted == root) {
                root = null;
            } else {
                if (isBlack(deleted)) {
                    // 复杂调整
                    fixDoubleBlack(deleted);
                } else {
                    // 红色叶子, 无需任何处理
                }
                if (deleted.isLeftChild()) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
                deleted.parent = null;
            }
            return;
        }
        // 有一个孩子
        if (deleted.left == null || deleted.right == null) {
            // case 1 删除的是根节点
            if (deleted == root) {
                root.key = replaced.key;
                root.value = replaced.value;
                root.left = root.right = null;
            } else {
                if (deleted.isLeftChild()) {
                    parent.left = replaced;
                } else {
                    parent.right = replaced;
                }
                replaced.parent = parent;
                deleted.left = deleted.right = deleted.parent = null; // help gc
                if (isBlack(deleted) && isBlack(replaced)) {
                    // 复杂处理 @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑
                    fixDoubleBlack(replaced);
                } else {
                    // case 2 删除是黑,剩下是红
                    replaced.color = Color.BLACK;
                }
            }
            return;
        }
        // case 0 有两个孩子 => 有一个孩子 或 没有孩子
        int t = deleted.key;
        deleted.key = replaced.key;
        replaced.key = t;

        Object v = deleted.value;
        deleted.value = replaced.value;
        replaced.value = v;
        doRemove(replaced);
    }
}

二叉搜索树小结

二叉搜索树可以参考《瑞_数据结构与算法_二叉搜索树》
AVL树可以参考《瑞_数据结构与算法_AVL树》

  二叉搜索树对比如下:

维度普通二叉搜索树AVL树红黑树
查询平均O(logn),最坏O(n)O(logn)O(logn)
插入平均O(logn),最坏O(n)O(logn)O(logn)
删除平均O(logn),最坏O(n)O(logn)O(logn)
平衡性不平衡严格平衡近似平衡
结构二叉树自平衡的二叉树具有红黑性质的自平衡二叉树
查找效率
插入删除效率

  普通二叉搜索树插入、删除、查询的时间复杂度与树的高度相关,因此在最坏情况下,时间复杂度为O(n),而且容易退化成链表,查找效率低。

  AVL树是一种高度平衡的二叉搜索树,其左右子树的高度差不超过1。因此,它能够在logn的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。

  红黑树是一种近似平衡的二叉搜索树,它在保持高度平衡的同时,又能够保持较高的插入删除效率。红黑树通过节点着色和旋转操作来维护平衡。红黑树在维护平衡的过程中,能够进行较少的节点旋转操作,因此插入删除效率较高,并且查询效率也较高。

  综上所述,红黑树具有较高的综合性能,是一种广泛应用的数据结构。




本文是博主的粗浅理解,可能存在一些错误或不完善之处,如有遗漏或错误欢迎各位补充,谢谢

  如果觉得这篇文章对您有所帮助的话,请动动小手点波关注💗,你的点赞👍收藏⭐️转发🔗评论📝都是对博主最好的支持~


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1422739.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《Pandas 简易速速上手小册》第1章:Pandas入门(2024 最新版)

文章目录 1.1 Pandas 简介1.1.1 基础知识1.1.2 案例&#xff1a;气候变化数据分析1.1.3 拓展案例一&#xff1a;金融市场分析1.1.4 拓展案例二&#xff1a;社交媒体情感分析 1.2 安装和配置 Pandas1.2.1 基础知识1.2.2 案例&#xff1a;个人财务管理1.2.3 拓展案例一&#xff1…

Qt之QLabel介绍

概述 QLabel是QT界面中的标签类&#xff0c;它从QFrame下继承&#xff0c;QLabel 类代表标签&#xff0c;它是一个用于显示文本或图像的窗口部件。我们主要介绍一下QLabel的一些简单的使用。 设置颜色背景色和字体的颜色大小 字体及颜色 设置文字使用的是setText函数。 QStri…

linux中常用的命令

一&#xff1a;tree命令 &#xff08;码字不易&#xff0c;关注一下吧&#xff0c;w~~w) 以树状形式查看指定目录内容。 tree --树状显示当前目录下的文件信息。 tree 目录 --树状显示指定目录下的文件信息。 注意&#xff1a; tree只能查看目录内容&#xff0c;不能…

如何抠图换背景?分享6个不能错过的工具!

在数字化时代&#xff0c;抠图换背景已经成为了一种常见的图像处理需求。无论是出于商业宣传、个人创作还是日常生活分享的需要&#xff0c;抠图换背景都能帮助我们创造出更具视觉冲击力、更富有个性的图片。那么&#xff0c;如何进行抠图换背景呢&#xff1f;又有哪些工具可以…

云计算HCIE备考经验分享

大家好&#xff0c;我是来自深圳信息职业技术学院22级鲲鹏3-1班的刘同学&#xff0c;在2023年9月19日成功通过了华为云计算HCIE认证&#xff0c;并且取得了A的成绩。下面把我的考证经验分享给大家。 转专业进鲲鹏班考HCIE 大一上学期的时候&#xff0c;在上Linux课程的时候&…

2024新鲜出炉 Java集合常见面试题总结(上)

2024新鲜出炉 Java集合常见面试题总结(上) 文章目录 2024新鲜出炉 Java集合常见面试题总结(上)集合概述Java 集合概览说说 List, Set, Queue, Map 四者的区别&#xff1f;集合框架底层数据结构总结ListSetQueueMap 如何选用集合?为什么要使用集合&#xff1f; ListArrayList 和…

MySql 慢SQL配置,查询,处理

一.慢SQL配置相关 1.查看慢SQL是否开启 执行下面命令查看是否开启慢SQL show variables like %slow_query_log; 复制代码 OFF: 未开启ON: 2.打开慢SQL配置 执行下面的命令开启慢查询日志 set global slow_query_logON; 复制代码 3.修改慢查询阈值 前面介绍了SQL执行到达了…

SD-WAN和专线混合组网:企业出海网络解决方案

目前&#xff0c;有很多国内企业涉足海外业务&#xff0c;如跨境电商、游戏、社交网络、区块链等。都会使用海外服务器。同时&#xff0c;这些企业在国内还有自己的机房&#xff0c;IDC或者使用国内其他云厂商的机房。如果他们想要相互通信或传输数据该怎么办&#xff1f;在成本…

【数据结构】链表的一些面试题

简单不先于复杂&#xff0c;而是在复杂之后。 链表面试题 删除链表中等于给定值 val 的所有结点。OJ链接 //1.常规方法struct ListNode* removeElements(struct ListNode* head, int val) {struct ListNode* cur head, *prev NULL;while(cur){if(cur->val val){//1.头删/…

WPF入门到跪下 第十一章 Prism(五)IOC的依赖注入

IOC的依赖注入 一、构造函数方式的依赖注入 以项目启动时MainWindowViewModel的依赖注入为例&#xff0c;默认情况下Prism框架的项目&#xff0c;在打开窗口时会自动匹配主窗口的视图模型类&#xff08;PrismApplication启动&#xff09;&#xff0c;这里是MainWindowViewMod…

外汇天眼:纽约总检察长起诉花旗银行,指责其未能保护欺诈受害者

纽约总检察长莉蒂西亚詹姆斯今天起诉花旗银行&#xff0c;指责其未能保护并拒绝偿还欺诈受害者。该诉讼声称&#xff0c;花旗银行没有实施强有力的在线保护措施来阻止未经授权的账户劫持&#xff0c;误导账户持有人关于账户被黑客攻击并且资金被盗后的权利&#xff0c;并非法地…

uniapp多格式文件选择(APP,H5)

uniapp多格式文件选择&#xff08;APP&#xff0c;H5&#xff09; 背景实现代码实现运行结果注意事项 尾巴 背景 从手机选择文件进行上传是移动端很常见的需求&#xff0c;在原生开发时由于平台专一性很容易实现。但是用uniapp开发官方提供的API在APP平台只能选择图片和视频&a…

负载均衡下的webshell连接

一、环境配置 1.在Ubuntu上配置docker环境 我们选择用Xshell来将环境资源上传到Ubuntu虚拟机上&#xff08;比较简单&#xff09; 我们选择在root模式下进行环境配置&#xff0c;先将资源文件复制到root下&#xff08;如果你一开始就传输到root下就不用理会这个&#xff09; …

手把手教测试,全网内容最全最深-jmeter-Recording Controller(录制控制器)

5.1.6.14.Recording Controller(录制控制器) 第一步&#xff1a; 第二步&#xff1a;点击启动按钮&#xff0c;生成证书。证书在jmeter的bin目录下。 第三步&#xff1a;设置代理 第四步&#xff1a;抓取https包需要安装证书&#xff0c;在浏览器edge中安装 未完待续。。。 手…

Django4.2(DRF)+Vue3 读写分离项目部署上线

文章目录 1 前端2 后端2.1 修改 settings.py 文件关于静态文件2.2 关于用户上传的文件图片 3 Nginx4 镜像制作4.1 nginx4.3 Django镜像4.3.1 构建 5 docker-compose 文件内容 1 前端 进入前端项目的根目录&#xff0c;运行如下命令进行构建 npm run build构建完成后&#xff…

金田金业教你如何看懂国际黄金价格走势图

对于黄金投资者来说&#xff0c;看懂国际黄金价格走势图是至关重要的。通过观察走势图&#xff0c;可以了解金价的实时动态&#xff0c;预测未来的走势&#xff0c;从而做出相应的投资决策。本文将详细解析如何看懂国际黄金价格走势图。 一、国际黄金价格走势图的基本构成 国…

10s 内得到一个干净、开箱即用的 Linux 系统

安装 使用官方脚本安装我的服务器不行 官方脚本 mkdir instantbox && cd $_ bash <(curl -sSL https://raw.githubusercontent.com/instantbox/instantbox/master/init.sh) 下面是我的完整安装过程 mkdir /opt/instantbox cd /opt/instantbox 1.脚本文件 (这个没…

12.MySql服务

目录 1. 什么是数据库 1.1. 数据&#xff1a; 1.2. 数据库&#xff1a; 2. mysql概述 3. 版本及下载 4. yum仓库安装 4.1. 添加yum源 4.2. 安装 5. 本地RPM包安装 5.1. 使用迅雷下载集合包 5.2. 上传数据 5.3. 安装 6. 生产环境中使用通用二进制包安装 6.1. 作用…

如何应对Android面试官-> CoordinatorLayout详解,我用 Behavior 实现了手势跟随

前言 本章主要讲解下 CoordinatorLayout 的基础用法、工作原理和自定义Behavior 原理 使用很简单&#xff0c;百度上可以搜索下基础使用 协调者布局的功能 作为应用的顶层布局作为一个管理容器&#xff0c;管理与子 View 或者子 View 之间的交互处理子控件之间依赖下的交互处…

ChatGPT可与自定义GPTs一起使用,智能AI代理时代来啦!

1月31日凌晨&#xff0c;OpenAI在社交平台公布了一个超强新功能&#xff0c;可以在ChatGPT中输入“GPTs名字”的方法&#xff0c;调用多个自定义GPTs一起协同工作。 例如&#xff0c;我想开发一款社交APP&#xff0c;1&#xff09;可以先用专业分析GPTs做一下市场调研&#xf…