红黑树笔记
前言
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); }
小结:
红黑树这东西慢慢来理解,反而也没想象中那么困难。概念得懂。
也感谢大神写的文章,让我有个更好理解的方式。
还要继续努力啊~