数据结构与算法 - 红黑树

news2024/9/23 3:31:01

一、概述

1. 历史

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

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

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

红黑树也是一种自平衡的二叉搜索树,较之AVL,插入和删除时旋转次数更少

2. 红黑树特性

  • 所有节点都有两种颜色:红🔴、黑⚫️
  • 所有null视为黑色⚫️
  • 红色🔴节点不能相邻
  • 根节点是黑色⚫️
  • 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样

二、实现

1. 插入情况

插入节点均视为红色🔴

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

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

插入节点的父节点为红色🔴,触发红红相邻

case3:叔叔为红色🔴

  • 父亲变为黑色⚫️,为了保证黑色平衡,连带的叔叔也变为黑色⚫️
  • 祖父如果是黑色不变,就会造成这棵子树黑色过多,因此祖父节点变为红色🔴
  • 祖父如果变成红色,可能会接着触发红红相邻,因此对将祖父进行递归调整

case 4:叔叔为黑色⚫️

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

  • 让父亲变黑⚫️,为了保证这棵子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
  • 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻

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

  • 父亲左旋,变为LL情况,按1.来后续处理

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

  • 让父亲变黑⚫️,为了保证这棵子树黑色不变,将祖父变成红🔴,但叔叔子树少了一个黑色
  • 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻

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

  • 父亲右旋,变成RR情况,按3.后续处理

节点类Node

    private static class Node {
        int key;
        Object value;
        Node left;
        Node right;
        Node parent;
        Color color = Color.RED;  // 颜色


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

        /**
         * 找叔叔节点
         * @return
         */
        public Node uncle() {
            if(parent == null || parent.parent == null) {
                return null;
            }
            if(parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                return parent.parent.left;
            }
        }

        /**
         * 找兄弟节点
         * @return
         */
        public Node sibling() {
            if(parent == null) {  // 根节点
                return null;
            }
            if(this.isLeftChild()) {
                return parent.right;
            } else {
                return parent.left;
            }
        }
    }

判断节点的颜色

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

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

右旋

右旋前

右旋后

代码:

    private Node root;

    /**
     * 右旋
     * 1. parent的处理
     * 2. 旋转后新根的父子关系
     * @param pink
     */
    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) {
            // 旋转后yellow为根节点
            root = yellow;
        }else if(parent.left == pink) {
            // 左子树
            parent.left = yellow;
        } else {
            // 右子树
            parent.right = yellow;
        }
    }

