数据结构与算法【红黑树】的Java实现+图解

前言

建议先阅读普通二叉搜索树与平衡二叉搜索树的文章。理解一些基本的二叉树知识数据结构与算法【二叉搜索树】Java实现-CSDN博客

介绍

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

首先介绍代码实现会用到的概念

  • 兄弟节点:具有同一个父结点的一对节点可以互称为兄弟节点
  • 叔叔节点:父结点的兄弟节点

红黑树特性

  1. 所有节点都有两种颜色:红🔴、黑⚫️

  2. 所有 null 视为黑色⚫️

  3. 红色🔴节点不能相邻

  4. 根节点是黑色⚫️

  5. 从根到任意一个叶子节点,路径中的黑色⚫️节点数一样

根据该特性,我们可以总结出

红色节点要么没有孩子要么有两个黑孩子

举例

以下情况均不属于红黑树

红红相邻

不满足从根节点到任意叶子节点路径中的黑色节点个数相同,到达1、3节点黑色节点个数为2,而到7、9的黑色节点个数为3。

当叶子节点不存在兄弟节点这种情况时。需要加入null值,而null值充当黑色节点。

因此,将该图补充完整后如下

节点2的叶子节点与其他叶子节点路径上的黑色个数不同。因此也不能称为红黑树。

但是下图就属于红黑树

这种情况下,即使将null值加上,也满足红黑树

实现

大体框架

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, 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 node == null || node.color == BLACK;
    }

    //与平衡二叉搜索树的旋转相比,多了一步修改各个节点的parent属性修改
    private void rightRotate(Node node) {
        //被旋转节点的父结点
        Node parent = node.parent;
        //获取新的子树父结点
        Node newNode = node.left;
        //将新的子树父结点的右孩子充当旋转节点的左孩子,腾出新父节点的右孩子位置给被旋转节点
        Node rightChild = newNode.right;
        if (rightChild != null) {
            //如果右孩子不是null,那么将右孩子的父结点设置为被旋转节点
            rightChild.parent = node;
        }
        node.left = rightChild;
        //将新的子树节点的右孩子设置为被旋转节点
        newNode.right = node;
        //将新父节点的parent属性设置为被旋转节点的parent
        newNode.parent = parent;
        //将被旋转节点的parent属性设置为新的父结点
        node.parent = newNode;

        //修改被旋转节点的父结点的孩子属性
        if (parent == null) {
            //说明被旋转的节点为根节点
            root = newNode;
        } else if (parent.left == node) {//如果被旋转节点是父结点的左孩子
            //将新的左孩子设置为新的子树父结点
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
    }

    // 左旋
    private void leftRotate(Node node) {
        Node parent = node.parent;
        Node newNode = node.right;
        Node leftChild = newNode.left;
        if (leftChild != null) {
            leftChild.parent = node;
        }
        newNode.left = node;
        newNode.parent = parent;
        node.right = leftChild;
        node.parent = newNode;
        if (parent == null) {
            root = newNode;
        } else if (parent.left == node) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
    }
    
}

展示一下右旋的流程

旋转不需要进行变色,只需要修改移动节点的parent属性以及left或是right属性。

接下来针对红黑树特性,实现插入和删除代码

实现插入的功能

