源码
看Java的TreeMap源代码
5个性质
- 每个结点要么是红的要么是黑的
- 根节点是黑的
- 定义null(叶子节点的孩子)为黑节点
- 如果某个子结点是红色,那么他的两个子结点都是黑色,他的父节点也是黑色(就是不存在另个连接的红色结点)
- 对于任意节点来说,他到叶节点的每一条路径都包含数目相同的黑结点
插入后调整(fixAfterInsertion)
-
为了维持平衡,每一次插入后都必须进行调整,解决破坏上面五条性质的问题
-
每一个插入的结点在插入调整操作时会被设置为红色(新建结点默认是黑色),之所以调整时变红,是因为不想破坏黑高,也就是说性质五,但是在插入红色结点后,就会发生破坏性质4的情况,就是插入结点的父节点也是红色的,就是这样的情况下也有三种子情况:
- 先设置一下前情:x为当前结点,p为x的父节点,g为x的爷爷结点,且p为g的左孩子,y为p的兄弟结点,也就是x的叔叔结点, x和p为红色是前提;
- case1: y为红色,g为黑色. 解决 : 将p和y染黑,g染红,当前结点x执行g(我们一次只能处理爷孙三代的问题,但是当爷爷改变时,可能爷爷结点上面的结点又会发生破坏性质的问题,所以将当前结点指向爷爷结点,然后进入下一次循环再解决爷爷结点的问题,直到根节点为止);
- 有一个小知识,如果当前结点的爷爷结点是根节点,当我们完成一次修复后,根节点会被染红,这样破坏了性质二,所以需要将根节点染黑,这样所有结点的黑高就增加了1,这也是整个修复过程中唯一可以改变黑高的可能;
- case2: y为黑色, g为黑色,x是p的右孩子 ; 解决,首先左旋p结点(也就是x替代p的位置,x的孩子对应修改),这样的修改满足了性质五,但是没有满足性质4,所以需要继续修改;其实就是变成了情况三;
- case3 : y和g为黑色,p是g的左孩子,x是左孩子; 解决 – p染黑,g染红,右旋g(也就是说p代替了g的位置,对应孩子进行修改);这样修改之后,节点p为黑色的,就不会破坏其上结点的性质,再看下面的结点,右旋之后,p的左孩子是x,右孩子是g,二者都是红色的,没有破坏任何性质;调整就可以结束了;
- 整个调整的过程,就是在一个循环中,每一次循环需要判断是
which case
,然后进行修改;
-
AVL VS RBT
- 就在插入后调整方面对比来说,RBT是好与AVL的,可以看到,当发生case1时,RBT每次可以回溯到爷爷节点,而AVL只能回溯的父节点,这样的时间复杂度就是两倍的关系了;但这仅仅是插入后修改的对比;查询是AVL高于RBT的(极端情况下,RBT的左子树高度可以是右子树的两倍高);
删除后调整(fixAfterDelection)
- 如果删除的是红色节点的话,是没有影响的,所以只有删除的是黑色节点时,才需要修复;
- 前情标记 — 当前节点x,父节点p,兄弟节点s,左侄子ln,右侄子rn
- 删除左孩子x,有四种情况(右孩子镜像操作):
- case1: s是红色 : s染黑,p染红,左旋p,ln成为新的s;这样的修复后,并没有完成修改,还是违反了性质5,
- case2 : 共有条件:s,ln,rn都是黑色,然后又分为两种情况:
- case2-1: p为黑色,s染红,x回溯至p
- 修改完后,满足性质4,但是s和p的黑高都减少了1,违反性质5,需要继续修改p(指针在p)
- case2-2: p为红色,s染红,x回溯至p
- 这时违反性质4和5,但是只需要将p染黑,就可以解决两个问题,直接完成修复;
- case3: s为黑色,ln为红色,rn为黑色 : 解决: ln染黑,s染红,右旋s,s执行ln
- case4: s为黑色,p可红可黑,rn为黑色
- case4-1:ln为红色
- case4-2:ln为黑色
- 解决: s的颜色设置与p相同,p染黑,rn染黑,左旋p,x指向根节点