左旋

    /**
     * 左旋
     * @param 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;
        }
    }

新增节点

    /**
     * 新增或更新
     * 正常增、遇到红红不平衡进行调整
     * @param key
     * @param value
     */
    public void put(int key, Object value) {
        // 1. 找空位
        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
     */
    private void fixRedRed(Node x) {
        if(x == root) {
            // 情况1 插入节点为根节点,将根节点变黑
            x.color = Color.BLACK;
            return;
        }

        if(isBlack(x.parent)) {
            // 情况2 插入节点的父节点为黑色,树的红黑特性不变,无需调整
            return;
        }

        Node parent = x.parent;
        Node uncle = x.uncle();
        Node grandparent = parent.parent;

        if(isRed(uncle)) {
            // 情况3 插入节点的父节点与叔叔节点为红色
            // 父亲变为黑色,为了保证黑色平衡,连带的叔叔也变为黑色
            parent.color = Color.BLACK;
            uncle.color = Color.BLACK;
            // 祖父如果是黑色不变,会造成这棵子树黑色过多,因此祖父节点变为红色
            grandparent.color = Color.RED;
            // 如果祖父变成红色,可能会触发红红相邻,因此对祖父进行递归调整
            fixRedRed(grandparent);
            return;
        }

        // 情况4 插入节点的父节点为红色,叔叔为黑色
        // 1. 父亲为左孩子,插入节点也是左孩子,此时即LL不平衡 -> 右旋
        if(parent.isLeftChild() && x.isLeftChild()) {
            // 父亲变黑
            parent.color = Color.BLACK;
            // 祖父变红
            grandparent.color = Color.RED;
            // 右旋
            rightRotate(grandparent);
        }
        // 2. 父亲为左孩子,插入节点是右孩子,此时即LR不平衡 -> 左右旋
        else if(parent.isLeftChild()) {
            // 以父节点为支点进行左旋
            leftRotate(parent);
            // 插入节点变黑
            x.color = Color.BLACK;
            // 祖父节点变红
            grandparent.color = Color.RED;
            // 以祖父节点为支点进行右旋
            rightRotate(grandparent);
        }
        // 3. 父亲为右孩子,插入节点也是右孩子,此时即RR不平衡 -> 左旋
        else if(!x.isLeftChild()) {
            // 父节点变黑
            parent.color = Color.BLACK;
            // 祖父节点变红
            grandparent.color = Color.RED;
            // 以祖父节点为支点进行左旋
            leftRotate(grandparent);
        }
        // 4. 父亲节点为右孩子,插入节点是左孩子,此时即RL不平衡 -> 右左旋
        else {
            // 以父节点为支点进行右旋
            rightRotate(parent);
            // 插入节点变黑
            x.color = Color.BLACK;
            // 祖父节点变红
            grandparent.color = Color.RED;
            // 以祖父节点为支点进行左旋
            leftRotate(grandparent);
        }
    }

CASE 4-1:LL

调整前:

调整后:

  • 父亲变黑

  • 祖父变红

  • 右旋

CASE 4-2:LR

调整前:

调整后:

  • 左旋

  • 父节点变黑色,祖父节点变红色

  • 右旋

2. 删除情况

case 0:如果删除节点有两个孩子

  • 交换删除节点和后继节点的key,value,递归删除后继节点,直到该节点没有孩子或只剩下一个孩子

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

case 1:删的是根节点

  • 删完了,直接将root = null
  • 用剩余节点替换根节点的key,value,根节点孩子null,颜色保持黑色⚫️不变

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

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

删除节点和剩余节点都是⚫️黑,触发双黑,双黑的意思是,少了一个黑

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

  • 删除节点是左孩子,父亲左旋
  • 删除节点是右孩子,父亲右旋
  • 父亲和兄弟要变色,保证旋转后颜色平衡
  • 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入case 4 或 case 5

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

  • 将兄弟变红🔴,目的是将删除节点和兄弟那边的黑色高度同时减少1
  • 如果父亲是红🔴,则需将父亲变为黑⚫️,避免红红,此时路径黑节点数目不变
  • 如果父亲是黑⚫️,说明这条路径还是少黑,再次让父节点触发双黑

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

①如果兄弟是左孩子,左侄子是红,LL不平衡

  • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,左侄子也是黑⚫️
  • 原来兄弟要成为父亲,需要保留父亲颜色

例如,删除节点4

右旋

变色

②如果兄弟是左孩子,右侄子是红🔴,LR不平衡

  • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
  • 右侄子会取代原来父亲,因此它保留父亲颜色
  • 兄弟已经是黑了⚫️,无需改变

例如,要删除节点4

左旋(以兄弟为支点)

右旋(以父节点为支点)

变色

③如果兄弟是右孩子,右侄子是红🔴,RR不平衡

  • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️,平衡起见,右侄子也是黑⚫️
  • 原来兄弟要成为父亲,需要保留父亲颜色

死如果兄弟是右孩子,左侄子是红🔴,RL不平衡

  • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑⚫️
  • 左侄子会取代原来父亲,因此它保留父亲颜色
  • 兄弟已经是黑了⚫️,无需改变
    /**
     * 解决双黑问题 -> case 3、 case 4、 case 5
     * @param x
     */
    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;

            // 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入case 4或 case 5
            fixDoubleBlack(x);
            return;
        }

        if (sibling != null) {
            // case 4:被调整节点的兄弟为黑,两个侄子都为黑
            if(isBlack(sibling.left) && isBlack(sibling.right)) {
                // 1. 将兄弟变红,目的是将删除节点和兄弟那边的黑色高度同时减少1
                sibling.color = Color.RED;
                if(isRed(parent)) {
                    // 2. 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
                    parent.color = Color.BLACK;
                } else {
                    // 3. 如果父亲是黑,说明这条路径还是少黑,再次让父节点触发双黑
                    fixDoubleBlack(parent);
                }

            }
            // case 5:被调整节点的兄弟是黑色,至少一个侄子是红色
            else {
                // 5.1 如果兄弟是左孩子,左侄子是红色,LL不平衡
                if(sibling.isLeftChild() && isRed(sibling.left)) {
                    // 右旋
                    rightRotate(parent);
                    // 左侄子变黑色
                    sibling.left.color = Color.BLACK;
                    // 原来兄弟要成为父亲,需要保留父亲颜色
                    sibling.color = parent.color;
                }
                // 5.2 如果兄弟是左孩子,右侄子是红色,LR不平衡
                else if(sibling.isLeftChild() && isRed(sibling.right)) {
                    // 右侄子会取代原来的父亲,保留父亲的颜色
                    sibling.right.color = parent.color;
                    // 左旋(以兄弟为支点)
                    leftRotate(sibling);
                    // 右旋(以父节点为支点)
                    rightRotate(parent);
                }
                // 5.3 如果兄弟是右孩子,右侄子是红色,RR不平衡
                else if(!sibling.isLeftChild() && isRed(sibling.right)) {
                    // 左旋
                    leftRotate(parent);
                    // 右侄子变成黑色
                    sibling.right.color = Color.BLACK;
                    // 原来兄弟要成为父亲,需要保留父亲颜色
                    sibling.color = parent.color;
                }
                // 5.4 如果兄弟是右孩子,左侄子是红色,RL不平衡
                else {
                    // 左侄子会取代原来父亲,因此它保留父亲颜色
                    sibling.left.color = parent.color;
                    // 右旋(以兄弟为支点)
                    rightRotate(sibling);
                    // 左旋(以父亲为支点)
                    leftRotate(parent);
                }
                // 旋转过来的父亲要变成黑色
                parent.color = Color.BLACK;

            }
        } else {
            // @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null
            fixDoubleBlack(parent);
        }
    }

    private void doRemove(Node deleted) {
        Node replaced = findReplaced(deleted);
        Node parent = deleted.parent;

        // 1. 没有孩子
        if(replaced == null) {
            // case 1:删除的是根节点,且删完了,直接将root = null
            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;  // help GC
            }
            return;
        }

        // 2. 有一个孩子
        if(deleted.left == null || deleted.right == null) {
            // case 1: 删除的是根节点,用剩余节点替换根节点key、value,根节点孩子=null,颜色保持黑色不变
            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)) {
                    // 复杂处理
                    fixDoubleBlack(replaced);
                } else {
                    // case 2:删的是黑色,剩下的是红色,剩下这个红节点变黑
                    replaced.color = Color.BLACK;
                }
            }
            return;
        }

        // 3. case 0:被删除节点有两个孩子 => 有一个孩子 或 没有孩子
        // 3.1 交换被删节点和后继节点的key,value值
        int k = deleted.key;
        deleted.key = replaced.key;
        replaced.key = k;

        Object v = deleted.value;
        deleted.value = replaced.value;
        replaced.value = v;
        // 3.2 递归删除后继节点,直到该节点没有孩子或只剩一个孩子
        doRemove(replaced);
    }

