在红黑树里面最复杂的操作就是对红黑树里面的节点进行删除了。不仅要进行二叉搜索树的删除操作,还需要进行对红黑树性质的保持。
我们先定义删除操作的函数原型:
void RBTree<Key, Value>::erase(Key key)
函数接受一个Key类型的值,树会搜索含有这个值的节点,并且删除这个节点,如果找不到,不会对树进行任何的操作。
删除操作也需要一些辅助函数:
void rotateLeft(Node<Key, Value>* node);
void rotateRight(Node<Key, Value>* node);
Node<Key, Value>* findNext(Node<Key, Value>* node);
Node<Key, Value>* findMin(Node<Key, Value>* node);
void dealOneChild(Node<Key, Value>* node, Node<Key, Value>* child);
void adjustDelete(Node<Key, Value>* node, Node<Key, Value>* realParent);
上面的函数中我们需要关注的函数只有adjustDelete这个函数,这个是用来在删除一个节点之后,用来调整红黑树的。
其他的操作依次为:①左旋 ②右旋 ③找到node节点中序的下一个节点 ④找到以node为根的树的最小节点 ⑤交换两个节点的位置(指针操作,并不是交换节点内部的成员)
我们可以想象:删除一个节点会出现四种情况:删除的节点(del)替代的节点(rep)
① del是红色的,rep是红色的。
② del是红色的,rep是黑色的。
③ del是黑色的,rep是红色的。
④ del是黑色的,rep是黑色的。
当rep是红色的时候,无论del是红是黑,只要简单地把rep的颜色改成黑色就可以保持所有的红黑树性质。
所以我们只需要对rep是黑色的那两种情况进行讨论即可。
在二叉搜索树中,我们删除一个节点会分成三种情况:①被删除节点的左子女为空 ②被删除节点的右子女为空 ③被删除的节点左右子女都不为空。
同样的,我们在红黑树的删除中,也需要考虑这三种情况,并且要对这三种情况进行相应的处理(因为涉及到一些指针的保存)
删除操作跟插入操作不一样,不是对于父节点的兄弟节点进行判断,而是对于rep的兄弟节点来进行判断,因为rep是黑色的,所以在rep到它祖先节点的所有节点上,第五条规则都会被破坏掉,我们需要对rep的兄弟节点的颜色来进行不同的处理。(这些处理都是对于第五条规则的维护)
注意:这里的rep是已经把del替换之后的rep,而不是替换之前的rep
同样的,我们也需要对bro的左右进行判断,并且左右也是对称的,跟插入一样。
①当rep的兄弟节点 (bro)为红色的时候:
我们可以通过把bro的颜色变黑,把rep和bro的父节点变红,进行一次旋转。
②当rep的兄弟节点为黑色的时候:
在这种情况中,我们还需要对bro的子女的颜色进行判断:双黑,左边红色,右边红色。
(1)双黑:因为rep那边已经少了一个黑色节点,我们需要把bro变成红色,达到平衡效果
(2)左边红色:我们需要把bro的左子女变成黑色,bro变成红色,进行一次旋转变成(3)这种情况
(3)右边红色:bro变成红色,rep的父节点变成黑色,进行一次旋转。
经过最多三次变化,我们就可以维持住红黑性质。
下面是主要的代码(adjustDelete,erase)
erase:
template <class Key, class Value>
void RBTree<Key, Value>::erase(Key key) {
Node<Key, Value>* target = find(key);
if (target == NULL) {
cout << "Can not delete node not exist" << endl;
return ;
}
int originalColor = target->color;
//next节点是用来替换被删除的节点的,replaceNode是用来替换next节点的
//我们还需要一个额外的节点来保存replaceNode的父节点,因为replaceNode的值可能是NULL
//额外的节点也要当做参数传到adjustDelete函数里面
Node<Key, Value>* next = target;
Node<Key, Value>* replaceNode = NULL;
Node<Key, Value>* replaceParent = NULL;
// 先处理要删除的节点只有一个子女的情况
if (target->left == NULL) {
replaceNode = target->right;
dealOneChild(target, replaceNode);
replaceParent = target->parent;
} else if (target->right == NULL) {
replaceNode = target->left;
dealOneChild(target, replaceNode);
replaceParent = target->parent;
} else { //这里开始处理删除节点有两个子女的情况
next = findNext(target);
originalColor = next->color;
replaceNode = next->right; //只能是右子女,左子女的话,那么就不是最小的节点了
//要处理next的右子树的根不为后继的情况,这里主要是处理后继节点替换
//如果右子树的根为后继的话,那么直接就开始替换被删除的节点了
if (next->parent != target) {
dealOneChild(next, next->right);
next->right = target->right;
target->right->parent = next;
replaceParent = next->parent; //注意这个赋值
} else {
replaceParent = next;
}
dealOneChild(target, next);
next->left = target->left;
target->left->parent = next;
next->color = target->color;
}
if (originalColor == BLACK) {
adjustDelete(replaceNode, replaceParent);
}
delete target;
target = NULL;
}
adjustDelete:
template <class Key, class Value>
void RBTree<Key, Value>::adjustDelete(Node<Key, Value>* node, Node<Key, Value>* nodeParent) {
while (node != root && (node == NULL || node->color == BLACK)) {
if (node == nodeParent->left) {
Node<Key, Value>* bro = nodeParent->right;
if (bro->color == RED) { //node的兄弟节点为红色,情况1
bro->color = BLACK;
bro->parent->color = RED;
rotateLeft(bro->parent);
bro = nodeParent->right;
}
//node兄弟节点为黑色
if ((bro->left == NULL || bro->left->color == BLACK)
&& (bro->right == NULL || bro->right->color == BLACK)) { //兄弟节点的两个子节点都为黑色,情况2
bro->color = RED;
node = nodeParent;
nodeParent = nodeParent->parent;
} else { //兄弟节点其中一个节点为红色
if (bro->right == NULL || bro->right->color == BLACK) { //兄弟节点的左子女为红色,情况3
if (bro->left != NULL) {
bro->left->color = BLACK;
}
bro->color = RED;
rotateRight(bro);
bro = nodeParent->right;
}
// 兄弟节点的右子女为红色,情况4
bro->color = RED;
nodeParent->color = BLACK;
if (bro->right != NULL) {
bro->right->color = BLACK;
}
rotateLeft(nodeParent);
node = root;
}
} else {
Node<Key, Value>* bro = nodeParent->left;
if (bro->color == RED) {
bro->color = BLACK;
bro->parent->color = RED;
rotateRight(bro->parent);
bro = nodeParent->left;
}
if ((bro->left == NULL || bro->left->color == BLACK)
&& (bro->right == NULL || bro->right->color == BLACK)) {
bro->color = RED;
node = nodeParent;
nodeParent = nodeParent->parent;
} else {
if (bro->left == NULL || bro->left->color == BLACK) {
if (bro->right != NULL) {
bro->right->color = BLACK;
}
bro->color = RED;
rotateLeft(bro);
bro = nodeParent->left;
}
bro->color = RED;
nodeParent->color = BLACK;
if (bro->left != NULL) {
bro->left->color = BLACK;
}
rotateRight(nodeParent);
node = root;
}
}
}
if (node != NULL) {
node->color = BLACK;
}
}
上面的代码中,需要注意的是replaceParent这个指针变量,因为我们在寻找到next之后,next->right这个值可能是null,所以不能简单地通过next->right->parent这个值来找到真正的next的父节点,所以对不同的情况我们需要分开赋值(在替换节点的时候)