《数据结构与算法之红黑树(Java实现)》

说在前头:本人为大二在读学生,书写文章的目的是为了对自己掌握的知识和技术进行一定的记录,同时乐于与大家一起分享,因本人资历尚浅,发布的文章难免存在一些错漏之处,还请阅读此文章的大牛们见谅与斧正。若在阅读时有任何的问题,也可通过评论提出,本人将根据自身能力对问题进行一定的解答。

前言

在前面的章节中,我们探讨了二叉搜索树,且针对二叉搜索树的弊端(在数据有序时,最坏的情况下树结构会退化成链表)介绍了二叉平衡树中的AVL树,这一章节我们继续来探讨另一种二叉平衡树——红黑树。

红黑树是一种特化的AVL树(平衡二叉树),都是在进行插入和删除操作时通过特定操作保持二叉查找树的平衡,从而获得较高的查找性能。它虽然是复杂的,但它的最坏情况运行时间也是非常良好的,并且在实践中是高效的:它可以在O(log n)时间内做查找,插入和删除,这里的n是树中元素的数目。

01—红黑规则

当插入(或者删除)一个节点时,必须遵循一定的规则,它们被称为红黑规则。如果遵循这些规则,树就是平衡的:(新插入的节点默认为红色

  • 每一个节点不是黑色就是红色

  • 根总是黑色的

  • 如果节点是红色的,则它的子节点必须是黑色的,反之则不一定成立

  • 从根到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(称为黑色高度,即两边的黑色高度需要保持一致)

02—颜色变换

我们继续引入上图中的红黑树,并为其插入新数据8,此时,数据8应该为10的左子节点(如下图),插入后结构显得不那么平衡了,但仍然遵守四条红黑规则,所以插入后的数据并未打破该结构的平衡。

但是当我们再插入一个数据6时,就会出现下面的情况

此时的结构明显违反了第三条红黑规则(红节点的子节点都应该为黑色),为了不让6节点破坏我们第三条的红黑规则,我们可以将6节点的颜色从红色转为黑色(如下图)

此时的结构已经遵守了第三条红黑规则(红节点的子节点为黑色),但新的问题又来了,我们为了让新的节点不破环规则三,对其进行了颜色的变换,这样使得我们破坏了第四个红黑规则(所有叶子节点的查找路径黑节点数目相同)。因为查找叶子节点6时,路径下有三个黑节点;查找叶子节点80时,路径下只有两个黑节点,两边黑色高度不一致,明显已经不遵守第四条红黑规则。

通过上面的实验不难得知,一棵树如果超过了两层不平衡(一边的子树比另一边的子树高两层以上),是不可能满足红黑规则的,因为如果一条路径上的节点数比另一条路径上的节点数多一个以上,那它或者有更多的黑色节点,或者有两个相邻的红色节点,都会违背红黑规则。

03—旋转

为了平衡一棵树,需要重新手动地排列节点,如果大部分节点都在根的左侧,就需要把一些节点移到右侧,称为左旋;如果大部分节点都在根的右侧,就需要把一些节点移到左侧,成为右旋。这里所说的选左旋与右旋是相对于一棵子树的根节点而言的。

下面我们来看看一个右旋的简单例子:(旋转前)

(旋转后)

在进行左右旋时,我们除了对节点进行旋转外,我们还需要对树的结构进行考虑,旋转的前后都需要遵循基本的二叉树规则和红黑规则(下面我们来举例一个较为复杂的右旋转)。

(旋转前)

(旋转后)

04—插入

插入或删除操作,都有可能改变红黑树的平衡性,利用颜色变化与旋转这两大法宝就可应对所有情况,将不平衡的红黑树变为平衡的红黑树。

在进行颜色变化或旋转的时候,往往要涉及祖孙三代节点:

  • X:表示操作的基准节点

  • P:代表X的父节点

  • G:代表X的父节点的父节点

05—插入情况一(X为外侧节点)

当基准点x在外侧的情况时,我们需要采取下面三个步骤:

  • 改变G的颜色

  • 改变P的颜色

  • 以G为中心进行向X上升的旋转

(旋转后)

06—插入情况二(X为内侧节点)

当X为内侧的节点时,会比在外侧复杂一点,我们需要将X从内侧节点通过颜色的变化和旋转变更为外侧节点,然后在进行旋转即可。主要可分为下面的四个步骤:

  • 改变G的颜色

  • 改变X的颜色

  • 以P为中心向X上升的方向旋转

  • 以G为中心向X上升的方向旋转

(初始状态)

(将内侧节点旋转为外侧节点)

(插入成功)

07—删除

与插入操作一样,我们在删除一个节点的时候,也有可能破坏一颗红黑树的结构,我们需要在规则被破坏时,对树进行一定的调整。

删除操作在大体上分为三类情况:

  • 需要删除的节点为叶子节点

  • 需要删除的节点有一个子节点

  • 需要删除的节点有两个子节点

08—需删除的节点为叶子节点

当需要删除的节点为叶子节点时,即需要删除的节点没有子节点,也会出现多种情况:

①需删除叶子节点为红色(如下图,当我们需要删除30或者50时,只需要将40对应的子节点置为null即可)

②需要删除的节点为黑色:

-.需要删除的黑色节点父节点为红色

②--.需要删除的节点的兄弟节点没有左子树:(如下图)

需要将30节点删除后,围绕40做左旋转,删除成功(如下图)

②--.需要删除的节点的兄弟节点存在左子树:(如下图)

在删除节点40时,我们需要围绕其兄弟节点做一次右旋转(如下图)

👇👇👇

-.需要删除的黑色节点父节点为黑色(兄弟节点为红色):

②--.需要删除的节点的左侄节点存在右节点:(如下图)

此时我们需要将左侄节点的右子节点涂黑,并且围绕左侄节点的父节点做右旋转(如下图)

最后我们只需要围绕节点55做左旋转即可得到最终的红黑树(如下图)

②--.需要删除的节点的左侄节点没有右节点:(如下图)

此时当我们删除节点50时,将左子侄几点涂红,左侄节点的左子节点涂黑,并且围绕左侄节点做右旋转得到下面的图(如下)

经过上述的操作后,左侄节点拥有了一个右子节点,这样我们就将情况②-②-②改变为了②-②-①,我们只需要再进行一次②-②-①的操作即可得到标准的红黑树。

②--③.需要删除的节点的左侄节为叶子节点:

针对这种情况,我们只需要对左侄节点涂红,及其父节点涂黑,最后围绕其爷节点左旋转即可得到标准的红黑树(如下图)

-③.需要删除的黑色节点父节点为黑色(兄弟节点为黑色):

②-③-①.兄弟节点存在右节点(如下图)

此时候将右侄节点涂黑,并围绕待删除节点的父节点进行左旋即可(如下图)

②-③-②.兄弟节点没有右节点(如下图)

这时需要将左侄节点涂黑,并围绕待删除的节点的兄弟节点左右旋转(如下图)

此时只需要做个简单的左旋转即可(如下图)

②-③-③.兄弟节点没有子节点(如下图)

如果我们删除了节点60,整颗树无法遵守第四条红黑规则,因为节点60的删除,导致70左边的黑色高度只有1,但右边为2,两边黑色高度不一致。此时,我们可以使节点75更改颜色为红色。(如下图)

08—需删除的节点存在一个子节点

如上图,如果需要删除节点60,左边的黑色高度减一,此时我们可以将节点55涂黑,代替原先的节点60时,树就可以重新遵守红黑规则。(如下图)

09—需删除的节点存在两个子节点

当需要删除的节点存在两个子节点时,我们又该怎么处理呢?前面我们强调过红黑树继承了二叉搜索树的基本属性,这时我们可以回想一下我们处理二叉搜索树的具体删除操作——寻找待删除节点的后继节点代替删除的节点。需要注意的是,因为红黑树相对二叉搜索树增加了红黑规则,所以寻找到后继节点后,并非直接与待删除节点直接替换,他们仅仅只需要替换数值即可。这样删除节点的操作变成了删除其后继节点。(如上图,待删除节点是60,其后继节点是63,待删除节点与后继节点交换数值,从而转变为删除后继节点,如下图)

👇👇👇

10—具体代码

节点类:

package com.bosen.www;
​
/**
 * <p>节点类</p>
 * @author Bosen 2021/6/17 23:37
 */
public class Node {
    private boolean isBlack;// 红黑标志(true为黑色,false为红色)
    private int data;       // 数据
    private Node parent;    // 父节点
    private Node left;      // 左子节点
    private Node right;     // 右子节点
​
    public Node(boolean isBlack, int data) {
        this.isBlack = isBlack;
        this.data = data;
    }
​
    @Override
    public String toString() {
        return "[" + (isBlack?"黑":"红") + ":" + data + "]";
    }
​
    public boolean isBlack() {
        return isBlack;
    }
​
    public void setBlack(boolean black) {
        isBlack = black;
    }
​
    public Node getParent() {
        return parent;
    }
​
    public void setParent(Node parent) {
        this.parent = parent;
    }
​
    public int getData() {
        return data;
    }
​
    public void setData(int data) {
        this.data = data;
    }
​
    public Node getLeft() {
        return left;
    }
​
    public void setLeft(Node left) {
        this.left = left;
    }
​
    public Node getRight() {
        return right;
    }
​
    public void setRight(Node right) {
        this.right = right;
    }
}

红黑树类:

package com.bosen.www;
​
/**
 * <p>红黑树</p>
 * @author Bosen 2021/6/17 23:36
 */
public class RedBlackTree {
    /*
     * 根节点
     */
    private Node root;
​
    public Node getRoot() {
        return root;
    }
​
    /*
     * 插入
     */
    public void insert(int data) {
        if (root == null) {
            // 红黑树为空
            root = new Node(true, data);
            return;
        }
        Node node = new Node(false, data);
        Node nodeParent = null;
        Node temp = root;
        // 看成二叉搜索树将数据节点插入,后面再修正
        while (temp != null) {
            nodeParent = temp;
            if (data < temp.getData()) {
                temp = temp.getLeft();
            } else {
                temp = temp.getRight();
            }
        }
        node.setParent(nodeParent);
        if (data < nodeParent.getData()) {
            nodeParent.setLeft(node);
        } else {
            nodeParent.setRight(node);
        }
        // 修正节点
        insertFixUp(node);
        root.setBlack(true);
    }
​
    /*
     * 删除
     */
    public void delete(int data) {
        // 定位待删除的节点
        Node node = select(data);
        Node parent, child;
        boolean isBlack;
        // 拥有两个节点
        if(node.getLeft() != null && node.getRight() != null) {
            Node replace = node.getRight();
            // 定位后继节点
            while (replace.getLeft() != null) {
                replace = replace.getLeft();
            }
            if (node.getParent() == null) {
                root = replace;
            } else {
                if (node.getParent().getLeft() == node) {
                    node.getParent().setLeft(replace);
                } else {
                    node.getParent().setRight(replace);
                }
            }
            // 后继节点的右子节点
            child = replace.getRight();
            parent = replace.getParent();
            // 保存后继节点的颜色
            isBlack = replace.isBlack();
            if (parent == node) {
                // 被删除节点为后继节点的父节点
                parent = replace;
            } else {
                if (child != null) {
                    // child不为空
                    child.setParent(parent);
                    parent.setLeft(child);
                }
                replace.setRight(node.getRight());
                node.getRight().setParent(replace);
            }
​
            replace.setParent(node.getParent());
            replace.setBlack(node.isBlack());
            replace.setLeft(node.getLeft());
            node.getLeft().setParent(replace);
​
            if (isBlack) {
                // 删除修正
                deleteFixUp(child, parent);
            }
            node = null;
            return;
        }
​
        // 拥有一个节点
        if (node.getLeft() !=null) {
            // 拥有左子节点
            child = node.getLeft();
        } else {
            // 拥有右子节点
            child = node.getRight();
        }
​
        parent = node.getParent();
        // 保存"取代节点"的颜色
        isBlack = node.isBlack();
​
        if (child!=null) {
            child.setParent(parent);
        }
        // "node节点"不是根节点
        if (parent!=null) {
            if (parent.getLeft() == node)
                parent.setLeft(child);
            else
                parent.setRight(child);
        } else {
            root = child;
        }
​
        if (isBlack) {
            deleteFixUp(child, parent);
        }
        node = null;
    }
​
    /*
     * 搜索节点
     */
    public Node select(int data) {
        Node temp = root;
        Node node = null;
        while (temp != null) {
            node = temp;
            if (data == node.getData()) {
                return node;
            }
            if (data < temp.getData()) {
                temp = temp.getLeft();
            } else {
                temp = temp.getRight();
            }
        }
        return node;
    }
​
    /*
     * 删除-修正红黑树
     */
    public void deleteFixUp(Node node, Node parent) {
        Node other;
​
        while ((node==null || node.isBlack()) && (node != root)) {
            if (parent.getLeft() == node) {
                other = parent.getRight();
                if (!other.isBlack()) {
                    // Case 1: x的兄弟w是红色的
                    other.setBlack(true);
                    parent.setBlack(false);
                    leftRotate(parent);
                    other = parent.getRight();
                }
​
                if ((other.getLeft()==null || other.getLeft().isBlack()) &&
                        (other.getRight()==null || other.getRight().isBlack())) {
                    // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
                    other.setBlack(false);
                    node = parent;
                    parent = node.getParent();
                } else {
​
                    if (other.getRight()==null || other.getRight().isBlack()) {
                        // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
                        other.getLeft().setBlack(true);
                        other.setBlack(false);
                        rightRotate(other);
                        other = parent.getRight();
                    }
                    // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
                    other.setBlack(parent.isBlack());
                    parent.setBlack(true);
                    other.getRight().setBlack(true);
                    leftRotate(parent);
                    node = root;
                    break;
                }
            } else {
​
                other = parent.getLeft();
                if (!other.isBlack()) {
                    // Case 1: x的兄弟w是红色的
                    other.setBlack(true);
                    parent.setBlack(false);
                    rightRotate(parent);
                    other = parent.getLeft();
                }
​
                if ((other.getLeft()==null || other.getLeft().isBlack()) &&
                        (other.getRight()==null || other.getRight().isBlack())) {
                    // Case 2: x的兄弟w是黑色,且w的俩个孩子也都是黑色的
                    other.setBlack(false);
                    node = parent;
                    parent = node.getParent();
                } else {
​
                    if (other.getLeft()==null || other.getLeft().isBlack()) {
                        // Case 3: x的兄弟w是黑色的,并且w的左孩子是红色,右孩子为黑色。
                        other.getRight().setBlack(true);
                        other.setBlack(false);
                        leftRotate(other);
                        other = parent.getLeft();
                    }
​
                    // Case 4: x的兄弟w是黑色的;并且w的右孩子是红色的,左孩子任意颜色。
                    other.setBlack(parent.isBlack());
                    parent.setBlack(true);
                    other.getLeft().setBlack(true);
                    rightRotate(parent);
                    node = root;
                    break;
                }
            }
        }
​
        if (node!=null) {
            node.setBlack(true);
        }
    }
    /*
     * 插入-修正红黑树
     */
    public void insertFixUp(Node node) {
        Node parent, uncle, grandpa;
        // 父节点存在且为红色
        while ((parent = node.getParent()) != null && !parent.isBlack()) {
            grandpa = parent.getParent();
            grandpa.setBlack(false);
            if (parent == grandpa.getLeft()) {
                uncle = grandpa.getRight();
                if (uncle!=null && !uncle.isBlack()) {
                    // 叔叔节点为红色
                    parent.setBlack(true);
                    uncle.setBlack(true);
                } else {
                    // 叔叔节点为黑色
                    if (parent.getLeft() == node) {
                        // node为父节点左节点
                        parent.setBlack(true);
                        rightRotate(grandpa);
                    } else {
                        // node为父节点右节点
                        node.setBlack(true);
                        leftRotate(parent);
                        rightRotate(grandpa);
                    }
                }
            } else {
                // 父节点为爷爷节点的右节点
                uncle = grandpa.getLeft();
                if (!uncle.isBlack()) {
                    // 叔叔节点为红色
                    parent.setBlack(true);
                    uncle.setBlack(true);
                } else {
                    // 叔叔节点为黑色
                    if (parent.getLeft() == node) {
                        // node为父节点左节点
                        node.setBlack(true);
                        rightRotate(parent);
                        leftRotate(grandpa);
                    } else {
                        // node为父节点右节点
                        parent.setBlack(true);
                        rightRotate(grandpa);
                    }
                }
            }
            node = grandpa;
        }
    }
​
    /*
     * 左旋
     */
    public void leftRotate(Node p) {
        Node x = p.getRight();
        // 将x的左孩子设为p的右孩子
        p.setRight(x.getLeft());
        if (p.getLeft() != null) {
            x.getLeft().setParent(p);
        }
        // 将p的父节点设置为x的父节点
        x.setParent(p.getParent());
        if (x.getParent() == null) {
            root = x; // 设置x为根节点
        } else {
            if (p.getParent().getLeft() == p) {
                // x设为左节点
                x.getParent().setLeft(x);
            } else {
                // x设为右节点
                x.getParent().setRight(x);
            }
        }
        // x的左孩子为p
        x.setLeft(p);
        // p的父节点设置为x
        p.setParent(x);
    }
​
    /*
     * 右旋
     */
    public void rightRotate(Node p) {
        Node x = p.getLeft();
        // 将x的右孩子设为p的左孩子
        p.setLeft(x.getRight());
        if (p.getRight() != null) {
            x.getRight().setParent(p);
        }
        // 将p的父节点设置为x的父节点
        x.setParent(p.getParent());
        if (x.getParent() == null) {
            root = x; // 设置x为根节点
        } else {
            if (p.getParent().getRight() == p) {
                // x设为右节点
                x.getParent().setRight(x);
            } else {
                // x设为左节点
                x.getParent().setLeft(x);
            }
        }
        // x的右孩子为p
        x.setRight(p);
        // p的父节点设置为x
        p.setParent(x);
    }
​
    /*
     * 中序遍历
     */
    public void display(Node node) {
        if (node != null) {
            display(node.getLeft());
            System.out.print(node + "\t");
            display(node.getRight());
        }
    }
}
​

 👇扫描二维码关注

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

云丶言

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

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

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

打赏作者

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

抵扣说明:

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

余额充值