3. 完整代码

package com.itheima.datastructure.RedBlackTree;


import com.sun.org.apache.regexp.internal.RE;

/**
 * 红黑树
 */
public class RedBlackTree {
    enum Color {
        RED, BLACK;
    }

    private static class Node {
        int key;
        Object value;
        Node left;
        Node right;
        Node parent;
        Color color = Color.RED;  // 颜色

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

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

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


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

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

        /**
         * 找叔叔节点
         * @return
         */
        public Node uncle() {
            if(parent == null || parent.parent == null) {
                return null;
            }
            if(parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                return parent.parent.left;
            }
        }

        /**
         * 找兄弟节点
         * @return
         */
        public Node sibling() {
            if(parent == null) {  // 根节点
                return null;
            }
            if(this.isLeftChild()) {
                return parent.right;
            } else {
                return parent.left;
            }
        }
    }

    private Node root;

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

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

    /**
     * 右旋
     * 1. parent的处理
     * 2. 旋转后新根的父子关系
     * @param pink
     */
    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) {
            // 旋转后yellow为根节点
            root = yellow;
        }else if(parent.left == pink) {
            // 左子树
            parent.left = yellow;
        } else {
            // 右子树
            parent.right = yellow;
        }
    }

    /**
     * 左旋
     * @param 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;
        }
    }

    /**
     * 新增或更新
     * 正常增、遇到红红不平衡进行调整
     * @param key
     * @param value
     */
    public void put(int key, Object value) {
        // 1. 找空位
        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
     */
    private void fixRedRed(Node x) {
        if(x == root) {
            // 情况1 插入节点为根节点,将根节点变黑
            x.color = Color.BLACK;
            return;
        }

        if(isBlack(x.parent)) {
            // 情况2 插入节点的父节点为黑色,树的红黑特性不变,无需调整
            return;
        }

        Node parent = x.parent;
        Node uncle = x.uncle();
        Node grandparent = parent.parent;

        if(isRed(uncle)) {
            // 情况3 插入节点的父节点与叔叔节点为红色
            // 父亲变为黑色,为了保证黑色平衡,连带的叔叔也变为黑色
            parent.color = Color.BLACK;
            uncle.color = Color.BLACK;
            // 祖父如果是黑色不变,会造成这棵子树黑色过多,因此祖父节点变为红色
            grandparent.color = Color.RED;
            // 如果祖父变成红色,可能会触发红红相邻,因此对祖父进行递归调整
            fixRedRed(grandparent);
            return;
        }

        // 情况4 插入节点的父节点为红色,叔叔为黑色
        // 1. 父亲为左孩子,插入节点也是左孩子,此时即LL不平衡 -> 右旋
        if(parent.isLeftChild() && x.isLeftChild()) {
            // 父亲变黑
            parent.color = Color.BLACK;
            // 祖父变红
            grandparent.color = Color.RED;
            // 右旋
            rightRotate(grandparent);
        }
        // 2. 父亲为左孩子,插入节点是右孩子,此时即LR不平衡 -> 左右旋
        else if(parent.isLeftChild()) {
            // 以父节点为支点进行左旋
            leftRotate(parent);
            // 插入节点变黑
            x.color = Color.BLACK;
            // 祖父节点变红
            grandparent.color = Color.RED;
            // 以祖父节点为支点进行右旋
            rightRotate(grandparent);
        }
        // 3. 父亲为右孩子,插入节点也是右孩子,此时即RR不平衡 -> 左旋
        else if(!x.isLeftChild()) {
            // 父节点变黑
            parent.color = Color.BLACK;
            // 祖父节点变红
            grandparent.color = Color.RED;
            // 以祖父节点为支点进行左旋
            leftRotate(grandparent);
        }
        // 4. 父亲节点为右孩子,插入节点是左孩子,此时即RL不平衡 -> 右左旋
        else {
            // 以父节点为支点进行右旋
            rightRotate(parent);
            // 插入节点变黑
            x.color = Color.BLACK;
            // 祖父节点变红
            grandparent.color = Color.RED;
            // 以祖父节点为支点进行左旋
            leftRotate(grandparent);
        }
    }

    /**
     * 查找删除节点
     * @param key
     * @return
     */
    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;
    }

    /**
     * 查找被删除节点的剩余节点
     * @param deleted
     * @return
     */
    private Node findReplaced(Node deleted) {
        // 1. 被删除节点是叶子节点
        if(deleted.left == null && deleted.right == null) {
            return null;
        }
        // 2. 只有右孩子
        if(deleted.left == null) {
            return deleted.right;
        }
        // 3. 只有左孩子
        if(deleted.right == null) {
            return deleted.left;
        }
        // 4. 有两个孩子 -> 找后继
        Node s = deleted.right;
        while(s.left != null) {
            s = s.left;
        }
        return s;
    }

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

    /**
     * 解决双黑问题 -> case 3、 case 4、 case 5
     * @param x
     */
    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;

            // 旋转的目的是让黑侄子变为删除节点的黑兄弟,对删除节点再次递归,进入case 4或 case 5
            fixDoubleBlack(x);
            return;
        }

        if (sibling != null) {
            // case 4:被调整节点的兄弟为黑,两个侄子都为黑
            if(isBlack(sibling.left) && isBlack(sibling.right)) {
                // 1. 将兄弟变红,目的是将删除节点和兄弟那边的黑色高度同时减少1
                sibling.color = Color.RED;
                if(isRed(parent)) {
                    // 2. 如果父亲是红,则需将父亲变为黑,避免红红,此时路径黑节点数目不变
                    parent.color = Color.BLACK;
                } else {
                    // 3. 如果父亲是黑,说明这条路径还是少黑,再次让父节点触发双黑
                    fixDoubleBlack(parent);
                }

            }
            // case 5:被调整节点的兄弟是黑色,至少一个侄子是红色
            else {
                // 5.1 如果兄弟是左孩子,左侄子是红色,LL不平衡
                if(sibling.isLeftChild() && isRed(sibling.left)) {
                    // 右旋
                    rightRotate(parent);
                    // 左侄子变黑色
                    sibling.left.color = Color.BLACK;
                    // 原来兄弟要成为父亲,需要保留父亲颜色
                    sibling.color = parent.color;
                }
                // 5.2 如果兄弟是左孩子,右侄子是红色,LR不平衡
                else if(sibling.isLeftChild() && isRed(sibling.right)) {
                    // 右侄子会取代原来的父亲,保留父亲的颜色
                    sibling.right.color = parent.color;
                    // 左旋(以兄弟为支点)
                    leftRotate(sibling);
                    // 右旋(以父节点为支点)
                    rightRotate(parent);
                }
                // 5.3 如果兄弟是右孩子,右侄子是红色,RR不平衡
                else if(!sibling.isLeftChild() && isRed(sibling.right)) {
                    // 左旋
                    leftRotate(parent);
                    // 右侄子变成黑色
                    sibling.right.color = Color.BLACK;
                    // 原来兄弟要成为父亲,需要保留父亲颜色
                    sibling.color = parent.color;
                }
                // 5.4 如果兄弟是右孩子,左侄子是红色,RL不平衡
                else {
                    // 左侄子会取代原来父亲,因此它保留父亲颜色
                    sibling.left.color = parent.color;
                    // 右旋(以兄弟为支点)
                    rightRotate(sibling);
                    // 左旋(以父亲为支点)
                    leftRotate(parent);
                }
                // 旋转过来的父亲要变成黑色
                parent.color = Color.BLACK;

            }
        } else {
            // @TODO 实际也不会出现,触发双黑后,兄弟节点不会为 null
            fixDoubleBlack(parent);
        }
    }

    private void doRemove(Node deleted) {
        Node replaced = findReplaced(deleted);
        Node parent = deleted.parent;

        // 1. 没有孩子
        if(replaced == null) {
            // case 1:删除的是根节点,且删完了,直接将root = null
            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;  // help GC
            }
            return;
        }

        // 2. 有一个孩子
        if(deleted.left == null || deleted.right == null) {
            // case 1: 删除的是根节点,用剩余节点替换根节点key、value,根节点孩子=null,颜色保持黑色不变
            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)) {
                    // 复杂处理
                    fixDoubleBlack(replaced);
                } else {
                    // case 2:删的是黑色,剩下的是红色,剩下这个红节点变黑
                    replaced.color = Color.BLACK;
                }
            }
            return;
        }

        // 3. case 0:被删除节点有两个孩子 => 有一个孩子 或 没有孩子
        // 3.1 交换被删节点和后继节点的key,value值
        int k = deleted.key;
        deleted.key = replaced.key;
        replaced.key = k;

        Object v = deleted.value;
        deleted.value = replaced.value;
        replaced.value = v;
        // 3.2 递归删除后继节点,直到该节点没有孩子或只剩一个孩子
        doRemove(replaced);
    }
}