/**
 * 新增或更新
 * 正常增、遇到红红不平衡进行调整
 *
 * @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);
}

修正红黑树方法fixRedRed()存在四种情况:

首先需要知道的是插入节点均视为红色🔴

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

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

插入节点的父亲为红色🔴,触发红红相邻,红红相邻又分为case 3与case 4两种情况

case 3:叔叔为红色🔴

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

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

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

图示如下

接下来需要插入节点 1

触发红红相邻,且叔叔节点 4 也为红色

此时,不满足从根节点到任意叶子节点路径上黑色节点个数相同的条件,因此,需要把祖父节点 3变成红色

此时又触发了红红相邻,因此再次执行相同操作。

到达根节点时,将根节点变色就是实现了红黑树调整

case 4:叔叔为黑色⚫️

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

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

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

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

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

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

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

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

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

图示如下

接下来去添加节点2

触发红红相邻,经过调整后如下图所示

此时不平衡,需要进行一次右旋,旋转后结果如下

private 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);
    }
}

实现删除的功能

删之前我们需要清楚红黑树一个特性:删黑色会失衡,删红色不会失衡

一共存在下面几种情况:

一;如果删除的是叶子节点

如果是红色节点,直接删除就好

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

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

图示如下

比如说要删除节点 8,那么找到 8 的后继节点 9,并交换 8 与 9 的key与value

接下来就相当于删除叶子节点了。

case 1:

  • 删的是根节点
    • 删完了,直接将 root = null

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

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

图示如下

接下来去删除节点 2。

节点 3 顶替节点 2 的位置,但此时违背从根节点到叶子节点的黑色节点个数相同。因此,需要将剩下的这个节点修改为黑色

调整节点和剩下节点都是黑⚫️,触发双黑,双黑意思是,少了一个黑

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

  • 删除节点是左孩子,父亲左旋

  • 删除节点是右孩子,父亲右旋

  • 父亲和兄弟要变色,保证旋转后颜色平衡

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

图示如下

接下来要删除节点 4。父亲节点 6 左旋。

旋转过后颜色不平衡,需要修改被删除节点的原兄弟节点 8 与原父结点 6 的颜色。

此时删除节点 4 时,仍然会触发双黑,但是此时触发双黑走的是另一个逻辑case4或case5

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

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

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

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

当父结点为红色的情况,图示如下

将被调整节点 4 的兄弟节点 7 变色为红色,但此时节点6,7不调整的话触发双红,因此需要将父结点修改为黑色

修改过后,可以直接将删除节点去除。

以上情况是父结点为红色的情况,如果父结点为黑色,那么调整父结点颜色也无法使红黑树平衡。下面是父结点为黑色的情况。

需要删除节点 1。将兄弟节点 3 变红。但时父结点为黑色节点,那么将父结点作为调整节点再次执行双黑代码

被调整节点 2 仍满足case4的情况,因此将兄弟节点修改为红色。但父结点 4 依然是黑色节点,那么将父结点 4 作为新的被调整节点,执行双黑代码

此时被调整节点 4 仍满足case4的情况,因此将兄弟节点修改为红色。虽然父结点8仍然是黑色节点,但由于已经是根节点,因此结束触发双黑的代码。最后调整结果如下

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

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

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

    • 原来兄弟要成为父亲,需要保留父亲颜色

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

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

    • 右侄子会取代原来父亲,因此它保留父亲颜色

    • 兄弟已经是黑了⚫️,无需改变

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

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

    • 原来兄弟要成为父亲,需要保留父亲颜色

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

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

    • 左侄子会取代原来父亲,因此它保留父亲颜色

    • 兄弟已经是黑了⚫️,无需改变

接下来查看一种LL的情况,图示如下

首先要删除的节点为 4。删除后LL不平衡,因此对节点 3 进行一次右旋。

旋转过后,需要对节点颜色进行修改,首先是原来的兄弟节点 2 ,修改为原来的父亲节点 3 的颜色。新的两个孩子节点1 ,3修改为黑色。

再来看一种LR的情况,图示如下

要删除节点 4 ,需要将兄弟节点 1 进行一次左旋。

然后再将父结点 3 进行一次右旋

旋转过后,可以看到,原本兄弟节点 1 的右孩子 2 变成了新的父结点,因此,需要将 2 的颜色修改为原本的父结点 3 的颜色,将原本的父结点 3 的颜色修改为黑色。

具体实现代码如下

	public void remove(int key) {
        //得到被删除节点
        Node deleted = find(key);
        if (deleted == null) {
            return;
        }
        doRemove(deleted);
    }

    private void doRemove(Node deleted) {
        //替代被删除节点的节点
        Node replaceNode = findReplaced(deleted);
        Node parent = deleted.parent;
        //首先进行判断,如果要删除的节点是叶子节点
        if (replaceNode == null) {
            //如果是根节点
            if (deleted == root) {
                root = null;
            } else {
                if (isBlack(deleted)) {
                    //需要进行调整
                    fixDoubleBlack(deleted);
                }
                //如果不是根节点,判断是父结点的左孩子还是右孩子
                if (deleted.isLeftChild()) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
            }
            return;
        }
        //如果被删除节点存在一个孩子
        if (deleted.left == null || deleted.right == null) {
            if (deleted == root) {
                //如果是根节点,那么让该子节点直接顶替root节点即可
                root.key = replaceNode.key;
                root.value = replaceNode.value;
                replaceNode.parent = root.left = root.right = null;
            } else {
                if (deleted.isLeftChild()) {
                    //如果被删除节点是父结点的左孩子
                    parent.left = replaceNode;
                } else {
                    parent.right = replaceNode;
                }
                replaceNode.parent = parent;
                deleted.left = deleted.parent = deleted.right = null;
                //将被删除节点删除后,判断是否需要进行调整
                if (isBlack(deleted) && isBlack(replaceNode)) {
                    //如果删除节点与后驱节点都为黑色,那么删除一个黑色会导致黑色节点个数不同(这种情况不存在)
                    fixDoubleBlack(replaceNode);
                } else {
                    //如果被删除节点与后驱节点是一红一黑,那么都只需要将后驱节点颜色设置为黑色即可
                    replaceNode.color = BLACK;
                }
            }
            return;
        }
        //说明有两个孩子,replaceNode是该节点的后驱节点,这里我们采用值交换的方法来实现删除节点
        int t = deleted.key;
        deleted.key = replaceNode.key;
        replaceNode.key = t;

        Object value = deleted.value;
        deleted.value = replaceNode.value;
        replaceNode.value = value;
        //经过交换后,此时被删除节点只有一个孩子或是没有孩子。再次执行删除操作
        doRemove(replaceNode);
    }

    /**
     * 触发双黑的调整
     * @param node 被调整的节点
     */
    private void fixDoubleBlack(Node node) {
        if (node == root){
            return;
        }
        //被调整节点的父亲节点
        Node parent = node.parent;
        //被调整节点的兄弟节点
        Node sibling = node.sibling();
        //case 3代码,目的是调整红黑树为case4或case5的情况
        if (isRed(sibling)){
            if (node.isLeftChild()){
                leftRotate(parent);
            }else {
                rightRotate(parent);
            }
            parent.color = RED;
            sibling.color =BLACK;
            fixDoubleBlack(node);
            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);
                }
            }else {
                //case 5
                // 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;
            }

        }
    }

    private Node find(int key) {
        Node p = root;
        while (p != null) {
            if (p.key > key) {
                p = p.left;
            } else if (key > p.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;
    }

完整代码

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, 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 node == null || node.color == BLACK;
    }

    //需要处理的是,被旋转节点的孩子节点的parent属性修改,以及被旋转节点的父结点的孩子属性修改
    private void rightRotate(Node node) {
        //被旋转节点的父结点
        Node parent = node.parent;
        //获取新的子树父结点
        Node newNode = node.left;
        //将新的子树父结点的右孩子充当旋转节点的左孩子,腾出新父节点的右孩子位置给被旋转节点
        Node rightChild = newNode.right;
        if (rightChild != null) {
            //如果右孩子不是null,那么将右孩子的父结点设置为被旋转节点
            rightChild.parent = node;
        }
        node.left = rightChild;
        //将新的子树节点的右孩子设置为被旋转节点
        newNode.right = node;
        //将新父节点的parent属性设置为被旋转节点的parent
        newNode.parent = parent;
        //将被旋转节点的parent属性设置为新的父结点
        node.parent = newNode;

        //修改被旋转节点的父结点的孩子属性
        if (parent == null) {
            //说明被旋转的节点为根节点
            root = newNode;
        } else if (parent.left == node) {//如果被旋转节点是父结点的左孩子
            //将新的左孩子设置为新的子树父结点
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
    }

    // 左旋
    private void leftRotate(Node node) {
        Node parent = node.parent;
        Node newNode = node.right;
        Node leftChild = newNode.left;
        if (leftChild != null) {
            leftChild.parent = node;
        }
        newNode.left = node;
        newNode.parent = parent;
        node.right = leftChild;
        node.parent = newNode;
        if (parent == null) {
            root = newNode;
        } else if (parent.left == node) {
            parent.left = newNode;
        } else {
            parent.right = newNode;
        }
    }


    /**
     * 新增或更新
     * 正常增、遇到红红不平衡进行调整
     *
     * @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);
    }

    private 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);
        }
    }

    public void remove(int key) {
        //得到被删除节点
        Node deleted = find(key);
        if (deleted == null) {
            return;
        }
        doRemove(deleted);
    }

    private void doRemove(Node deleted) {
        //替代被删除节点的节点
        Node replaceNode = findReplaced(deleted);
        Node parent = deleted.parent;
        //首先进行判断,如果要删除的节点是叶子节点
        if (replaceNode == null) {
            //如果是根节点
            if (deleted == root) {
                root = null;
            } else {
                if (isBlack(deleted)) {
                    //需要进行调整
                    fixDoubleBlack(deleted);
                }
                //如果不是根节点,判断是父结点的左孩子还是右孩子
                if (deleted.isLeftChild()) {
                    parent.left = null;
                } else {
                    parent.right = null;
                }
            }
            return;
        }
        //如果被删除节点存在一个孩子
        if (deleted.left == null || deleted.right == null) {
            if (deleted == root) {
                //如果是根节点,那么让该子节点直接顶替root节点即可
                root.key = replaceNode.key;
                root.value = replaceNode.value;
                replaceNode.parent = root.left = root.right = null;
            } else {
                if (deleted.isLeftChild()) {
                    //如果被删除节点是父结点的左孩子
                    parent.left = replaceNode;
                } else {
                    parent.right = replaceNode;
                }
                replaceNode.parent = parent;
                deleted.left = deleted.parent = deleted.right = null;
                //将被删除节点删除后,判断是否需要进行调整
                if (isBlack(deleted) && isBlack(replaceNode)) {
                    //如果删除节点与后驱节点都为黑色,那么删除一个黑色会导致黑色节点个数不同(这种情况不存在)
                    fixDoubleBlack(replaceNode);
                } else {
                    //如果被删除节点与后驱节点是一红一黑,那么都只需要将后驱节点颜色设置为黑色即可
                    replaceNode.color = BLACK;
                }
            }
            return;
        }
        //说明有两个孩子,replaceNode是该节点的后驱节点,这里我们采用值交换的方法来实现删除节点
        int t = deleted.key;
        deleted.key = replaceNode.key;
        replaceNode.key = t;

        Object value = deleted.value;
        deleted.value = replaceNode.value;
        replaceNode.value = value;
        //经过交换后,此时被删除节点只有一个孩子或是没有孩子。再次执行删除操作
        doRemove(replaceNode);
    }

    /**
     * 触发双黑的调整
     * @param node 被调整的节点
     */
    private void fixDoubleBlack(Node node) {
        if (node == root){
            return;
        }
        //被调整节点的父亲节点
        Node parent = node.parent;
        //被调整节点的兄弟节点
        Node sibling = node.sibling();
        //case 3代码,目的是调整红黑树为case4或case5的情况
        if (isRed(sibling)){
            if (node.isLeftChild()){
                leftRotate(parent);
            }else {
                rightRotate(parent);
            }
            parent.color = RED;
            sibling.color =BLACK;
            fixDoubleBlack(node);
            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);
                }
            }else {
                //case 5
                // 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;
            }

        }
    }

    private Node find(int key) {
        Node p = root;
        while (p != null) {
            if (p.key > key) {
                p = p.left;
            } else if (key > p.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;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python-opencv在图片中绘制各种图形

python-opencv在图片中绘制各种图形 1.绘制直线 2.绘制矩形 3.绘制圆 4.绘制椭圆 5.绘制多边形 6.嵌入文字 实现代码都在下面了&#xff0c;代码中参数做了简单注释 import copy import math import matplotlib.pyplot as plt import matplotlib as mpl import numpy a…

OpenStack云计算平台-启动一个实例

目录 一、创建虚拟网络 ​二、创建m1.nano规格的主机 三、生成一个键值对 四、增加安全组规则 ​五、启动一个实例 1、确定实例选项 2、创建实例 3、使用虚拟控制台访问实例 4、验证能否远程访问实例 一、创建虚拟网络 下面的说明和框图使用示例IP 地址范围。你必须依…

单个视频生成视频二维码,手把手图文教程

单个视频生成视频二维码帮助教程&#xff08;图文教程&#xff09;&#xff0c;手把手教程如下&#xff1a; STEP1 注册帐号 使用视频二维码&#xff0c;您需要注册酷播云用户帐号&#xff08;免费5G空间&#xff0c;普通用户够用&#xff09;。 参考如图1-1&#xff0c;按照…

关于ElectronVue3中集成讯飞星火AI

前言&#xff1a;我的最终目的是为了在QQ上集成一个AI机器人&#xff0c;因此在这里先实现一个简单的集成 先上效果图 总体还是很简单的&#xff0c;我在调用websock获取回复内容的基础上另外集成了一个事件总线&#xff0c;让我们在调用获取消息的时候能够更加方便快捷 工具代…

前端学习--React(3)

一、Redux 集中状态管理工具&#xff0c;不需要react即可使用&#xff0c;每个store的数据都是独立于组件之外的 vue小链接&#xff1a;vuex/pinia 基本使用 Redux将数据修改流程分成三个概念&#xff0c;state、action和reducer state - 一个对象 存放我们管理的数据状态 a…

OpenStack云计算平台-镜像服务

目录 一、镜像服务概览 二、安装和配置 1、先决条件 2、安全并配置组件 3、完成安装 三、验证操作 一、镜像服务概览 OpenStack镜像服务是IaaS的核心服务&#xff0c;如同 :ref:get_started_conceptual_architecture所示。它接受磁盘镜像或服务器镜像API请求&#xff0c;…

2023服务端测试开发必备技能:Mock测试

什么是mock测试 Mock 测试就是在测试活动中&#xff0c;对于某些不容易构造或者不容易获取的数据/场景&#xff0c;用一个Mock对象来创建以便测试的测试方法。 Mock测试常见场景 无法控制第三方系统接口的返回&#xff0c;返回的数据不满足要求依赖的接口还未开发完成&#…

浅谈电气设备的绝缘在线监测与状态维修探究

贾丽丽 安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;在线监测是控制好电气设备绝缘的重要方式&#xff0c;为电力系统稳定奠定重要基础。在线监测电气设备时&#xff0c;要利用检测技术促进电力系统运行效率提升&#xff0c;让电气设备在具体工作过程中发挥更大作…

YAML 深入解析:从语法到最佳实践

什么是YAML YAML&#xff08;YAML Ain’t Markup Language&#xff09;是一种人类可读的数据序列化语言。它的设计目标是使数据在不同编程语言之间交换和共享变得简单。YAML采用了一种简洁、直观的语法&#xff0c;以易于阅读和编写的方式表示数据结构。 YAML广泛应用于配置文…

外部 prometheus监控k8s集群资源(pod、CPU、service、namespace、deployment等)

prometheus监控k8s集群资源 一&#xff0c;通过CADvisior 监控pod的资源状态1.1 授权外边用户可以访问prometheus接口。1.2 获取token保存1.3 配置prometheus.yml 启动并查看状态1.4 Grafana 导入仪表盘 二&#xff0c;通过kube-state-metrics 监控k8s资源状态2.1 部署 kube-st…

php生成xml数据

在PHP中&#xff0c;你可以使用以下几种方法生成XML数据&#xff1a; 使用DOM扩展&#xff1a; $xml new DOMDocument(1.0, UTF-8); $root $xml->createElement(root); $xml->appendChild($root); $child $xml->createElement(child); $root->appendChild($ch…

鸿蒙原生应用/元服务开发-AGC分发如何配置签名信息

使用制作的私钥&#xff08;.p12&#xff09;文件、在AGC申请的证书文件和Profile&#xff08;.p7b&#xff09;文件&#xff0c;在DevEco Studio配置工程的签名信息&#xff0c;以构建携带发布签名信息的APP。 1.打开DevEco Studio&#xff0c;菜单选择“File > Project S…

IP定位揭秘:如何揪出SEM、百度竞价恶意点击

在当今的数字营销领域&#xff0c;搜索引擎营销&#xff08;SEM&#xff09;和百度竞价成为了企业推广的重要手段。然而&#xff0c;随着这些渠道的普及&#xff0c;恶意点击现象也日益严重。恶意点击主要来自竞争对手&#xff0c;或是竞价服务的提供商&#xff0c;他们通过点击…

听GPT 讲Rust源代码--src/tools(2)

题图来自AI生成 File: rust/src/tools/rust-analyzer/crates/hir-def/src/src.rs rust-analyzer 是一个 Rust 语言的语法分析器和语义分析器&#xff0c;用于提供代码补全、导航、重构等开发工具。而 rust-analyzer 的代码实现存储在 rust/src/tools/rust-analyzer 这个文件夹中…

【数据结构】一题带你出师链表!

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 题目链接 138. 随机链表的复制https://leetcode.cn/problems/copy-list-with-random-pointer/ 题目描述 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机…

(保姆级教程)Mysql中索引、触发器、存储过程、存储函数的概念、作用,以及如何使用索引、存储过程,代码操作演示

讲解 MySQL 中索引、触发器、存储过程、存储函数的使用 文章目录 1. 索引1.1 索引的分类1.2 索引的设计原则1.3 如何使用&#xff08;create index&#xff09; 2. 触发器2.1 触发器的分类2.2 如何使用&#xff08;create trigger&#xff09; 3. 存储过程3.1 如何使用&#xf…

【尚跑】2023泾阳半程马拉松144 PB完赛

1、赛事背景 来到泾阳&#xff0c;就来到了中国大地原点&#xff1b; 来到泾阳&#xff0c;就来到了陕西的“白菜心心”&#xff1b; 来到泾阳&#xff0c;就来到了具有2000多年的历史长河&#xff1b; 泾河水缓缓流&#xff0c;流过郑国渠&#xff1b; 泾河水缓缓流&…

管理类联考——英语二——备考 100 句涵盖所有词汇

全中 在海里的这个地区&#xff0c;熊猫们喜欢就着苏打碗豆喝茶。而大洋州的民兵则喜欢经过半岛&#xff0c;带着编剧本的公式上餐厅去。附件的电影院里有额外的歌剧和香蕉&#xff0c;这一时代的斑马们被外面的天线所吸引。实验室里的蟹想用它的肋骨去戳四肢象灯炮的小羊。但…

经典滑动窗口试题(一)

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、将x减到0的最小操作数1、题目讲解2、讲解算法原理3、代码实现 二、无重复的最长子串1、题…
最新文章