红黑树(3): 删除
前言
本文将介绍红黑树两大操作之一:删除
删除
首先考察红黑树性质:
(1)根节点是黑色。
(2)每个外部节点(NULL)是黑色。
(3)如果一个节点是红色的,则它的子节点必须是黑色的。
(4)从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点。
首先,根据BST常规删除,我们在删除红黑树一个节点时,只有三种情况
1)两个子树不为空
2)有且仅有一颗非空子树
3)没有任何子树
而1)在BST删除中最终被转化为2)或3)情况
具体来讲就是:x有两颗非空子树时
取其左子树最大值或右子树最小值m
用m的值来替换x的值
转而删除m
所以在此我们从2),3)来考虑红黑树的删除情况
在此,我们不妨先定义一些变量来表示某些节点:
x:待删除节点
r:接替x子树的节点
p:x的父节点
s:x的兄弟节点
t:s的一个可用的红色孩子节点
情况1:x , r不全是黑
略去对称和相似情况
如上图,该情况很容易理解,在删除x后只需要将r染黑即可保持局部子树黑高度不变。调整完毕
情况2:x,r全为黑
略去对称和相似情况
该情况下,删除x一定会违反性质(4)。
该情况我们称为双黑缺陷
根据情况的不同我们将分为四种情况讨论
BB - 1 —— t 不为空
略去对称和相似情况
我们对其进行提升变换
可知该问题对应一次B树的下溢
此时根据B树的解决办法:下溢节点 x 向其父亲借得一个关键码,父亲再向其另一个儿子借得一个关键码即可修复
根据B树还原红黑树可见:
该次旋转之后,局部子树得高度恢复,修复是彻底的
具体步骤:
1)右旋 p
2)s变成p的颜色,染黑p ,染黑 t
解决该问题的关键是 x 的兄弟 s 足够富有(红孩子), 使得 p 还存在旋转的余地
那么如果 t 并不存在,也就是说 s 没有任何红孩子,无法通过旋转来调整高度,我们该如何调整呢?
BB - 2 —— s 为黑 , t 不存在
该情况可分为两种
1) p 为 红 ,我们将其称为 BB - 2R
2) p 为 黑 , BB - 2B
BB - 2R
当 p 为红时
略去对称和相似情况
通过提升变换我们得到下面对应的B树
此时B树通过合并操作来修复
1)从父节点中取出一个关键码
2)以其为粘合剂将左右节点合并
通过还原到红黑树
我们得到一种可行的修复方式: 染红 s , 染黑 p
但是上层节点损失了一个关键码 p,这种修复方式是否能够彻底修复红黑树的缺陷吗,会不会导致上层再次出现下溢?
好消息是“并不会”,原因在于此时上层至少还存在一个黑色关键码,在p借出去之后不至发生下溢。可见该修复一步到位。
那么如果 p 是黑色呢?
BB - 2B
略去对称和相似情况
当p为黑时,我们仍然可以先采用BB - 2R的修复,局部子树恢复高度。
不过此时上层节点必定下溢,因此我们要对 p 再进行一次双黑缺陷修正即可
但注意:p 的修复可能并不是一步到位的,每次修复都可能导致下溢上升一层,最多修复至根,此时局部子树变为整颗红黑树,黑高度随即 -1,传播次数至多log(n)次
至此,s为黑色的情况到此结束
下面我们将来讨论 s为红色的情况 BB - 3
BB - 3
略去对称和相似情况
在B树中,我们令 s,p 互换颜色
但这并不是无用功,请注意,通过该次旋转,我们将其转化为了前面的几种情况,可能是 BB - 1,或 BB - 2R,但绝不会是 BB - 2B。
变换回红黑树,不难发现
该次操作只需要将p右旋,染红p,染黑s即可。
至此,红黑树的删除操作已经讲完,下面放出相关代码
C++代码
双黑缺陷修正
// RedBlackTree - 解决双黑缺陷
void _fix_before_remove(_Mytn *it) {
_Mytn *p, *s, *t ,*r;
r = it->lc ? it->lc : it->rc;
if ( it->color == _Tree_Node::red || (r && r->color == _Tree_Node::red) ) {
if (r) r->color = _Tree_Node::black;
return;
}
while (it) {
p = it->parent;
if (!p) { // it 是根节点
return;
}
// p不为空
s = it == p->lc ? p->rc : p->lc; // s必定不为空
t = nullptr;
// s 为黑
if (s->color == _Tree_Node::black) {
t = (s->lc && s->lc->color == _Tree_Node::red) ? s->lc : ((s->rc && s->rc->color == _Tree_Node::red) ? s->rc : nullptr);
if (t) { // BB - 1 有红色侄子
if (s == p->lc) {
if (t == s->rc) {
this->_left_rotate_at(s);
std::swap(s, t);
}
this->_right_rotate_at(p);
}
else {
// 尝试判断 s 的右孩子是不是红节点
t = (s->rc && s->rc->color == _Tree_Node::red) ? s->rc : t;
if (t == s->lc) {
this->_right_rotate_at(s);
std::swap(s, t);
}
this->_left_rotate_at(p);
}
// 染黑 t s设为p的颜色 染黑 p
t->color = _Tree_Node::black;
s->color = p->color;
p->color = _Tree_Node::black;
return;
}
else {
// BB - 2R
// 染黑 g 染红 s
s->color = _Tree_Node::red;
if (p->color == _Tree_Node::red) {
p->color = _Tree_Node::black;
return;
}
// BB - 2B
it = p;
continue;
}
}
else {
// s 为红
// BB - 3
if (s == p->lc) {
this->_right_rotate_at(p);
}
else {
this->_left_rotate_at(p);
}
s->color = _Tree_Node::black;
p->color = _Tree_Node::red;
continue;
}
}
}
其他函数
// 移除元素
_Myit erase(_Elem v) {
_Mytn *it = this->_root->find(v);
if (!it) return _Myit(nullptr);
// 经过重定向的节点最多只有一颗非空子树
it = this->_remove_redirect(it);
this->_fix_before_remove(it); // 试图修复删除后任何可能出现的缺陷
this->_remove_at(it);
return this->upper_bound(v); // 返回v的后继迭代器
}
// 重定向
_Mytn* _remove_redirect(_Mytn* it) {
_Mytn *p, *l, *r, *m;
l = it->lc;
r = it->rc;
if (l && r) {
p = it->parent;
if (p && p->lc == it) {
m = l->max();
}
else {
m = r->min();
}
it->value = std::move(m->value);
// 重定向要删除的元素
it = m;
}
return it;
}
// 删除
_Mytn* _remove_at(_Mytn* it, _Mytn** _hot = nullptr ) {
// 重构,使用迭代方式
_Mytn *x = it,*p , *l , *r,*m = nullptr ;
while (x) {
// 重定向
p = x->parent;
l = x->lc;
r = x->rc;
m = nullptr;
// 左右子树均不空
if (l && r) {
it = _remove_redirect(it);
continue;
}
if (l) {
if (p) {
if (p->lc == x) {
p->lc = l;
}
else {
p->rc = l;
}
}
else {
this->_root = l;
}
l->parent = p;
m = x->lc;
x = nullptr;
}
else {
if (p) {
if (p->lc == x) {
p->lc = r;
}
else {
p->rc = r;
}
}
else {
this->_root = r;
}
if (r) {
r->parent = p;
m = x->rc;
}
m = x->parent;
x = nullptr;
}
}
delete it;
--this->_size;
if (_hot && m) {
*_hot = m->parent;
}
// 将删除后的孩子节点返回
return m;
}