红黑树简介:
R-B Tree,全称是Red-Black Tree,又称为“红黑树”,是一种特殊的二叉查找树。红黑树的每个结点上都有存储位表示结点的颜色,可以是红(Red)或黑(Black)。
红黑树的定义:
- 每个结点是黑色或者红色。
- 根结点是黑色。
- 每个叶子结点(NIL)是黑色。 [注意:这里叶子结点,是指为空(NIL或NULL)的叶子结点!]
- 如果一个结点是红色的,则它的子结点必须是黑色的。
- 每个结点到叶子结点NIL所经过的黑色结点的个数一样的。[确保没有一条路径会比其他路径长出俩倍,所以红黑树是相对接近平衡的二叉树的!]
注:源码来源:JDK1.8 TreeMap
插入
红黑树插入方式与二叉树插入一致,只是多了一步调整。
插入调整:
前提条件:插入节点的父节点是红色时,需要调整(不满足定义4)。
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)))) {
代码1:
Entry<K,V> y = rightOf(parentOf(parentOf(x)));
代码2:
if (colorOf(y) == RED) {
setColor(parentOf(x), BLACK);
setColor(y, BLACK);
setColor(parentOf(parentOf(x)), RED);
x = parentOf(parentOf(x));
} else {
代码3:
if (x == rightOf(parentOf(x))) {
x = parentOf(x);
rotateLeft(x);
}
代码4:
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;
}
定义插入节点为 c,插入节点父节点为(父节点) p(红色) ,插入节点父节点的兄弟节点为(叔叔节点) u,插入节点的爷爷节点为g。
代码1:
插入节点c(默认为红色)插入后,不满足定义4,所以已u为入点调整树。
代码2: 调整点u为红色
先将p,u置为黑色,g置为红色。在将g节点视为插入点递归调整。
理解:
前提可知: p,u 是红色,若u是红色,根据定义4可得g是黑色。
插入后,为了满足定义4,定义5,将p,u置为黑色,g置为红色,保证左右子树每个分支黑色节点个数与插入前相同。
但变色后可能存在g的父节点是红色的情况下,例如图1.1 插入7,变色后得图1.2 ,g对于节点10,不满足定义4。所以将g做为新的插入点。
图1.1:
图1.2
代码3:
调整点u为黑色,插入节点c为父节点p的右孩子,此时将p设置为插入点 ,并左旋。
图1.3:
例如图1.3 此时插入节点5,形状类似 ‘ < ’,需要旋转变为‘ / ’。旋转后将4当做新插入点。
图1.4:
代码4:
调整点u为黑色(可能为null),先变色,在旋转。
理解:
调整点u为空的情况,例如图1.3,此时插入2,不满足定义4,变色;变色后,不满足定义5,旋转。
调整点u不为空的情况,例如图1.4,此时插入9,此时p为黑色,这种情况不满足插入调整,不必调整。
对于图1.3,此时插入2。可得:
节点4为黑色,即使4存在父节点,必能满足定义4,且调整后分支黑色节点个数没有发生改变,必能满足定义5,所以结束调整。
插入总结:
1、先把插入节点的父亲的兄弟节点做为初始调整点。
2、若调整点为红色,先做变色,将在爷爷节点做为调整点循环调整。
3、若调整点为黑色,先把插入后树形状 为 ‘< >’,处理为 ‘ / \ ’并变色。
4、变色旋转,结束调整。
5、 循环结束后,将根节点变为黑色。
删除
二叉树删除
- 删除节点有一个孩子,用子孩子顶替删除节点。
- 删除节点没有子孩子,直接用null节点顶替删除节点。
- 删除节点有两个子孩子,找到删除节点的前驱或后继节点( 左子树下最大的节点或者右子树下最小的节点 ),将前驱或后继节点的值赋给删除节点,并做为新的删除节点。
红黑树删除多一步调整。
删除调整
前提:删除节点不是根节点,并且颜色为黑色(不满足定义5)
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) {
左删: 删除节点为父节点的左孩子
if (x == leftOf(parentOf(x))) {
代码1
Entry<K,V> sib = rightOf(parentOf(x));
代码2
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateLeft(parentOf(x));
sib = rightOf(parentOf(x));
}
代码3
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
代码4
if (colorOf(rightOf(sib)) == BLACK) {
setColor(leftOf(sib), BLACK);
setColor(sib, RED);
rotateRight(sib);
sib = rightOf(parentOf(x));
}
代码5
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(rightOf(sib), BLACK);
rotateLeft(parentOf(x));
x = root;
}
}
右删:删除节点为父节点的右孩子 与左删对称
else {
Entry<K,V> sib = leftOf(parentOf(x));
if (colorOf(sib) == RED) {
setColor(sib, BLACK);
setColor(parentOf(x), RED);
rotateRight(parentOf(x));
sib = leftOf(parentOf(x));
}
if (colorOf(rightOf(sib)) == BLACK &&
colorOf(leftOf(sib)) == BLACK) {
setColor(sib, RED);
x = parentOf(x);
} else {
if (colorOf(leftOf(sib)) == BLACK) {
setColor(rightOf(sib), BLACK);
setColor(sib, RED);
rotateLeft(sib);
sib = leftOf(parentOf(x));
}
setColor(sib, colorOf(parentOf(x)));
setColor(parentOf(x), BLACK);
setColor(leftOf(sib), BLACK);
rotateRight(parentOf(x));
x = root;
}
}
}
代码6
setColor(x, BLACK);
}
定义删除节点为 d,删除节点父节点为 p,删除节点的兄弟节点为 b。
代码1
删除d,根据定义5,直接影响p的左右子树黑色节点个数不平衡,b子树的黑色节点个数多余,所以将b做为初始的调整点。
代码2
调整点b位红色, 先将b置为黑色,p置为红色,围绕p右旋。后将p的右孩子做为新的调整点。
理解:
前提可知:b节点为红色,根据定义4,可得p节点和b的左右孩子为黑色。(d为黑色,删除前,必定满足定义5,即p的子树中必有黑色节点存在,又因b为红色,为满足定义4,b的左右孩子必存在且为黑色。)例如图2.1删除节点4。
d删除后,不满足定义5,所以将b置为黑色,p置为红色,围绕p右旋(旋转后b的左孩子变为p的右孩子)。相当于将b顶替p,并分了一个黑色节点的左孩子给p的右子树,弥补d的删除。
但旋转后,p左孩子为空,右孩子为黑色,不满足定义5,p右子树黑色节点多余,所以将p的右孩子做为新的调整点。
例如图2.1删除节点4,变色旋转后得图2.2,此时p对应节点8,将节点9做为新的调整点。(相当于把调整点为红色的情况转化调整点为黑色的情况,如图2.2 ,想象下删除节点4的情况)
图2.1
图2.2
代码3
调整点b为黑色,且调整点b左右孩子为黑色,将b置为红色,并将p做为新的删除点。
理解:
前提可知:到代码3这里,b的颜色一定是黑色(在代码2处被调整),由于d是黑色,为满足定义5,所以这里b没有黑色的子孩子(若b有子节点也一定是红色)。
此时只能将b置为红色,让p的左右子树先满足定义5,并将p做为新的删除点向上调整。
此时p的颜色2种考虑:
p的颜色若为红色,如图2.3,删除节点9 ,最终在代码6出被调整;
p的颜色若为黑色,如图2.4,删除节点12,将25变红后,不满足定义5,要将23做为新的删除点向上调整。
图2.3
图2.4
代码4
调整点b的右孩子置为黑色,调整点b置为红色,并绕调整点右旋,将调整点b的右孩子置为新的调整点。
理解:
前提可知:经过代码2,代码3,b为黑色,且b至少有1个孩子且为红色。
若b的右孩子为黑色,必为空(代码4处已说明),则b的左孩子一定为红色节点,删除d后,树的形状为‘>’,所以要先变色旋转,变为‘ \ ’。例如如2.5 删除节点7,此时9-13-12 形状为‘ >’ 。
图2.5
代码5
将调整u置为p的颜色,将p与u的右孩子置为黑色,在绕p左旋,结束调整。
理解:此处做法是用调整点u顶替p,p顶替删除节点d,主要目的是为了满足定义5,让删除前后分支黑色节点个数没有变化。。
例如图2.6 删除7 的图2.7 ,删除前后分支黑色节点个数没有变化,所以结束调整。
图2.6
图2.7
删除总结:
1. 将删除节点的兄弟节点做为调整点(可能为红色,可能为黑色)。
2. 将调整点为红色的情况转化为调整点为黑色的情况,继续调整。
3. 将调整点无孩子的情况,变色,取父节点位新的删除点,向上循环调整。
4. 将调整点有孩子的情况,删除后调整点部分形状类似 ‘> ’ '< ',先做变色,旋转变为 ‘ \ ’ ’ / '。
5. 变色旋转,结束调整。
6. 删除点置为黑色。