本文内容搬运了一篇博客以及java TreeMap实现的源码,还有一些自己的体会。本以为红黑树的插入操作就挺难实现了,结果看到了红黑树的删除操作,发现自己还是太年轻了,首先看一下红黑树的性质:
- 每个结点要么是红的要么是黑的。
- 根结点是黑的。
- 每个叶结点(叶结点即指树尾端NIL指针或NULL结点)都是黑的。
- 如果一个结点是红的,那么它的两个儿子都是黑的。
- 对于任意结点而言,其到叶结点树尾端NIL指针的每条路径都包含相同数目的黑结点。
1、若插入结点的父亲结点p与p的兄弟结点都为红色,我们可以将p与p的兄弟结点都置为黑色,将p的父亲结点置为红色,这样维护了性质4。
2、若插入结点的父亲结点p为红色(p的兄弟结点不为红),且插入结点为p的右孩子,那我们将p左旋(关于左旋和右旋的介绍很多,也易于理解,这里就不详细解释了),变成情况3.
3、若插入结点的父亲结点p为红色(p的兄弟结点不为红),且插入结点为p的左孩子,将p置黑,将p的父亲结点置红(p的父亲结点必为黑否则不成立),将p的父亲结点右旋。
以上规则可以自己画图实验一下,非常直观,代码如下:
private void fixAfterInsertion(Entry<K,V> x) { x.color = RED; // 直到 x 节点的父节点不是根,且 x 的父节点不是红色 while (x != null && x != root && x.parent.color == RED) { // 如果 x 的父节点是其父节点的左子节点 if (parentOf(x) == leftOf(parentOf(parentOf(x)))) { // 获取 x 的父节点的兄弟节点 Entry<K,V> y = rightOf(parentOf(parentOf(x))); // 如果 x 的父节点的兄弟节点是红色 if (colorOf(y) == RED) { // 将 x 的父节点设为黑色 setColor(parentOf(x), BLACK); // 将 x 的父节点的兄弟节点设为黑色 setColor(y, BLACK); // 将 x 的父节点的父节点设为红色 setColor(parentOf(parentOf(x)), RED); x = parentOf(parentOf(x)); } // 如果 x 的父节点的兄弟节点是黑色 else { // 如果 x 是其父节点的右子节点 if (x == rightOf(parentOf(x))) { // 将 x 的父节点设为 x x = parentOf(x); rotateLeft(x); } // 把 x 的父节点设为黑色 setColor(parentOf(x), BLACK); // 把 x 的父节点的父节点设为红色 setColor(parentOf(parentOf(x)), RED); rotateRight(parentOf(parentOf(x))); } } // 如果 x 的父节点是其父节点的右子节点 else { // 获取 x 的父节点的兄弟节点 Entry<K,V> y = leftOf(parentOf(parentOf(x))); // 如果 x 的父节点的兄弟节点是红色 if (colorOf(y) == RED) { // 将 x 的父节点设为黑色。 setColor(parentOf(x), BLACK); // 将 x 的父节点的兄弟节点设为黑色 setColor(y, BLACK); // 将 x 的父节点的父节点设为红色 setColor(parentOf(parentOf(x)), RED); // 将 x 设为 x 的父节点的节点 x = parentOf(parentOf(x)); } // 如果 x 的父节点的兄弟节点是黑色 else { // 如果 x 是其父节点的左子节点 if (x == leftOf(parentOf(x))) { // 将 x 的父节点设为 x x = parentOf(x); rotateRight(x); } // 把 x 的父节点设为黑色 setColor(parentOf(x), BLACK); // 把 x 的父节点的父节点设为红色 setColor(parentOf(parentOf(x)), RED); rotateLeft(parentOf(parentOf(x))); } } } // 将根节点设为黑色 root.color = BLACK; }红黑树的删除操作相对要复杂许多,首先删除结点的过程与BST是一样的,分别考虑删除结点有几个孩子,若是2个需要找到后继结点代替该结点,若是一个或没有就比较简单了。执行完删除后最重要的是维持树的完美平衡,若是删除红结点不存在这种情况,若删除的是黑结点肯定会违背性质5,因此又要进行旋转和调色,这个过程非常复杂,我这里搬运一下我认为讲的比较清楚的一篇博客:http://blog.csdn.net/spch2008/article/details/9338923
第一:先看最简单情况,即删除红色节点。删除红色节点,不影响红黑树平衡性质,如图:
只需要删除红色节点,不需要进行调整,因为不影响红黑树的性质。 黑色节点没有增多也没有减少。
注意:以下几种单支情况在平衡的红黑树中不可能出现。
因为上述的情况,红黑树处于不平衡状态。(破坏到null,黑色节点数目相同)
所以,平衡状态下红黑树要么单支黑-红,要么有两个子节点。
第二:删除单支黑节点
此种情况被包含在“第三”中,详见“第三”分析
第三:若删除节点有左右两个儿子,即左右子树,需要按照二叉搜索树的删除规律,从右子树中找最小的替换删除节点(该节点至多有一个右子树,
无左子树),我们将该节点记为y, 将删除节点记为z,将y的右儿子记为x(可能为空)。
删除规则:用y替换z,交换y与z颜色,同时y = z,改变y的指向,让y指向最终删除节点。
为了便于理解,可以先这样假设:将y与z的数据交换,但颜色不交换,这样,实际相当于将删除转移到了y节点,而z处保持原先状态(处于平衡)。
此时可以完全不用了理会z节点,直接删除y节点即可。因为y最多只有一个右子树,无左子树,这便转移到了“第二”。
对于删除y节点,有几种考虑。
1. 若y为红色,则这种情况如上述”第一“所述,并不影响平衡性。(null视为黑色)
2. 若y为黑色,则删除y后,x替换了y的位置,这样x子树相对于兄弟节点w为根的子树少了一个黑节点,影响平衡,需要进行调整。
剩下的调整工作就是将x子树中找一合适红色节点,将其置黑,使得x子树与w子树达到平衡。
若x为红色,直接将x置为黑色,即可达到平衡;
若x为黑色,则分下列几种情况。
情况1:x的兄弟w为红色,则w的儿子必然全黑,w父亲p也为黑。
改变p与w的颜色,同时对p做一次左旋,这样就将情况1转变为情况2,3,4的一种。
情况2:x的兄弟w为黑色,x与w的父亲颜色可红可黑。
因为x子树相对于其兄弟w子树少一个黑色节点,可以将w置为红色,这样,x子树与w子树黑色节点一致,保持了平衡。
new x为x与w的父亲。new x相对于它的兄弟节点new w少一个黑色节点。如果new x为红色,则将new x置为黑,则整棵树平衡。否则,
情况2转换为情况1,3,4 情况2转变为情况1,2,3,4.
情况3:w为黑色,w左孩子红色,右孩子黑色。
交换w与左孩子的颜色,对w进行右旋。转换为情况4
情况4:w为黑色,右孩子为红色。
交换w与父亲p颜色,同时对p做左旋。这样左边缺失的黑色就补回来了,同时,将w的右儿子置黑,这样左右都达到平衡。
可以看到,操作主要是在四种情况下的转换,情况2可能达到平衡,情况4一定会达到平衡,以上这四种情况是x为其父亲结点的左子结点,我觉得如果能理解的话右子结点肯定也没问题了,代码如下:// 删除节点后修复红黑树 private void fixAfterDeletion(Entry<K,V> x) { // 直到 x 不是根节点,且 x 的颜色是黑色 while (x != root && colorOf(x) == BLACK) { // 如果 x 是其父节点的左子节点 if (x == leftOf(parentOf(x))) { // 获取 x 节点的兄弟节点 Entry<K,V> sib = rightOf(parentOf(x)); // 如果 sib 节点是红色 if (colorOf(sib) == RED) { // 将 sib 节点设为黑色 setColor(sib, BLACK); // 将 x 的父节点设为红色 setColor(parentOf(x), RED); rotateLeft(parentOf(x)); // 再次将 sib 设为 x 的父节点的右子节点 sib = rightOf(parentOf(x)); } // 如果 sib 的两个子节点都是黑色 if (colorOf(leftOf(sib)) == BLACK && colorOf(rightOf(sib)) == BLACK) { // 将 sib 设为红色 setColor(sib, RED); // 让 x 等于 x 的父节点 x = parentOf(x); } else { // 如果 sib 的只有右子节点是黑色 if (colorOf(rightOf(sib)) == BLACK) { // 将 sib 的左子节点也设为黑色 setColor(leftOf(sib), BLACK); // 将 sib 设为红色 setColor(sib, RED); rotateRight(sib); sib = rightOf(parentOf(x)); } // 设置 sib 的颜色与 x 的父节点的颜色相同 setColor(sib, colorOf(parentOf(x))); // 将 x 的父节点设为黑色 setColor(parentOf(x), BLACK); // 将 sib 的右子节点设为黑色 setColor(rightOf(sib), BLACK); rotateLeft(parentOf(x)); x = root; } } // 如果 x 是其父节点的右子节点 else { // 获取 x 节点的兄弟节点 Entry<K,V> sib = leftOf(parentOf(x)); // 如果 sib 的颜色是红色 if (colorOf(sib) == RED) { // 将 sib 的颜色设为黑色 setColor(sib, BLACK); // 将 sib 的父节点设为红色 setColor(parentOf(x), RED); rotateRight(parentOf(x)); sib = leftOf(parentOf(x)); } // 如果 sib 的两个子节点都是黑色 if (colorOf(rightOf(sib)) == BLACK && colorOf(leftOf(sib)) == BLACK) { // 将 sib 设为红色 setColor(sib, RED); // 让 x 等于 x 的父节点 x = parentOf(x); } else { // 如果 sib 只有左子节点是黑色 if (colorOf(leftOf(sib)) == BLACK) { // 将 sib 的右子节点也设为黑色 setColor(rightOf(sib), BLACK); // 将 sib 设为红色 setColor(sib, RED); rotateLeft(sib); sib = leftOf(parentOf(x)); } // 将 sib 的颜色设为与 x 的父节点颜色相同 setColor(sib, colorOf(parentOf(x))); // 将 x 的父节点设为黑色 setColor(parentOf(x), BLACK); // 将 sib 的左子节点设为黑色 setColor(leftOf(sib), BLACK); rotateRight(parentOf(x)); x = root; } } } setColor(x, BLACK); }想要一次理解红黑树的删除操作是很困难的,本人目前也是属于一知半解,只能循序渐进吧。