4. 小结

维度普通二叉搜索树AVL树红黑树
查询平均O(log n),最坏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/1989998.html

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

相关文章

美国司法部对谷歌反垄断案的最新进展,Google每年给苹果200亿?

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 美国司法部对谷歌反垄断案的前因和最新进展 美国司法部对谷歌的反垄断案是一个复杂且历时长久的法律过程&#xff0c;其核心争议在于谷歌是否利…

CentOS 7.6 安装 Weblogic

注&#xff1a;本教程是以虚拟机作为安装环境&#xff0c;如果您公司需要安装 Weblogic 服务器&#xff0c;请先以虚拟机模拟安装一遍&#xff0c;否则出现失误&#xff0c;概不负责&#x1f601;。 一、环境 虚拟机&#xff1a;VMware Workstation 16 Linux&#xff1a;Cent…

@Autowired提示:Field injection is not recommended

1、问题 在项目升级版本过程中&#xff0c;Autowired提示Field injection is not recommendedField injection is not recommended Inspection info: Reports injected or autowired fields in Spring components. The quick-fix suggests the recommended constructor-based d…

Linux 下查看 CPU 使用率

目录 一、什么是 CPU 使用率二、查看 CPU 利用率1、使用 top 查看2、用 pidstat 查看3、用 ps 查看4、用 htop 查看5、用 nmon 查看6、用 atop 查看7、用 glances 查看8、用 vmstat 查看9、用 sar 查看10、dstat11、iostat 三、总结 CPU 使用率是最直观和最常用的系统性能指标&…

