该系列已经全部更完,有5篇文章:
【算法】红黑树(二叉树)概念与查询(一):https://blog.csdn.net/lsr40/article/details/85230703
【算法】红黑树插入数据(变色,左旋、右旋)(二):https://blog.csdn.net/lsr40/article/details/85245027
【算法】红黑树插入数据的情况与实现(三):https://blog.csdn.net/lsr40/article/details/85266069
【算法】红黑树删除数据(寻找继承人)(四):https://blog.csdn.net/lsr40/article/details/85322371
【算法】红黑树删除数据(最后一步,平衡红黑树)(五):https://blog.csdn.net/lsr40/article/details/86711889
这里我总结为4种大情况,在第4种情况中,有阶段1和阶段2!
情况1:如果是根节点,直接插入就完事了(插入还是固定为红色,然后在代码的最后把根目录设置为黑色)
情况2:插入节点的父亲,为黑色,也一样,插入就完事了,不用做任何的改动
情况3:插入节点的父亲为红色,叔叔节点(插入节点的爷爷的另一个子节点)的颜色也是红色
情况4:插入节点的父亲为红色,叔叔节点节点为黑色(这种情况最麻烦,因为需要再做一次判断)
我先来描述下:这里可能会出现4种情况(别怕,四种情况只有2种处理方式),看图:
(爷爷节点我用G表示,爸爸:F,叔叔:U,插入节点:M)
图从左往右,从上到下看
图1:父节点是爷爷节点的左节点,插入节点是父节点的右节点
图2:父节点是爷爷节点的左节点,插入节点是父节点的左节点
图3:父节点是爷爷节点的右节点,插入节点是父节点的左节点
图2:父节点是爷爷节点的右节点,插入节点是父节点的右节点
是不是被绕晕了,其实就是爷爷和爸爸和插入节点要是否三点一线,如果不是三点一线,就符合情况4的第1阶段
如果是三点一线,就符合情况4的第2阶段!为什么是阶段1和阶段2呢?因为阶段1的处理方式,就是经过旋转变成阶段2,然后再做阶段2应该做的旋转!
情况1:这棵树没有任何节点,插入的点为根节点,一开始插入红色,然后直接转为黑色
情况2:父节点是黑色,直接插入,不做任何的换色和旋转
情况3:父节点是红色,且叔叔节点也是红色,直接把叔叔和爸爸变成黑色,然后把爷爷变成和自己一样的红色,继续迭代(因为这样可能会出现爷爷和太爷爷的都是红色的情况,那么就要继续判断是哪种情况)
情况4:父节点是红色,且叔叔节点也是红色,要先判断在哪个阶段
-1.如果符合阶段1,就是图1和图3
图1就对F节点做左旋,图3就对F节点做右旋,把图形的样子旋转成阶段2
-2.如果符合阶段2,就是图2和图4
图2就对G节点做右旋,然后将G变红色,F变黑色,如下图
图4就对G节点做左旋,然后G变红色,F变黑色,如下图
我们一起来思考下应该怎么实现?
1、外层要有循环
所以我们发现,一套变换下来,实现了局部满足红黑树的所有规则的,但是情况3将爷爷节点变成了红色,那就有可能爷爷和太爷爷变成了两个红色,相互冲突(违反规则4),所以代码应该外面有一层循环。
2、判断父节点是爷爷节点的哪边的子节点
因为情况4中,需要判断新插入的节点和父亲节点和爷爷节点是否三点一线,如果三点一线就直接进行阶段2的变换,否则要先进行阶段1,再进行阶段2。
3、其他设置
插入的节点必须为红色,代码的最后根节点必须设置为黑色,最好封装好左旋和右旋的方法,获取父节点,获取左右子节点的方法等待调用
如果上面三点大家能够想明白的话,我们再一起来看看TreeMap的put和fixAfterInsertion的源代码!
/**
* 我相信put的代码,我就不需要过多的解释了
* put就是判断是否有比较器,然后从根节点一层层往下遍历
* 最后插入到最底下的节点中,关键是插入完之后
* 调用了fixAfterInsertion方法(用来使插入节点后的树重新满足红黑树的性质)
*/
public V put(K key, V value) {
Entry t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
int cmp;
Entry parent;
// split comparator and comparable paths
Comparator super K> cpr = comparator;
if (cpr != null) {
do {
parent = t;
cmp = cpr.compare(key, t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
else {
if (key == null)
throw new NullPointerException();
@SuppressWarnings("unchecked")
Comparable super K> k = (Comparable super K>) key;
do {
parent = t;
cmp = k.compareTo(t.key);
if (cmp < 0)
t = t.left;
else if (cmp > 0)
t = t.right;
else
return t.setValue(value);
} while (t != null);
}
Entry e = new Entry<>(key, value, parent);
if (cmp < 0)
parent.left = e;
else
parent.right = e;
fixAfterInsertion(e);
size++;
modCount++;
return null;
}
private void fixAfterInsertion(Entry x) {
//将插入的新节点,颜色设置为红色
x.color = RED;
/**
* 当插入的节点x不为空,不是根节点,并且父节点是红色的
* 因为如果父节点是黑色的,直接插入红色的新节点是不会影响红黑树的性质的,不用做任何操作
* 所以到这里已经把情况1和情况2都过滤掉了,下面就是对情况3和情况4的处理
* 并不是处理一次就能得到最后的结果,大家还需要注意x的重新赋值,进入下一次循环
*/
while (x != null && x != root && x.parent.color == RED) {
//如果x的祖父节点的左节点是x的父节点,这里的判断就是为了情况4的三点一线
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
//那设置x的祖父节点的右节点为y(就是x的叔叔节点为y)
Entry y = rightOf(parentOf(parentOf(x)));
//如果叔叔y颜色是红色,那就是情况3
if (colorOf(y) == RED) {
//那么就设置x的父节点为黑色
setColor(parentOf(x), BLACK);
//叔叔节点y也设置为黑色
setColor(y, BLACK);
//设置祖父节点为红色
setColor(parentOf(parentOf(x)), RED);
//把x设置为祖父节点,继续往上游循环
x = parentOf(parentOf(x));
//否则叔叔y的颜色是黑色,情况4
} else {
//如果x是父节点的右节点,三点不一线,先做情况4的阶段1
if (x == rightOf(parentOf(x))) {
//把x设置为父节点
x = parentOf(x);
//基于刚刚新设置的x节点进行左旋
rotateLeft(x);
}
//情况4的阶段2
//设置x节点为黑色
setColor(parentOf(x), BLACK);
//祖父节点为红色
setColor(parentOf(parentOf(x)), RED);
//基于祖父节点进行右旋
rotateRight(parentOf(parentOf(x)));
}
//否则就是情况4的另外两种,父节点是爷爷节点的右子节点
} else {
Entry y = leftOf(parentOf(parentOf(x)));
//如果叔叔y是红色,那就是情况3
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
//把x设置为祖父节点,继续往上游循环
x = parentOf(parentOf(x));
} else {
//否则叔叔y是黑色,就是情况4,先判断是不是情况4的阶段1,
if (x == leftOf(parentOf(x))) {
//阶段1会把插入的节点转到上面去,所以我需要重新设置插入的节点为原来的父节点
x = parentOf(x);
rotateRight(x);
}
//执行情况4的阶段2
setColor(parentOf(x), BLACK);
setColor(parentOf(parentOf(x)), RED);
rotateLeft(parentOf(parentOf(x)));
}
}
}
root.color = BLACK;
}