Java数据结构——红黑树

97 阅读5分钟

3. 红黑树

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

红黑树特性

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

插入情况

插入节点均视为红色:red_circle:

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

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

插入节点的父亲为红色:red_circle:,触发红红相邻

case 3:叔叔为红色:red_circle:

  • 父亲变为黑色:black_circle:,为了保证黑色平衡,连带的叔叔也变为黑色:black_circle:

  • 祖父如果是黑色不变,会造成这颗子树黑色过多,因此祖父节点变为红色:red_circle:

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

case 4:叔叔为黑色:black_circle:

  1. 父亲为左孩子,插入节点也是左孩子,此时即 LL 不平衡
    • 让父亲变黑:black_circle:,为了保证这颗子树黑色不变,将祖父变成红:red_circle:,但叔叔子树少了一个黑色
    • 祖父右旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
  2. 父亲为左孩子,插入节点是右孩子,此时即 LR 不平衡
    • 父亲左旋,变成 LL 情况,按 1. 来后续处理
  3. 父亲为右孩子,插入节点也是右孩子,此时即 RR 不平衡
    • 让父亲变黑:black_circle:,为了保证这颗子树黑色不变,将祖父变成红:red_circle:,但叔叔子树少了一个黑色
    • 祖父左旋,补齐一个黑色给叔叔,父亲旋转上去取代祖父,由于它是黑色,不会再次触发红红相邻
  4. 父亲为右孩子,插入节点是左孩子,此时即 RL 不平衡
    • 父亲右旋,变成 RR 情况,按 3. 来后续处理

删除情况

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

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

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

case 1:删的是根节点

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

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

case 2:删的是黑:black_circle:,剩下的是红:red_circle:,剩下这个红节点变黑:black_circle:

删除节点和剩下节点都是黑:black_circle:,触发双黑,双黑意思是,少了一个黑

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

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

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

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

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

  • 如果兄弟是左孩子,左侄子是红:red_circle:,LL 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:,平衡起见,左侄子也是黑:black_circle:
    • 原来兄弟要成为父亲,需要保留父亲颜色
  • 如果兄弟是左孩子,右侄子是红:red_circle:,LR 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:
    • 右侄子会取代原来父亲,因此它保留父亲颜色
    • 兄弟已经是黑了:black_circle:,无需改变
  • 如果兄弟是右孩子,右侄子是红:red_circle:,RR 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:,平衡起见,右侄子也是黑:black_circle:
    • 原来兄弟要成为父亲,需要保留父亲颜色
  • 如果兄弟是右孩子,左侄子是红:red_circle:,RL 不平衡
    • 将来删除节点这边少个黑,所以最后旋转过来的父亲需要变成黑:black_circle:
    • 左侄子会取代原来父亲,因此它保留父亲颜色
    • 兄弟已经是黑了:black_circle:,无需改变

完整代码

package com.itheima.datastructure.redblacktree;

import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.BLACK;
import static com.itheima.datastructure.redblacktree.RedBlackTree.Color.RED;

/**
 * <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 = 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() {
            if (parent == null || parent.parent == null) {
                return null;
            }
            if (parent.isLeftChild()) {
                return parent.parent.right;
            } else {
                return parent.parent.left;
            }
        }

        // 兄弟
        Node sibling() {
            if (parent == null) {
                return null;
            }
            if (this.isLeftChild()) {
                return parent.right;
            } else {
                return parent.left;
            }
        }
    }

    // 判断红
    boolean isRed(Node node) {
        return node != null && node.color == RED;
    }

    // 判断黑
    boolean isBlack(Node node) {
//        return !isRed(node);
        return node == null || node.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);
    }

    void fixRedRed(Node x) {
        // case 1 插入节点是根节点,变黑即可
        if (x == root) {
            x.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 = BLACK;
            uncle.color = BLACK;
            grandparent.color = RED;
            fixRedRed(grandparent);
            return;
        }

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

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

    public boolean contains(int key) {
        return find(key) != null;
    }

    // 查找删除节点
    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;
    }

    // 查找剩余节点
    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 = RED;
            sibling.color = BLACK;
            fixDoubleBlack(x);
            return;
        }
        if (sibling != null) {
            // case 4 兄弟是黑色, 两个侄子也是黑色
            if (isBlack(sibling.left) && isBlack(sibling.right)) {
                sibling.color = RED;
                if (isRed(parent)) {
                    parent.color = BLACK;
                } else {
                    fixDoubleBlack(parent);
                }
            }
            // case 5 兄弟是黑色, 侄子有红色
            else {
                // LL
                if (sibling.isLeftChild() && isRed(sibling.left)) {
                    rightRotate(parent);
                    sibling.left.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 = BLACK;
                    sibling.color = parent.color;
                }
                parent.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;
                if (isBlack(deleted) && isBlack(replaced)) {
                    // @TODO 实际不会有这种情况 因为只有一个孩子时 被删除节点是黑色 那么剩余节点只能是红色不会触发双黑
                    fixDoubleBlack(replaced);
                } else {
                    // case 2 删除是黑,剩下是红
                    replaced.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);
    }
}