Linux下自动监控进程运行状态

目录 背景应用举例1、使用crontab脚本监控服务2、使用shell脚本监控服务2.1 编写自定义监控脚本2.2 运行脚本 背景 假设有一个服务需要长期运行&#xff0c;但可能会由于某种原因导致服务意外停止&#xff0c;不能及时发现&#xff0c;某天来到公司后发现出问题了才意识到服务…

【linux】curl命令用法

curl命令认识 curl命令其实在平常工作中就已经在使用了&#xff0c;但是一直没有系统看过&#xff0c;就在这记录下&#xff0c;以后要用的话&#xff0c;可以在这儿查阅。 curl命令写的更清楚一点其实是cURL&#xff08;client url&#xff0c;客户端URL或者command url命令…

时间复杂度计算方法以及常见题型

时间复杂度是衡量算法运行时间随输入规模增长而增长快慢的一种度量方式。它并不是指算法在特定硬件上的实际运行时间&#xff0c;而是算法在理想环境下执行时间的增长趋势。计算时间复杂度时&#xff0c;我们主要关注算法中执行次数最多的操作&#xff08;即基本操作&#xff0…

算法板子:匈牙利算法——二分图的最大匹配

目录 1. 基础概念 &#xff08;1&#xff09;二分图的概念 &#xff08;2&#xff09; 匈牙利算法的作用 2. 代码 1. 基础概念 &#xff08;1&#xff09;二分图的概念 顶点集 V 分为两个集合&#xff0c;且图中每条边依附的两个顶点都分属于这两个子集&#xff0c;也就是第…

