一、回顾
在上一篇博客中,我们已经分析出了插入一个节点之后,红黑树需要如何进行调整对应的三种情形:
首先:新插入红黑树的节点一定是红色
- 若新插入节点的爸爸是黑色节点,红黑树不需要调整
- 若新插入节点的爸爸和它叔叔都是红色节点,红黑树只需要变色,不需要旋转
- 若新插入节点的爸爸是红色,但是它叔叔是黑色(可能为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

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

被折叠的 条评论
为什么被折叠?



