红黑树笔记
前言
TreeMap理解的时候遇上了红黑树,每次都把自己卡的死死的。。
终于拿出决心把它给搞懂了。
说明
一篇不错的文章:https://www.cnblogs.com/wskwbog/p/11236136.html
本笔记中大部分资料来自于此文章,红黑树理解成2-3-4树的变形 就感觉概念上好理解了。
五条性质
1.每个结点都是红色或黑色的
2.根结点是黑色的(是红色最终也会转黑色)
3.所有叶子结点都是黑色的,这里的叶子结点指的是空结点,常用 NIL 表示
4.如果结点为红色,则其子结点均为黑色(红色表示可与父结点合并,子结点凑什么热闹)
5.从给定结点到其任何后代 NIL 结点的每条路径都包含相同数量的黑色结点(转成 2-4 树,所有叶子结点均在最底层)
主要是弄懂 插入和删除操作。
在这之前 先弄懂旋转
左旋转 旋转结点为p
1.结点p不为空
1.r = p的右结点 待用
2.p的右结点 = r的左结点
3.如果r的左结点 != null,r的左结点的父结点 = p
4.r的父结点 = p的父结点
5.如果p的父结点 == null,根结点设为 r
6.否则p的父结点的左结点 == p,p的父结点的左结点 = r
7.否则,p的父结点的右结点 = r
8.r的左结点 = p
9.p的父结点 = r
根据上文写出右旋转代码
void rotateRight(Entry<K,V> p) {
if (p != null) {
Entry<K,V> l = p.left;
p.left = l.right;
if (l.right != null)
l.right.parent = p;
l.parent = p.parent;
if (p.parent == null) {
root = l;
} else if (p.parent.left == p) {
p.parent.left = l;
} else {
p.parent.right = l;
}
l.left = p;
p.parent = l;
}
}
左右旋转OK
现在看插入
假设待插入结点为N P是N的父结点 G是N的祖父结点 U是N的叔叔结点
待插入的点默认都是红色
1.N是根结点,即红黑树的第一个结点
2.N的父结点(P)为黑色
不影响红黑树的性质,不会打破平衡,直接插入
3.P是红色的(不是根结点),它的兄弟结点U也为红色
P和U变成黑色,G变成红色,若G是根结点,直接变黑,否则递归向上检查是否平衡
4.P为红色,而U为黑色
P是G的左孩子,若N是P的左孩子,那么将祖父结点G右旋转即可
P是G的左孩子,若N是P的右孩子,那么P先左旋转,然后再将祖父结点G右旋转
P是G的右孩子,若N是P的右孩子,那么将祖父结点G左旋转即可
P是G的右孩子,若N是P的左孩子,那么P先右旋转,然后再将祖父结点G左旋转
旋转完后向上递归染色
/** From CLR */
private void fixAfterInsertion(Entry<K,V> x) {
x.color = RED; // 新增的x 都是红色
while (x != null && x != root && x.parent.color == RED) { // 当x不为空 而且x不为root 而且x的父结点P是红色
if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // P == GL 父结点是祖父结点的左结点
Entry<K,V> y = rightOf(parentOf(parentOf(x))); // y 为 U 叔叔结点
if (colorOf(y) == RED) { //P红 U 为红色
setColor(parentOf(x), BLACK); // P 变黑
setColor(y, BLACK); // U 也变黑
setColor(parentOf(parentOf(x)), RED); // G 变红
x = parentOf(parentOf(x)); // x 变为 祖父结点 这里用于循环判读 如果G不是根节点 会向上递归
} else { //P红 U为黑色
if (x == rightOf(parentOf(x))) { // x 如果是 PR
x = parentOf(x); // x 变为 P
rotateLeft(x); // x左旋 相当于P左旋
}
setColor(parentOf(x), BLACK); // P 设置为黑
setColor(parentOf(parentOf(x)), RED); // 祖父结点设为红色
rotateRight(parentOf(parentOf(x))); // 祖父结点右旋
}
} else { // P == GR 父结点是祖父结点的右结点
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;
}
现在看删除
先看2-3-4树结点删除
1.如果元素K是内部结点,并且在一个至少有2个key的多值叶子结点内部,则只需从结点中删除K
2.如果元素K是内部结点,并且有左孩子和右孩子,那么:
2.1如果左孩子至少有2个key,那么找到一个最大值替换K,然后删除这个最大值
2.2如果右孩子至少有2个key,那么找到一个最小值替换K,然后删除这个最小值
2.3如果两个孩子都只有1个key,那么将K下沉,与其子女合并,形成一个至少有2个key的结点,最后再删除K
3.如果元素K不是内部结点,所在结点只有它1个key,那么根据以下情况,最终会转成情况1或情况2
3.1如果它的兄弟结点内至少有2个key,那么选择一个推到父结点中,再把旧的父结点下沉和K合并
3.2如果它的兄弟结点也只有1个key,那么将父结点下沉,与其子女合并,再删除K,所以,此时需要父结点至少有2个key,如果没有那么在父结点上递归按情况3处理。
以上的情况,有一个前提,在遍历查找待删除结点时,必须保证路过的结点都至少有2个key,不是的话就需要合并结点。
假设待删除结点为M,如果有非叶子结点,称为C,那么有两种比较简单的删除情况
1.M为红色结点,那么它必是叶子结点,直接删除即可,因为如果它有一个黑色的非叶子结点,那么就违反了性质5,通过M向左或向右的路径黑色结点不等
2.M是黑色而C是红色,只需要让C替换到M的位置,并变成黑色即可,或者说交换C和M的值,并删除C
3.M和C都是黑色,此时M肯定是叶子结点,而C肯定是NIL结点,如果不是这样的情况将违反性质5
一个黑色结点被删除会打破平衡,需要找一个结点填补这个空缺,假设待删除结点为M,删除后它的位置上就变成了NIL结点,为了方便描述,这个结点记为N
P表示N的父结点,S表示N兄弟结点,S如果存在左右孩子,分别使用SL和SR表示
3.1 N是根结点
删除后,N变成了根结点,也就是说删除前只有M这一个结点,直接删除即可。
3.2 P黑S红
S是红色,那么它必有两个孩子结点,且都为黑色,而且P也肯定是黑色。此时,交换P和S的颜色,然后对P左旋转。
结点N的父结点P变成了红色,兄弟结点变成了SL,此时就可以按照情况4、5、6继续处理。
3.3 P黑S黑
P是黑色,S也是黑色,并且S也没有非空的孩子结点。此时,直接将S变成红色,那么经过S的路径也就少了一个黑色结点,整体上就导致经过P的路径比原来少了一个黑色结点,
把不平衡状态从结点N转移到了结点P,就可以把P按情况1处理,直到遇到根结点,以此形成递归
3.4 P红S黑
P是红色,S是黑色,并且S也没有非空的孩子结点。此时,只要交换P和S的颜色,正好填补了少一个黑色结点的空缺,也就是恢复了平衡的状态
3.5 P任意S黑SL红
P任意颜色,S黑色,S的左孩子红色(S有右孩子也是红色)。此时,对S右旋转,并交换S和SL的颜色,其实就是把这种情况,转成了情况6进行处理
3.6 P任意S黑SR红
P任意颜色,S黑色,S的右孩子红色(S有左孩子也是红色)。此时,对P左旋转,并交换P和S的颜色,并将SR变成黑色
此时恢复平衡的状态,无论P之前是什么颜色,N都比之前多了一个黑色父结点。假设P原先是红色的,现在变成了黑色;假设原先是黑色的,现在P又多了一个黑色的父结点S
所以,无论怎样,经过结点N路径增加了一个黑色结点。
以上6中情况,结点N都是左孩子,如果是右孩子,只需把左右对调即可。
private void deleteEntry(Entry<K,V> p) {
modCount++;
size--;
// If strictly internal, copy successor's element to p and then make p point to successor.
// 如果严格是内部的,则将继承者的元素复制到p,然后生成p指向后续任务。
if (p.left != null && p.right != null) {
Entry<K,V> s = successor(p); // 必存在右结点,因此这个方法返回的s为p右结点里的最小值的结点
// 要删除的结点的值 key 和 value 都被替换了。
p.key = s.key;
p.value = s.value;
// 用要代替的结点替换删除结点 暂时引用//地址不变。
p = s;
} // p has 2 children
// Start fixup at replacement node, if it exists.
// 在替换节点(如果存在)上启动修正。
Entry<K,V> replacement = (p.left != null ? p.left : p.right);
if (replacement != null) {
// Link replacement to parent
replacement.parent = p.parent;
if (p.parent == null)
root = replacement;
else if (p == p.parent.left)
p.parent.left = replacement;
else
p.parent.right = replacement;
// Null out links so they are OK to use by fixAfterDeletion.
// 空链接,以便FixAfterDeletion可以使用它们。
p.left = p.right = p.parent = null;
// Fix replacement
// 替代结点为黑色时,要修正
if (p.color == BLACK)
fixAfterDeletion(replacement);
} else if (p.parent == null) { // return if we are the only node. 唯一结点直接返回。
root = null;
} else { // No children. Use self as phantom replacement and unlink. 没有孩子。将自身用作幻象点替换并取消链接。
if (p.color == BLACK)
fixAfterDeletion(p);
if (p.parent != null) { // 将p的链接断开
if (p == p.parent.left)
p.parent.left = null;
else if (p == p.parent.right)
p.parent.right = null;
p.parent = null;
}
}
}
/**
* Returns the successor of the specified Entry, or null if no such.
* 返回指定项的后续项,如果没有,则返回空值。
*/
static <K,V> TreeMap.Entry<K,V> successor(Entry<K,V> t) {
if (t == null)
return null;
else if (t.right != null) {
Entry<K,V> p = t.right;
while (p.left != null)
p = p.left;
return p; // 如果t右结点存在,返回右结点中的最小值的结点p
} else {
Entry<K,V> p = t.parent;
Entry<K,V> ch = t;
while (p != null && ch == p.right) {
ch = p;
p = p.parent;
}
return p; // 如果t右结点不存在,一直找到父结点中最小的结点
}
}
/** From CLR */
private void fixAfterDeletion(Entry<K,V> x) {
while (x != root && colorOf(x) == BLACK) { // 当替换结点x 不为根结点 并且 替换结点为黑色
if (x == leftOf(parentOf(x))) { // 如果x为PL
Entry<K,V> sib = rightOf(parentOf(x)); // x的S
if (colorOf(sib) == RED) { // S为红色 情况3.2
setColor(sib, BLACK); // S变成黑色
setColor(parentOf(x), RED); // P变成红色
rotateLeft(parentOf(x)); // P左旋转
sib = rightOf(parentOf(x)); // S变为PR
}
// 在这里的时候S都是黑色
if (colorOf(leftOf(sib)) == BLACK &&
colorOf(rightOf(sib)) == BLACK) { // 如果S的SL,SR都是黑色
setColor(sib, RED); // S直接变红
x = parentOf(x); // 替换结点x 设为父结点 // 循环后按 情况3.2处理了
} else {
if (colorOf(rightOf(sib)) == BLACK) { // 如果SR是黑色 此时SL为红色或者不存在
setColor(leftOf(sib), BLACK); // SL变成黑色
setColor(sib, RED); // S变成红色
rotateRight(sib); // S右旋转
sib = rightOf(parentOf(x)); // S变为PR 对应了情况3.5
}
setColor(sib, colorOf(parentOf(x))); // S为P的颜色 情况3.6
setColor(parentOf(x), BLACK); // P设置为黑色
setColor(rightOf(sib), BLACK); // SR设置为黑色
rotateLeft(parentOf(x)); // P左旋
x = root; // 替换结点 变成 根结点退出循环
}
} else { // symmetric 对称的 // 如果x为父结点的右节点
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;
}
}
}
setColor(x, BLACK);
}
小结:
红黑树这东西慢慢来理解,反而也没想象中那么困难。概念得懂。
也感谢大神写的文章,让我有个更好理解的方式。
还要继续努力啊~

浙公网安备 33010602011771号