了解反向代理如何工作吗?

在当今数字化时代&#xff0c;网络通讯扮演着重要的角色&#xff0c;而代理技术为网络通讯提供了更多的灵活性和安全性。作为两种重要的代理技术&#xff0c;代理服务器和反向代理的运行原理和用途各有不同。本文将重点介绍反向代理的运行原理&#xff0c;深入探讨其在网络通讯…

运动耳机哪款好?多方位实测五大风靡网络的爆款,第一款竟然连奥运冠军都在用

随着健康意识的提升&#xff0c;将骨传导耳机作为运动伴侣的国人日益增多&#xff0c;其市场年度销售额已突破新高。然而&#xff0c;作为深耕运动装备领域多年的专家&#xff0c;我深感有责任告诫广大运动爱好者&#xff0c;在选择骨传导耳机时&#xff0c;务必保持警惕&#…

PD虚拟机共享文件夹 PD虚拟机共享蓝牙设备怎么设置 PD虚拟机如何共享文件

PD虚拟机&#xff08;Parallels Desktop&#xff09;是为有双系统使用需求人士设计的Mac系统软件。PD虚拟机可以在Mac电脑中设置Windows系统的应用软件。有了PD虚拟机的帮助&#xff0c;大家可以直接在Mac系统中使用其他如Windows的虚拟机系统&#xff0c;除此以外&#xff0c;…

