红黑树是一个特殊的二叉搜索弱平衡树,它是为了改善使用平衡树时需要大量维持平衡的操作二产生的。对于红黑树而言,实现的是黑节点的平衡,所以插入后继续维持红黑树的特性也较为方便。今天就来探讨红黑树的插入过程。
一、红黑树的规则:
红黑树是每个节点都带有颜色属性的二叉查找树,颜色或红色或黑色。
🔊红黑树的五个特征:
- 节点是红色或黑色。
- 根节点是黑色。
- 所有叶子都是黑色。(页子是NULL节点)
- 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
这些约束强制了红黑树的关键性质: 从根到叶子的最长的可能路径不多于最短的可能路径的两倍长。整棵树的黑节点时平衡的。当整个路径都是黑节点时,就是最短的路径,反之如果路径中时很色和红色节点交替,就是最长的路径。
当我们在对红黑树进行插入和删除等操作时,对树做了修改,那么可能会违背红黑树的性质,这时候就需要进行树的调整,使之再次成为一个红黑树,以下篇幅就是介绍了当插入一个节点后,红黑树进行的调整。
二、红黑树的插入图解:
首先说明:插入的节点是红色节点
🔊在插入时,有两种情况:
- 插入后没有破坏规则
- 插入后破坏了规则
对于没有破坏的情况,就是插入节点的父节点是一个黑节点,这样由于没有破坏黑平衡,所以可以直接插入。
我们要考虑的就是会破坏规则的情况,也就是插入节点的父节点是一个红节点,这时后就要对其进行调整,使得重新满足红黑树规则4。
破坏规则的所有形态:
根据上面的规则,插入的节点都是红色。我们约定 cur
作为插入的节点,parent
作为插入节点的父节点,grandparent
作为插入节点的父节点的父节点,uncle
作为插入节点的父节点的兄弟节点。接下来看插入时会出现的8种样子:
1. 当 cur 的parent是grandparent的左孩子时:
2. 当 cur 的parent是grandparent的右孩子时:
总之,我们分类就是按照 parent节点是其父节点的左右节点和uncle节点的颜色
虽然,插入时有这8种形态,但总结下来的情况只有三种。接下来就来逐一学习这三种情况:
⭐情况 1:
先来将最简单的第一种:uncle节点为红 对应上面形态标号的 1,2,5,6
此时为了满足两个红节点不能连在一起的要求,就要进行调整,这种调整也很简单:
📌只需要把parent
和uncle
的节点转黑,相应grandparent
节点转红 , 调整后有可能会破坏上面数的规格,那就继续循环向上调整。
先来看图解:
来讲下为什么可以这样调整:
uncle
节点为红,那么可以知道uncle
如果还有孩子节点,那么孩子节点也只能都是黑节点,否则就不满足性质4了,所以为了达到黑平衡,就可以直接将grandparent
的两个孩子都置为黑节点,同时将grandparent
节点置为红。随着这个节点置为红,可能就会破坏和其父节点之间的规则,这时候就可以将其看作一个新的cur进行插入,一直按插入规则向上调整直到符合规格。
根据上面的调整原理可知道,只要这个节点的uncle
是红色,那么都可以直接转换为情况1,也就是直接换颜色就好,所以上面的1,2,5,6 的情况都可以这样调整。
⭐情况 2 :
接下来是第二种情况:uncle节点为黑,且是左左型或者右右型,对应上面标号的情况 4,8
也就是这两种:
那么,对于这种情况怎么修复呢? 修复规则是:📌左左型对parent
右旋,右右型对parent
左旋,接着将parent
置为黑色,grandparent
置为红色
还是看图理解:
左左型:
右右型:
上面的图片是cur没有子树的变化过程,接着来看看有子树的情况,都是一样的规则:
这样调整之后,由于将parent置为黑色,所以不会继续破坏上面节点的规则,也就不需要向上调整了。
这里来解释下为啥上面图上看起来不满足规则五(就是黑节点数目一样)
对于grandparent节点来说:
- 如果uncle节点为null,那 null 节点自身就是黑节点,而没插入 cur 之前 parent 也有为 null 的叶子节点,所以两个子树的黑节点是一样的。
- 如果uncle不为空,uncle是有可能有子树的,那parent和cur也是有子树的,这种情况就来源于情况1中的由于调整一次后使得上面的树规则被破坏,因此还需要转化cur继续向上调整的情况。
⭐情况 3 :
最后一种情况就是uncle节点为黑,且是左右型或者右左型对应上面情况的3,7也就是这两种:
对于这种形状的修复就是:📌左右型对parent
左旋,右左型对parent
右旋,原来的cur
变parent
,原来的parent
变cur
接着按照情况二修复
先来看图:
可以看出这种情况在进行这小小的调整后就可以转化为情况 2,这样就可以使用情况2 的修复规则进行修复。
所以我们是不是可以把情况2和情况3看成是一种情况的两个阶段呢?情况2 和 情况3 都可以看作是uncle节点为黑的情况,其中情况3 是第一阶段,情况2 是第二阶段,当判断到uncle节点为黑时,接着判断是否为情况3:如果是,那就处理到情况2的形态;如果直接是情况2,那就直接以情况2来修复。
最后要统一把根置为黑色。
🎈我们来总结下上面的情况:
- 如果parent为黑,直接插入
- 否则,如果uncle为红:uncle、parent变为黑色,grandparent变为黑色
- 否则,uncle为黑,判断是否左右或右左型
(1)如果是,对parent进行左旋或右旋,交换parent和cur
(2)对parent进行左旋或右旋,parent变为黑色,grandparent变为红色 - 将根节点置为黑色
三、红黑树的插入源码
对于代码的实现来说,还是遵守上面的情况,理解思路后,源码也比较容易看懂了。
展示源码的各部分对应的情况:
贴上源码:
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)))) {
Entry<K,V> y = rightOf(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 == 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;
}
唠唠叨叨:
红黑树插入基本就是这样子了,看似很复杂其实分解归类后发现也不难,所以自己总结一次真的可以收获很多,也可以理清自己的思路,所以建议大家学完后自己进行总结下下。另外如果想更进一步到的了解红黑树,推荐一个博主的一篇文 红黑树,写的很棒相信看完后大家可以更加进一步理解红黑树的各种规则了。如果这篇文章右任何问题欢迎评论指正,也欢迎小伙伴们点赞关注一起进步!