红黑树

红黑树的存在是为了改变二叉排列树的性能,在最坏的时候,生成的二叉排列树就是链表,这种情况完全没有了二叉排列数的折半查找的优势,时间复杂度变回O(n)
在这里插入图片描述

什么是红黑树

红黑树本质上是一种二叉查找树,但它在二叉查找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为 O(logn)。正是因为这个,它的统计性能好于平衡二叉树;在 Java 集合框架中,很多部分(HashMap, TreeMap, TreeSet 等)都有红黑树的应用

红黑树的特性

在原来二叉查找树的基础上,红黑树又增加了以下几个要求

  1. 每个节点要么是红色,要么是黑色

  2. 根节点永远是黑色

  3. 每个叶子节点都是黑色(这里的叶子节点指的是为空的节点)

  4. 红色节点的两个子节点一定是黑色

  5. 从任一节点到其子树中每个叶子节点的路径都包含相同数量的黑色节点

同时他也要满足二叉排序树的基本性质:树中的任何节点的值大于它的左子节点,且小于它的右子节点。

这些性质保证了没有一条路径会比其他路径长处两倍,因而,红黑树是相对是接近平衡的二叉树

在这里插入图片描述

红黑树的基本操作

1. 插入

对于新插入的节点要先当作按照查找树的规则进行插入,同时也要将他涂成红色,涂成红色是因为:
① 在性质5中,从任意的节点出发到空叶子节点,经过的黑色节点个数是相同的,那么如果你当作黑色系节点插入,肯定会违背这个性质

② 同时由性质5可知,红黑树中黑色节点的个数至少是红色的两倍,所以新增的节点的父亲是黑色的概率较高,如果新增节点的父亲是黑色,而插入的是红色,那么不用再进行调整

但是这样插入可能会违背性质4,也就是说插入节点的父亲是红色的情况
下面是新增节点的几种不同情况

1.1 插入节点为根节点

这个时候只需把插入的红色节点变为黑色节点即可

1.2 插入节点的父亲节点为黑色节点

不需要任何调整

1.3 插入节点的父亲节点为红色,叔叔节点也为红色

因为红色节点的儿子都是黑色,所以要进行调整,策略是
将叔叔节点和父亲节点都变成黑色,爷爷改成红色,再把爷爷当成插入节点,重复上述操作,知道当前节点为根节点,然后根节点变为红色

例子
在下图插入125节点
在这里插入图片描述
① 插入125节点并且置为红色
在这里插入图片描述
② 将叔叔节点(150)和父亲节点(130)都置为黑色,再把爷爷节点变为红色
在这里插入图片描述
③ 然后将140节点当中新的插入节点处理重复以上操作,直到根节点再把根节点涂黑
在这里插入图片描述

1.4 新插入节点的父亲为红色,叔叔为黑色

1.4.1 父亲节点为爷爷节点的左孩子,新插入节点为父亲节点的左孩子

将父亲节点和爷爷节点的颜色互换然后针对爷爷进行右旋
例子
下图插入25
在这里插入图片描述
把父亲节点和爷爷节点的颜色互换(满足性质4),然后针对爷爷进行右旋(满足性质5)
在这里插入图片描述

1.4.2 父亲结点为爷爷结点的右孩子,新插入结点为父亲结点的右孩子

处理方式同上,只不过变为左旋

1.4.3 父亲节点是爷爷节点的左孩子,新插入节点为父亲节点的右节点

先把父亲节点当作当前节点进行左旋,变成1.4.1或1.4.2的情况,在进行调整
例子
下列插入126
在这里插入图片描述
① 先针对125进行左旋
在这里插入图片描述
② 这就变成1.4.1的情况,将父亲和爷爷交换颜色再以爷爷进行左旋
在这里插入图片描述

1.4.4 插入结点是左结点,父亲结点是右结点

以父节点为支进行右旋,再进行操作
在这里插入图片描述

