数据结构与算法【红黑树】的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;
    }
}
  • 9
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
算法数据结构涵盖了以下主要内容: 数据结构(Data Structures): 逻辑结构:描述数据元素之间的逻辑关系,如线性结构(如数组、链表)、树形结构(如二叉树、堆、B树)、图结构(有向图、无向图等)以及集合和队列等抽象数据类型。 存储结构(物理结构):描述数据在计算机中如何具体存储。例如,数组的连续存储,链表的动态分配节点,树和图的邻接矩阵或邻接表表示等。 基本操作:针对每种数据结构,定义了一系列基本的操作,包括但不限于插入、删除、查找、更新、遍历等,并分析这些操作的时间复杂度和空间复杂度。 算法算法设计:研究如何将解决问题的步骤形式化为一系列指令,使得计算机可以执行以求解问题。 算法特性:包括输入、输出、有穷性、确定性和可行性。即一个有效的算法必须能在有限步骤内结束,并且对于给定的输入产生唯一的确定输出。 算法分类:排序算法(如冒泡排序、快速排序、归并排序),查找算法(如顺序查找、二分查找、哈希查找),图论算法(如Dijkstra最短路径算法、Floyd-Warshall算法、Prim最小生成树算法),动态规划,贪心算法,回溯法,分支限界法等。 算法分析:通过数学方法分析算法的时间复杂度(运行时间随数据规模增长的速度)和空间复杂度(所需内存大小)来评估其效率。 学习算法数据结构不仅有助于理解程序的内部工作原理,更能帮助开发人员编写出高效、稳定和易于维护的软件系统。
当然,我可以为您介绍一些常见的数据结构与算法面试题。以下是一些常见的数据结构与算法面试题及其解答: 1. 请解释什么是数组? 数组是一种线性数据结构,它由相同类型的元素组成,并按照一定的顺序存储在连续的内存空间中。数组可以通过索引来访问和修改其中的元素。 2. 请解释什么是链表? 链表是一种线性数据结构,它由节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。链表的优点是插入和删除操作的时间复杂度为O(1),但访问元素的时间复杂度为O(n)。 3. 请解释什么是栈和队列? 栈和队列都是常见的数据结构。 - 栈是一种后进先出(LIFO)的数据结构,只允许在栈顶进行插入和删除操作。 - 队列是一种先进先出(FIFO)的数据结构,允许在队尾进行插入操作,在队头进行删除操作。 4. 请解释什么是二叉树? 二叉树是一种特殊的树结构,每个节点最多有两个子节点。二叉树可以分为二叉搜索树、平衡二叉树、完全二叉树等不同类型。 5. 请解释什么是排序算法? 排序算法是将一组数据按照一定的顺序进行排列的算法。常见的排序算法有冒泡排序、插入排序、选择排序、快速排序、归并排序等。 6. 请解释什么是动态规划? 动态规划是一种解决多阶段决策问题的优化方法。它通过将问题分解为多个子问题,并保存子问题的解,避免重复计算,从而提高算法的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zmbwcx2003

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值