红黑树的插入及调整过程(源码解读,有图有真相)

本文深入解析红黑树在插入新节点后的调整过程,包括四种旋转情况:左左、左右、右左、右右插入后的旋转逻辑及变色操作,结合源码详细说明修复红黑树平衡的步骤。

一、回顾

上一篇博客中,我们已经分析出了插入一个节点之后,红黑树需要如何进行调整对应的三种情形:

首先:新插入红黑树的节点一定是红色

  1. 若新插入节点的爸爸是黑色节点,红黑树不需要调整
  2. 若新插入节点的爸爸和它叔叔都是红色节点,红黑树只需要变色,不需要旋转
  3. 若新插入节点的爸爸是红色,但是它叔叔是黑色(可能为null,但是null是叶子节点,正儿八经的黑色),这时,一定是变色+旋转。

对于情形一二,我们给出了例子,也通过看源码得到了验证,现在我们就先来举一个例子,满足第三种情形:

首先看这样一个红黑树:
在这里插入图片描述
我们向其中插入【65】,它应该会成为【66】的左孩子
在这里插入图片描述
我们发现破坏了两个规则:1. 【66】和【65】都是红色 。2.【64】的右子树高度比左子树高2,也就是从根到叶子节点的路径上的黑色节点数不一样

首先,我们通过变色解决1

把【65】的爸爸【66】变成黑色;把【66】的爷爷【64】的爷爷变成【红色】
在这里插入图片描述
然后,无论怎么变色,都不会改变树的高度,所以我们必须借助旋转。

二、旋转操作的四种情形


1. 左左插入后旋转

这种情况下,父节点和插入的节点都是左节点

这种情况下,我们要插入节点【65】
在这里插入图片描述

规则如下:先【变色】解决连续红色问题,再【旋转】解决高度差问题

  • 把它爸爸【66】变成黑色,把它爷爷【69】变成红色
  • 它爷爷【69】右旋

在这里插入图片描述

2. 左右插入后旋转

这种情况下,父节点是左节点,插入的节点是右节点

这种情况下,我们要插入节点【67】
在这里插入图片描述

规则如下:先旋转使得偏向一边,再【变色】解决连续红色问题,再【旋转】解决高度差问题:

  • 它爸爸【66】左旋,偏向左边,左旋之后【66】变成了儿子,【67】变成了爸爸,(相当于是【66】成了新插入的红色节点)
  • 把它爸爸【67】变成黑色,把它爷爷【69】变成红色
  • 它爷爷【69】右旋
    在这里插入图片描述

可以想一下,为什么要先左旋??

因为它的爸爸是左孩子,可是它偏偏插在了右节点的位置,那么我现在不能确定到底哪边树高,要往哪边偏,那既然它爸爸本来是左孩子,我就先向左偏,左旋完后肯定是左边树高,并且你发现没??左旋完后就和第一种情况左左插一致了,所以后续【变色+右旋】两个过程也是一样的。

3. 右左插入后旋转

这种情况下,父节点是右节点,插入的节点是左节点

这种情况,我们要插入节点【68】
在这里插入图片描述

规则如下:先【旋转】使偏向一边,再【变色】解决连续红色问题,再【旋转】解决高度差问题

和情况2对比一下,你应该能想到,现在是爸爸是右孩子,儿子是左孩子,那么我们就先右旋,偏向右边,此时,它就变成了第四种情况(右右插),之后再【变色+左转】

  • 它爸爸【69】右旋,右旋后儿子爸爸身份互换,【68】成了爸爸,【69】成了儿子,相当于【69】成了新插入的红色节点)
  • 把它爸爸【68】变成黑色,把它爷爷【66】变成红色
  • 它爷爷【66】左旋

在这里插入图片描述

4. 右右插入后旋转

这种情况下,父节点和插入的节点都是右节点

这种情况下,我们要插入节点【70】
在这里插入图片描述

规则如下:先【变色】解决连续红色问题,再【旋转】解决高度差问题

  • 把它爸爸【69】变成黑色,把它爷爷【66】变成红色
  • 它爷爷【66】左旋
    在这里插入图片描述

三、总结

左左插:

  • 爸爸变黑色,爷爷变红色
  • 爷爷右旋

左右插:

  • 爸爸左旋,左旋之后爸爸成了儿子(也就是新插入的红色节点),之后和【左左插】一样
  • 爸爸变黑色,爷爷变红色
  • 爷爷右旋

右右插:

  • 爸爸变黑色,爷爷变红色
  • 爷爷左旋

左右插:

  • 爸爸右旋,右旋之后爸爸成了儿子(也就是新插入的红色节点),之后和【右右插】一样
  • 爸爸变黑色,爷爷变红色
  • 爷爷左旋

我们结合之前的情形,对 put() 过程做一个推测,大概是下面这个模式:

    public V put(K key, V value) {
   
   
        // 1. 从根节点往下,根据二叉排序树的特点,找到新的节点应该插入的合适位置
       
        // 2. 当找到合适位置后,根据键值大小,决定它要作为它爸爸的左孩子还是右孩子

        // 3. 插入之后,修复这颗红黑树
        fixAfterInsertion(e);
    }

fixAfterInsertion() 的过程应该是这样

    private void fixAfterInsertion(MyTreeMap.Entry<K,V> x) {
   
   
        // 1. 新节点【51】标记为红色
        x.color = RED;
        // 2. 如果它的爸爸是黑色,不进行变色也不用旋转,直接返回
        // 3. 如果他的爸爸是红色,而且肯定是有个循环逐步向上一直进行到根
        while (x != null && x != root && x.parent.color == RED) {
   
   
			  // 得到它的叔叔
              TreeMap.Entry<K,V> y = leftOf(parentOf(parentOf(x)));
              // 3.1 如果它的叔叔也是红色,那么只需要变色不需要旋转
              if (colorOf(y) == RED) {
   
   
              	// 把它爸爸变成黑色
                  setColor(parentOf(x), BLACK); 
                  // 把它叔叔变成黑色
                  setColor(y, BLACK); 
                  // 把它爷爷变成红色
                  setColor(parentOf(parentOf(x)), RED
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值