软件RAID配置实战(2个案例场景)

文章目录 3、软件RAID管理-mdadm工具安装mdadm组件格式示例选项说明mdadm命令其它常用选项 4、相关查询命令查看创建RAID的进度查看RAID磁盘详细信息查看文件系统的磁盘空间使用情况 5、RAID配置示例场景1&#xff1a;RAID5步骤 场景2&#xff1a;RAID10步骤 6、移除RAID阵列 接…

031_java.util.concurrent.CopyOnWriteArrayList

继承体系 CopyOnWriteArrayList存在的目的是为了解决在高并发下list的读写。设计上希望只阻塞写行为&#xff0c;不会阻塞读行为。CopyOnWriteArrayList设计就基于此&#xff0c;在内部含有ReentrantLock用作修改时加锁&#xff0c;CopyOnWriteArrayList下有很多可以写方法&…

三种向量相似度计量方法——欧式距离、余弦相似度、皮尔逊相关系数

1、欧式距离 欧氏距离在机器学习可以清晰展示不同对象的相似程度。 欧式距离是最直观的距离度量方法之一&#xff0c;它衡量两个点之间的直线距离, 较小的欧式距离意味着较高的相似度。 分类——K近邻算法&#xff08;KNN&#xff09;&#xff1a;需要对一个新的样本进行分类…

IIS6 PUT漏洞

一.漏洞描述 IIS Server 在 Web 服务扩展中开启了 WebDAV &#xff0c;配置了可以写⼊的权限&#xff0c;造成任意⽂件上传 1.1环境搭建 环境 fofa&#xff1a;"IIS-6.0" 本地搭建2003 server 1.2漏洞复现 1.开启 WebDAV 和写权限&#xff1a; 1.3 漏洞复现 使…

随笔(三)——项目代码优化

文章目录 一、数据驱动的优化点0.项目技术1.需求说明2. 优化前3.优化后&#xff08;复杂版&#xff09;4.优化后&#xff08;可读性高版&#xff09; 二、使用循环遍历&#xff0c;减少if-else1.源代码2. 优化后3. 优点 一、数据驱动的优化点 0.项目技术 vue2 view design …

OpenAI 推出 gpt-4o-2024-08-06 模型 解析结构化输出功能 附体验平台

人工智能技术的边界再次被突破&#xff0c;OpenAI 社区迎来了 gpt-4o-2024-08-06 模型的问世&#xff0c;这不仅是一次技术的飞跃&#xff0c;更是对智能助手功能和可靠性的一次全面革新。 技术革新&#xff1a;gpt-4o-2024-08-06 模型的诞生 gpt-4o-2024-08-06 模型是 OpenA…

【深度学习】用Pytorch完成MNIST手写数字数据集的训练和测试

模型训练相关 思路&#xff1a; 导入数据集&#xff08;对数据集转换为张量&#xff09;加载数据集&#xff08;使数据集成为可以进行迭代&#xff09;搭建卷积模型进行模型训练&#xff08;每训练一轮查看一次在测试集上的准确率&#xff09;使用tensorboard进行可视化保存训…

MySQL3 DQL数据查询语言

DQL SQL-DQL重要地位简单查询selectjia简单查询数据准备别名(AS)消除重复行(DISTINCT去重)算数运算符0.优先级1.算数运算符2.比较运算符3.逻辑运算符4.位运算符 空值空值参与运算 条件查询普通条件查询特殊比较运算符BETWEEN...AND...INLIKEIS NULLleast&#xff0c;greatest运…

Unity补完计划 之 SpriteEditer SingleMode

本文仅作笔记学习和分享&#xff0c;不用做任何商业用途 本文包括但不限于unity官方手册&#xff0c;unity唐老狮等教程知识&#xff0c;如有不足还请斧正 因为unity不只是3d需要&#xff0c;还有2d游戏需要大量编辑处理图片素材&#xff0c;所以需要了解Sprite&#xff08;精灵…