网上一直都没有关于删除算法的好文章和实现,在这里记下来,方便日后使用。
删除操作总是在只有一边有孩子的节点或者叶子节点上进行的,绝不会在一个有二个孩子的节点上进行删除操作。而successor函数只有在节点有2个孩子的时候被调用,这个时候,该函数一定是沿节点的右子树向下进行的,最终会找到一个只有一个孩子的节点。
会破坏那些平衡条件
如果删除一个红色的节点,不用做任何操作,红黑树的任何属性都不会被破坏。当删除一个黑色节点的时候,会出现3个问题:
1. 如果删除了根且一个红色的节点做了新的根,则属性1就会被破坏;
2. 该节点的所有祖辈的black height都会少1;
3. 如果该节点的父亲和孩子都是红色的,那么删除这个节点还可能引起连红的情况;
如何恢复平衡
恢复平衡是通过一个函数rb_delete_fixup完成的,以下是完整的算法:
template <class type>
void rb_tree<type>::rb_delete_fixup(node<type> *x) {
node<type> *w = NULL;
while ((x != root) && (x->cr == BLACK)) {
if (x == x->parent->left) {
w = x->parent->right;
if (w->cr == RED) {
// Case 1: w is RED. We change the color of
// x's parent and w, then perform a left rotate on
// parent of x, then we can enter 2, 3 or 4.
w->cr = BLACK;
x->parent->cr = RED;
left_rotate(x->parent);
w = x->parent->right; // Adjust the w to the new sibling of x
}
if ((w->left->cr == BLACK) && (w->right->cr == BLACK)) {
// Case 2: w is BLACK and both left and right
// children of w is black
w->cr = RED;
x = x->parent; // Shift x up a layer and re-enter the loop
}
else if (w->right->cr == BLACK) {
// Case 3: w is BLACK and the left child of w is RED, the
// right child of w is BLACK
w->cr = RED;
w->left->cr = BLACK;
right_rotate(w);
w = w->parent;
}
else {
// Case 4: w is BLACK and the right child of w is RED.
// After some color change and a left rotation, the tree
// is balanced again.
w->cr = w->parent->cr;
x->parent->cr = BLACK;
w->right->cr = BLACK;
left_rotate(x->parent);
x = root;
}
}
else { // x is right child of its parent
// Omit for simplicity
} // End if (x == x->parent->left)
}
x->cr = BLACK;
}
整个算法涉及以下几个节点:我们管被删除的节点叫做z,z的successor叫做y(当z只有一个孩子的时候,z和y是相同的,z和y没有在图中表示),y的唯一孩子叫做x,x的父亲叫做p[x],x的兄弟叫做w,如图所示:
整个算法的核心思想:所有的旋转会保持从图中的树根到所有的子节点α、β、γ、δ、ε和ζ的black height相同。那么我们假设x有2层黑色,最后通过颜色的变换和旋转,去掉x的这层额外的黑色,使得红黑树重新平衡。
例如:由于我们假设x有2层黑色,从A到α和β的black height等于3,执行变换后,从从A到α和β的black height仍然等于3。
如果根被删除,则肯定是一个新的红色节点充当了新的根,那么rb_delete_fixup会在最后把新根变成黑色,红黑树重新恢复平衡;
如果出现2个红色相连情况,则直接把x染成黑色,红黑树直接恢复平衡;
只有在x不为根,且x的颜色为黑色的时候,才需要通过while循环来恢复红黑树的black height平衡,以及解决连红的问题。从整体上来,算法按照x是p[x]的左/右孩子来分类,每一种分类有4种情况。由于这2种分类是对称的,这里就说一下x是p[x]左孩子的情况。在这种情况下,我们用w的颜色来区分4中情况:
· Case 1:w的颜色是红色的;
把p[x]和w的颜色互换,之后对p[x]左旋。这样D节点就变成了x的新兄弟,由于原来w是红色的,D一定是黑色的,从而进入Case 2、3或4。
· Case 2:w是黑色的,且w的两个孩子也是黑色的(图中的灰色节点为颜色无所谓,下同);
这种情况下,我们把x和w都去掉一层黑色,x变成单黑,w变成红色,这样如果p[x]是黑色,则红黑树重新平衡,如果p[x]是红色,会出现连红的问题,则把x上移一层,重新循环。
· Case 3:w是黑色的,且w的左孩子是红色的,w的右孩子是黑色的;
我们把w和w的左孩子颜色互换,之后对w右旋,从而进入Case 4。
· Case 4:w是黑色的,且w的右孩子是红色的(左孩子是红或黑无所谓);
p[x]和w的颜色互换,w的右孩子染成黑色,之后对p[x]左旋,由于在从根到α、β的路径上多了一个黑节点A,则不再需要x额外的一层黑色,此时红黑树就重新恢复平衡了。
至于x是p[x]的右孩子,情况是完全对称的,这里就不多述了