数据结构与算法 - 红黑树

一、概述

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的平均时间内完成插入、删除、查询操作,但是在维护平衡的过程中,需要频繁地进行旋转操作,导致插入删除效率较低。

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

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

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值