文章目录
学习目标:
- 理解红黑树添加节点后的平衡操作缘由,用4阶B树辅助记忆平衡操作。
学习内容:
- 删除节点的过程
- 删除对象的转换
- 失黑的原因&失黑修正原则
- 失黑修正
一、删除节点的过程
1、判断是否为空树,是则无需删除
2、不是空树,判断要删的节点是否存在,不存在则无需删除
3、不是空树且要删的节点存在,找替代节点进行交换,交换后判断要删的节点的颜色,红色则删除完成,黑色则失黑修正。
4、失黑修正完成。
二、 删除对象的转换
红黑树是一棵二叉搜索树,删除操作可以通过找前驱/后继来替换要删除的节点来保持其作为搜索树的性质。对于红黑树,只需要找删除节点(记为D)的前驱/后继(记为P)来替换要删除的节点即可,且节点P必然对应于4阶B树中的叶节点的某一关键词。原因:通过寻找D的前驱/后继P,如果P有两个孩子,则P可以以既定方向找到下一个节点作为前驱/后继P,所以P最多有1个孩子。而在4阶B树中,“非叶节点至少有两个孩子”,这与P最多有1个孩子矛盾,所以P必然是4阶B树中某个叶节点。
可能是以下情况:
二节点:
三节点:
四节点
三、失黑的原因&失黑修正原则
3.1失黑的原因
回顾红黑树性质5. ”从任一结点到其每个叶子的所有路径都包含相同数目的黑色结点。“,若我们删除了一个黑色节点 ,则从根节点出发到删除的节点所经过的黑节点数比其他路径上经过的黑界点数少1个黑色节点。所以只要发现实际要删除的节点是黑色的,就要进行失黑修正。
3.2 失黑修正原则
原则1:以最少的节点数进行平衡。
原则2:以最少的操作完成平衡为目标。
3.2.1 可以在节点内部平衡的情况
显然只有以下情况可以在节点内部进行平衡:
对于三节点:P是红色,删除P即可;P是黑色则以红色的关键字代替。
对于四节点:P只能是红色,删除P即可。
3.2.2 无法在节点内部平衡的情况
根据原则1我们应以最少的节点数进行平衡,但又些时候,在节点内部已经无法进行平衡,此时需要向更上一级的帮助来进行平衡,下面列出满足原则1并需要父节点帮助的情况:
D(Delete)-要删除的节点,P(Parent)-要删除的节点的父节点,B(Brother)要删除的节点的兄弟节点,侄-D节点的侄子节点
父是二节点:
父是三节点
父是四节点
以上情况如何进行失黑修正将在第四部分进行详细叙述
四、失黑修正
这里的失黑修正主要讨论 “3.2.2 无法在节点内部平衡的情况”的失黑修正。
4.1 情况1:统一要处理的对象:“近墨者黑!我只要黑色的兄弟!”
:情况1:兄弟是红色的。 操作:父旋转1次+兄&父颜色互换+关系转换侄子变兄弟
结果:转换为情况2
观察3.2.2提到的无法在节点内部平衡的情况,主要关注“P-B-D”三者的关系,(你是否有发现)B是黑色这种情况占大多数,具体的:二节点时,B是黑的比例为1;三节点时,B是黑色的比例为1/2;四节点时,B是黑色的比例为1!
那么为了统一进行处理,我们把三节点中,P黑B红全部改为P红B黑!
这在B树中只是颜色的变化,在红黑树中,对应的只是父旋转1次+兄&父颜色互换+关系转换侄子变兄弟
至此,我们成功不改变B树性质和红黑树性质的情况下只对父亲是三节点的情况完成了转换,我们要处理的情况变成了兄弟节点是黑色的。
4.2 情况2:D有红远侄子,平衡后就收工!
情况2:兄弟是黑色的,兄弟有红色远侄。操作:父旋转一次+兄父颜色互换+远侄染黑。平衡结束!!!
满足这种情况的太多了,不全部举出来来了。若此时D有红色远侄,则在4阶B树中,其具体情况为:
转换为红黑树的情况,只关注“D-P-B-侄”四者的关系,可以发现:只需要 父旋转一次+兄父颜色互换+远侄染黑 即可。并且可以大胆地说一句“收工!”。因为已经以最少的节点数,在一个家族内部完成了整体的平衡。
4.3 情况3:D只有红近侄子,变为情况2,黎明前的黑暗!
情况3:D是黑色的,D没有远红侄子,但是有近红侄子。操作: 兄弟旋转1次+兄&近侄颜色互换+关系转换近侄变兄弟->情况2
有没有发现这种情况和情况2很相似?只要B旋转一下就变成情况2了!情况2完成后就收工啦!
4.4 情况4:D只有黑色侄子,生活有喜有忧!
情况3:D是黑色的,D只有黑色侄子。兄&父颜色互换,如果互换后,父是红色的就收工(惊喜)!如果互换后,父是黑色的就以父为D向根迭代(唉)。
如图,父亲是三、四节点时,只需简单的颜色互换即可,其实还有很多修正方案,但是根据原则1、原则2,这样做事最省力的,毕竟只需要染色。比如四节点的情况,如果重组后,P红B黑是可行的,但是对应到红黑树就是旋转+颜色互换。
父亲是二节点并未完成平衡,因为原本的红黑子树黑高度为2,删除并平衡给高度变成了1,因此从根出发走到如图所示的P必然比别的路径少一个黑色节点。所以要向上级请求帮助。
五、失黑修正流程图
为了代码可读性和美观,我封装了一些简单函数,比如判断红黑,左右旋,判断是否是左孩子和颜色互换,直接复制是跑不了的。
template<typename T>
void RBTree<T>::SolveLostBlack(RBNode *DLeaf) {
//Dleaf是要删除的节点,其必然是叶节点,必然黑,(已经在外部排除D是根节点的情况)
cout << "\n Solving lost black...\n";
/********************预处理:*******************/
RBNode* Parent = DLeaf->Par;
RBNode* Bro = BrotherNode(DLeaf);//当D是黑节点时,兄弟节点必然存在,兄弟节点可红可黑
RBNode* VirtualDLeaf = Bro;//虚拟删除节点,迭代修正时使用,不会实际删除节点
//RBNodeColor DColor = DLeaf->NodeColor ? Black : Red;//删除节点的颜色
bool DIsLeft = IsLeftChild(DLeaf);
DIsLeft ? Parent->LeftChild=nullptr : Parent->RightChild = nullptr;
/*理清关系,理清楚后就把Dleaf删除*/
delete DLeaf;
/********************开始失黑修正:*******************/
/*D是黑的*/
do {
/*____________兄弟是红色节点___________*/
if (IsRed(Bro)) {//情况2
if (DIsLeft) {//DLeaf是左孩子
Parent->Zig(Parent->RightChild);
ColorSwap(Parent, Bro);
if (!Bro->Par) _root = Bro;
//情况2旋转后兄父关系需要变更
Bro = Parent->RightChild;
//Par不变
}
else {//DLeaf是右孩子
Parent->Zag(Parent->LeftChild);
ColorSwap(Parent, Bro);
if (!Bro->Par) _root = Bro;
//旋转后兄父关系需要变更
Bro = Parent->LeftChild;//这个节点必然是黑色的,因为原本的Bro自身是红色,故Bro的子不可能红
}
//由于旋转操作,P下沉,B上升,如果B成了新的根节点,要及时更新
//即if (!Bro->Par) _root = Bro;
}
/*____________兄弟是黑色节点___________*/
if (IsBlack(Bro)) {//error:Bro是null?
/*Bro是必然存在,但D被删了,可以通过Bro判断D*/
DIsLeft = IsLeftChild(Bro) ? false: true;
RBNode* FarNephew = DIsLeft ? Bro->RightChild : Bro->LeftChild;//远侄子
RBNode* CloseNephew = DIsLeft ? Bro->LeftChild : Bro->RightChild;
bool FarNepIsRed = FarNephew && IsRed(FarNephew) ? true : false;
bool ClsNepIsRed = CloseNephew && IsRed(CloseNephew) ? true : false;
if (FarNepIsRed) {
//情况3:B黑+D黑+D有远侄子且是红色的
if (DIsLeft) Parent->Zig(Parent->RightChild);//DLeaf是左孩子,父左旋
else Parent->Zag(Parent->LeftChild);//DLeaf是右孩子,父右旋
if (!Bro->Par) _root = Bro;
ColorSwap(Parent, Bro);
FarNephew->NodeColor = Black;//远侄染黑
return;//平衡完成
/*修正结束:大胆return*/
}
else if (ClsNepIsRed) {
if (DIsLeft) Bro->Zag(Bro->LeftChild);//D是左孩子,兄右旋
else Bro->Zig(Bro->RightChild);//D是右孩子,兄左旋
if (!Bro->Par) _root = Bro;
ColorSwap(Bro, CloseNephew);
Bro = CloseNephew;
//转换成了B黑+D黑+D有远侄子的情况
continue;
}
else {
Bro->NodeColor = Red;
if (IsRed(Parent)) {
Parent->NodeColor = Black;
return;
}
VirtualDLeaf = Parent;
DIsLeft = IsLeftChild(VirtualDLeaf);
Bro = BrotherNode(VirtualDLeaf);
Parent = VirtualDLeaf->Par;
}
}
} while (VirtualDLeaf!=_root);
return;
}