private void fixAfterInsertion(Entry<K,V> x) {
    x.color = RED;  //直接染成红色,少点麻烦

    //这里分析的都是父亲节点为红色的情况,不是红色就不用调整了
    while (x != null && x != root && x.parent.color == RED) {
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 插入节点 x 的父亲节点位于左孩子    
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));  // y 是 x 的叔叔节点
            if (colorOf(y) == RED) {    //如果 y 也是红色,只要把父亲节点和 y 都变成黑色,爷爷节点变成红的,就 Ok 了
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {    
            //特殊情况 待插入节点为右,父亲为左
                if (x == rightOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateLeft(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateRight(parentOf(parentOf(x)));
            }
        } else {    //和上面对称的操作
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    root.color = BLACK;
}

2. 左旋和右旋详解

进行旋转是为了让一些节点上升一些节点下降,帮助树的平衡

2.1 左旋

此处,以50为支点进行逆时针旋转,然后75成为了顶点,50成为了75的左子节点,65成为了50的右子节点,这个操作就是左旋转。
在这里插入图片描述
在这里插入图片描述

private void rotateLeft(Entry<K,V> x) {
    if (x != null) {
        Entry<K,V> y = x.right; 
        x.right = y.left;       // 左旋后,x 的右子树变成了 y 的左子树 β 
        if (y.left != null)         
            y.left.parent = x;  //β 确认父亲为 x
        y.parent = x.parent;        //y 取代 x 的第一步:认 x 的父亲为爹
        if (x.parent == null)       //要是 x 没有父亲,那 y 就是最老的根节点
            root = y;
        else if (x.parent.left == x) //如果 x 有父亲并且是它父亲的左孩子,x 的父亲现在认 y 为左孩子,不要 x 了
            x.parent.left = y;
        else                            //如果 x 是父亲的右孩子,父亲就认 y 为右孩子,抛弃 x
            x.parent.right = y;
        y.left = x;     //y 逆袭成功,以前的爸爸 x 现在成了它的左孩子
        y.parent = x;
    }
}

2.2 右旋

以75为支点顺时针旋转,然后50成为了顶点,75成为了50的右子节点,65成为了75的左子节点,这就是右旋转操作
在这里插入图片描述
在这里插入图片描述

private void rotateRight(Entry<K,V> y) {
    if (y != null) {
        Entry<K,V> x = y.left;
        y.left = x.right;
        if (x.right != null) 
        	x.right.parent = y;
        x.parent = y.parent;
        if (y.parent == null)
            root = x;
        else if (y.parent.right == y)
            y.parent.right = x;
        else y.parent.left = x;
        x.right = y;
        y.parent = x;
    }
}

3. 删除操作

3.1 删除节点没有儿子

3.1.1 删除节点为红色

这种情况直接删除即可
例子
删除130节点
在这里插入图片描述
在这里插入图片描述

3.1.2 删除节点为黑色,其兄弟节点没有儿子

在这种情况下,根据红黑树的第五条性质,兄弟节点肯定也是黑色的,这个时候删除节点要把兄弟节点变红,父亲节点变黑

例子
删除150
在这里插入图片描述
① 删除150,把兄弟节点126变红,父亲节点140变黑
在这里插入图片描述

3.1.3 删除节点为黑,兄弟节点有一个孩子节点,且和兄弟节点在同一边

先删除节点,交换父亲和兄弟的颜色,再把父亲涂黑,把兄弟节点的子节点涂成黑色,接下来

① 如果兄弟结点和兄弟结点的儿子都在右子树的话:对父亲结点进行左旋

② 如果兄弟结点和兄弟结点的儿子都在左子树的话:对父亲结点进行右旋
例子
删除110
在这里插入图片描述
在这里插入图片描述

3.1.4 删除结点为黑色,其兄弟结点有一个孩子不空,并且该孩子和兄弟结点不在同一边

这种情况下,兄弟结点的儿子50结点只能为红色,要不然满足不了红黑树的第5条性质

我们要做的是把这种情况准换为3.1.3的情况,即让兄弟节点和儿子节点在同一边
例子
删除80
在这里插入图片描述
① 删除80 然后交换兄弟节点和兄弟节点的儿子节点的颜色

如果兄弟结点是左子树,兄弟结点的儿子结点是右子树:对兄弟结点进行左旋

如果兄弟结点是右子树,兄弟结点的儿子结点是左子树:对兄弟结点进行右旋

在这里插入图片描述
这就转换成了3.1.3的情况,按照3.1.3处理即可
在这里插入图片描述

3.1.5 删除结点为黑色,其兄弟结点有两个孩子,兄弟结点为黑色而且两个孩子结点也为黑色(为空也是黑色)

直接删除要删除的节点,然后将父亲节点和兄弟节点的颜色互换即可

在这里插入图片描述
在这里插入图片描述

3.1.6 删除结点为黑色,其兄弟结点有两个孩子,而且兄弟结点为红色

这种情况下兄弟节点的孩子节点必定都是黑色
例子
删除110
在这里插入图片描述
① 交换兄弟节点和父亲节点的颜色

被删除的元素为左子树:对父亲结点左旋

被删除的元素为右子树:对父亲结点右旋
在这里插入图片描述
② 现在和情况3.1.5很像,按照3.1.5处理即可

在这里插入图片描述

3.2 删除节点有儿子的情况

3.2.1 删除节点有一个儿子

这只可能是删除节点为黑色,唯一的儿子节点为红色
在这里插入图片描述
我们将需要删除的结点删除,然后将子节点涂黑放到被删除结点的位置
在这里插入图片描述

3.2.2 删除节点有两个儿子节点

找到删除结点的右子树中最左的结点,两两值交换,然后删除结点的情况就变成了上面两种情况中的一种了

① 删除结点只有一个儿子的情况

② 删除结点没有儿子的情况
在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值