C++ 手撕红黑树(二):删除操作
C++ 手撕红黑树(二):删除操作
前情概述:C++ 手撕红黑树(一):节点的插入
1.红黑树的删除概述
在我们前面的文章中,归纳了红黑树的删除一个节点的情况,总结起来就是:删除看叔叔节点。在红黑树的插入过程中,也有这样的规律,那就是:删除看兄弟。我们只需要判断兄弟节点的颜色,就可以区分各种情况!
2.红黑树的删除
在进行情况分类之前,我们首先考虑最简单的情况:什么情况下我们删除一个节点,无需任何操作。根据前面已经提到的红黑树性质,每条路径的黑色节点数在删除前后应该是保持一致。所以,我们先出来一个结论:当删除的节点是红色的时候时,才不需要任何后续的操作。因此,这是一种简单的情况。下面我们开始讨论删除的节点是黑色的情况。
2.1 删除的节点是黑色
当需要删除的节点颜色是黑色时,最基本的情况如下所示:
其中,白色的节点表示不确定颜色。那么,既然不确定颜色,我们就来给它上色,然后分类讨论。
首先,我们在左边的子树删除了一个节点1,那么以1为根的接下来的路径黑色的数量都会少1。既然少了1,那我们就补回来。从哪儿补?很明显,兄弟的节点值都要大于1,肯定不是以兄弟(值为8的节点)为根的子树拿一个节点出来补,那就拿父节点来补。拿父节点来补,就是旋转。所以,在下面的分类讨论中,都涉及到了旋转。
首先,删除看兄弟,我们按兄弟的颜色进行区分,可以分为以下的四种情况:
情况1,兄弟是黑色的,而且有一个红色的右孩子:
如果我们要把删除的1的所在路径补上一个黑色,那就只能把父亲5左旋下来。然后为了保持这个树的局部性质不变,我们交换待删除节点的原兄弟和原父亲的颜色,并把兄弟的右孩子置为黑色(图中白色为未知颜色):
这样,第一种情况就完成了,而且不必再继续调整。
情况2,兄弟是黑色的,但是右孩子是黑色的,而左孩子是红色的:
在这种情况下,我们尽可能想向情况1靠拢,怎么办呢?右旋兄弟就行了:
这个时候已经跟1很像了,我们再交换6和8的颜色就一样了:
可以验证,从5到1、6、9的黑色节点个数在上面的操作之后没有任何变化。后续再按情况1进行调整即可
在上面的两种情况中,兄弟的孩子都有一个红色的,如果没有怎么办?因此,有了情况三:
情况三:兄弟的孩子都是黑色的
在之前,我们都是借兄弟黑色节点(兄弟有一个红色孩子,通过涂黑来补兄弟的位置)。现在兄弟也没了,那怎么办?没辙了,兄弟和我(待删除的黑色)那就一起减一吧,所以我们把兄弟变成了红色:
但是这样做是危险的,如果父节点5是红色怎么办?没得办,我们只能迭代检查了。如果父节点5原本是红色,那就太好啦,我们把5涂成黑色,那我跟兄弟减的1不是又加回来了?如果不是红色,我们继续向上检查,直到遇见一个节点是红色的,把它涂成黑色,就完成了我们所有的操作。那没有红色怎么办,其实在回溯的过程中,没有红色,就落在了我们正在讨论的情况之一(包括下面要介绍的情况4)。至此,我们的情况3已经讨论完毕了。
在上面的情况中,我们都是讨论了兄弟是黑色的情况,如果兄弟是红色的怎么办?注意,如果兄弟是红色,那父节点就一定是黑色的,所以局部情况长这个样子:
很明显,当兄弟是红色,只有这一种情况。要把这个节点补回来,我们还是选择向兄弟借!现在兄弟是红色的,我们又有机可乘了。先尝试把父节点5旋下来补这个黑色:
我们可以分析一下,在旋转之前,5到9有两个黑色,现在8到9只剩下一个了。那怎么办?先把8涂黑!:
再检查一下,发现原来5到6只有两个黑色,现在变成了三个!那怎么办?总不能把8涂回来吧?只能涂5了,把5涂成红色:
现在检查一下8到1、6、9和2到1、6、9的黑色节点有没有一样。结果确实是一样的。再看上面的情况,待删除节点的兄弟变成6了,6是什么颜色?黑色!那就是说,情况四经过我们的处理,又变成了上面三种情况的任意一种!
总结一下:
在上面中,我们根据兄弟的颜色,分了四种情况,其中三种是兄弟是黑色,一种是兄弟是红色。情况4经过处理,可以变成情况1~3的一种;情况3处理之后,又变成1、2、4的一种;情况2经过处理会变成情况1。
因此在处理的过程中,我们可以先看看是不是4,是的话处理一下;再看看是不3,是的话处理一下;再看看是不是2,是的话处理一下;最后看是不是1,是的话处理一下。
注意:由于情况3需要一直迭代处理,当遇见红色节点就可以退出了;而4是1~3的其中一种,所以我们需要一个循环,来迭代处理。而且,我们需要把1、2、3、4四种情况都写在这个循环内,即便是只需要在局部的树中处理就完成的情况1、2也是如此。为什么?因此情况4可能会变成1、2啊。难不成我们要1、2写一个单独的函数,等4变成1的时候就调用1的函数,变成2的时候调用2的函数?这样代码冗余度太高了!那不如全部放在一起得了。循环终止条件是什么?我们采取情况3得跳出情况来作为终止条件,就是当前的节点是红色时,跳出来,把当前得节点染成红色就行了。那么,本来只需要执行一次的情况1、2,现在会不会执行了很多次?不会!当是情况1或者2的时候,我们直接跳出循环!分析完成!开始上代码!
3.删除节点实现
// 删除
void remove(const int& val) {
if (root_ == nullptr) {
return;
}
Node* cur = root_;
while (nullptr != cur) {
if (cur->val_ > val) {
cur = cur->left_;
} else if (cur->val_ < val) {
cur = cur->right_;
} else {
break;
}
}
if (nullptr == cur) {
// 没找到 直接返回
return;
}
// 1.如果待删除的节点有左右孩子的话,我们就把前驱节点覆盖到待删除节点
// 然后去删除前驱节点
if (nullptr != cur->left_ && nullptr != cur->right_) {
Node* pre = cur->left_;
while (nullptr != pre->right_) {
pre = pre->right_;
}
// 在下面删除前驱节点
cur->val_ = pre->val_;
cur = pre;
}
// 2.到这里的话 最多只有一个孩子
Node* child = cur->left_;
if (cur->right_) {
child = cur->right_;
}
// 如果确实有一个孩子
if (nullptr != child) {
child->parent_ = cur->parent_;
if (cur->parent_ == nullptr) {
// 说明待删除节点是根节点
root_ = child;
/*setColor(root_, BLACK);
return;*/
} else {
if (cur->parent_->left_ == cur) {
// 说明cur是其父亲的左孩子
cur->parent_->left_ = child;
} else {
// 说明cur是其父亲的右孩子
cur->parent_->right_ = child;
}
}
Color deleteColor = cur->color_;
delete cur;
// 删除的节点是黑色节点才要检查
if (BLACK == deleteColor) {
fixAfterRemove(child);
}
} else {
// child = nullptr
// 删除的节点是叶子节点
if (nullptr == cur->parent_) {
// 而且是根节点
delete cur;
root_ = nullptr;
return;
} else {
if (color(cur) == BLACK) {
// 先调整,再删除 ???
fixAfterRemove(cur);
}
if (cur->parent_->left_ == cur) {
cur->parent_->left_ = nullptr;
} else {
cur->parent_->right_ = nullptr;
}
delete cur;
}
}
}
// 开始检查
void fixAfterRemove(Node* node) {
while (node != root_ && color(node) == BLACK) {
if (left(parent(node)) == node) {
// 待删除的黑色节点在左子树
Node* brother = right(parent(node));
if (color(brother) == RED) {
// 情况4
setColor(parent(node), RED); // 把父亲染红
setColor(brother, BLACK); // 把兄弟染黑
leftRoate(parent(node)); // 左旋父节点
brother = right(parent(node)); // 重置兄弟
}
if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
// 情况3
setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
node = parent(node); // 迭代检查
} else {
// 情况1、2 进入这里 执行完就会退出
if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
// 情况2
/* rightRoate(brother);
brother = right(parent(node));
setColor(brother, BLACK);
setColor(right(brother), RED);*/
setColor(brother, RED);
setColor(left(brother), BLACK);
rightRoate(brother);
brother = right(parent(node));
}
//情况1
setColor(brother, color(parent(node)));
setColor(parent(node), BLACK);
setColor(right(brother), BLACK);
leftRoate(parent(node));
break;
}
} else {
// 待删除的黑色节点在右子树 把上面涉及方向的操作都反一下就行
Node* brother = left(parent(node));
if (color(brother) == RED) {
// 情况4
setColor(parent(node), RED); // 把父亲染红
setColor(brother, BLACK); // 把兄弟染黑
rightRoate(parent(node)); // 左旋父节点
brother = left(parent(node)); // 重置兄弟
}
if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
// 情况3
setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
node = parent(node); // 迭代检查
} else {
// 情况1、2 进入这里 执行完就会退出
if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
// 情况2
/*leftRoate(brother);
brother = left(parent(node));
setColor(brother, BLACK);
setColor(left(brother), RED);*/
setColor(brother, RED);
setColor(right(brother), BLACK);
leftRoate(brother);
brother = left(parent(node));
}
//情况1
setColor(brother, color(parent(node)));
setColor(parent(node), BLACK);
setColor(left(brother), BLACK);
rightRoate(parent(node));
break;
}
}
}
// 如果发现node指向的节点是红色,直接涂成黑色,调整结束
setColor(node, BLACK);
}
3. 整棵红黑树的实现
#include <iostream>
#include <queue>
enum Color {
BLACK,
RED
};
struct Node
{
int val_;
Node* left_;
Node* right_;
Node* parent_;
Color color_;
Node(int val = INT_MAX, Node* left = nullptr, Node* right = nullptr,
Node* parent = nullptr, Color color = BLACK) :
val_(val), left_(left), right_(right), parent_(parent), color_(color) {
};
};
class RB_tree {
public:
RB_tree() : root_(nullptr) {}
// 返回节点的颜色
Color color(Node* node) {
return nullptr == node ? BLACK : node->color_;
}
// 返回节点的左孩子
Node* left(Node* node) {
return node->left_;
}
// 返回节点的右孩子
Node* right(Node* node) {
return node->right_;
}
// 返回节点的父节点
Node* parent(Node* node) {
return node->parent_;
}
// 设置颜色
void setColor(Node* node, Color color) {
node->color_ = color;
}
// 左旋
void leftRoate(Node* node) {
Node* child = node->right_;
child->parent_ = node->parent_;
if (node->parent_ == nullptr) {
// node本身是根节点 子节点转上去就会变成根节点
root_ = child;
} else {
if (node->parent_->left_ == node) {
// node是父节点的左孩子
node->parent_->left_ = child;
} else {
// node是父节点的右孩子
node->parent_->right_ = child;
}
}
node->right_ = child->left_;
if (child->left_ != nullptr) {
child->left_->parent_ = node;
}
child->left_ = node;
node->parent_ = child;
}
// 右旋操作
void rightRoate(Node* node) {
Node* child = node->left_;
child->parent_ = node->parent_;
if (node->parent_ == nullptr) {
// node是根节点
root_ = child;
} else {
if (node->parent_->left_ == node) {
node->parent_->left_ = child;
} else {
node->parent_->right_ = child;
}
}
node->left_ = child->right_;
if (child->right_ != nullptr) {
child->right_->parent_ = node;
}
child->right_ = node;
node->parent_ = child;
}
void insert(const int& val) {
// 如果为空树的话 new一个结点出来即可
if (root_ == nullptr) {
root_ = new Node(val);
return;
}
//std::cout << val << std::endl;
Node* parent = nullptr;
Node* cur = root_;
while (cur != nullptr) {
if (cur->val_ > val) {
parent = cur;
cur = cur->left_;
} else if (cur->val_ < val) {
parent = cur;
cur = cur->right_;
} else {
// 树里面已经有该值了 直接返回即可
return;
}
}
Node* newNode = new Node(val, nullptr, nullptr, parent, RED);
if (parent->val_ > val) {
parent->left_ = newNode;
} else {
parent->right_ = newNode;
}
// 插入的节点一定是红色
// 如果父节点的颜色是红色,那么在插入之后需要进行调整
if (RED == color(parent)) {
fixAfterInsert(newNode);
}
}
void fixAfterInsert(Node* node) {
// 判断是否需要迭代检查
while (RED == color(parent(node))) {
// 如果插入节点node的父亲是爷爷节点的左孩子
if (left(parent(parent(node))) == node) {
// 那么叔叔节点就是爷爷节点的右孩子
Node* uncle = right(parent(parent(node)));
if (RED == color(uncle)) { // 情况1和情况3:叔叔节点是红色的
setColor(parent(node), BLACK);
setColor(parent(parent(node)), RED);
setColor(uncle, BLACK);
node = parent(parent(node)); // 检查节点从插入节点->爷爷节点(因为爷爷的父亲可能是红色)
} else {
// 先判断是不是情况三 如果是的话 把情况三调整为情况2 再后续处理
if (right(parent(node)) == node) {
leftRoate(parent(node)); // 对插入节点node的父左旋,掰到同一直线
}
// 处理情况2
setColor(parent(node), BLACK);
setColor(parent(parent(node)), RED);
rightRoate(parent(parent(node)));
}
} else { // 如果插入节点node的父亲是爷爷节点的右孩子,处理完全镜像,所有left改成right即可
// 那么叔叔节点就是爷爷节点的右孩子
Node* uncle = left(parent(parent(node)));
if (RED == color(uncle)) { // 情况1和情况3:叔叔节点是红色的
setColor(parent(node), BLACK);
setColor(parent(parent(node)), RED);
setColor(uncle, BLACK);
node = parent(parent(node)); // 检查节点从插入节点->爷爷节点(因为爷爷的父亲可能是红色)
} else {
// 先判断是不是情况三 如果是的话 把情况三调整为情况2 再后续处理
if (left(parent(node)) == node) {
rightRoate(parent(node)); // 对插入节点node的父左旋,掰到同一直线
}
// 处理情况2
setColor(parent(node), BLACK);
setColor(parent(parent(node)), RED);
leftRoate(parent(parent(node)));
}
}
}
setColor(root_, BLACK); // 强制设置根节点为黑色
}
// 删除
void remove(const int& val) {
if (root_ == nullptr) {
return;
}
Node* cur = root_;
while (nullptr != cur) {
if (cur->val_ > val) {
cur = cur->left_;
} else if (cur->val_ < val) {
cur = cur->right_;
} else {
break;
}
}
if (nullptr == cur) {
// 没找到 直接返回
return;
}
// 1.如果待删除的节点有左右孩子的话,我们就把前驱节点覆盖到待删除节点
// 然后去删除前驱节点
if (nullptr != cur->left_ && nullptr != cur->right_) {
Node* pre = cur->left_;
while (nullptr != pre->right_) {
pre = pre->right_;
}
// 在下面删除前驱节点
cur->val_ = pre->val_;
cur = pre;
}
// 2.到这里的话 最多只有一个孩子
Node* child = cur->left_;
if (cur->right_) {
child = cur->right_;
}
// 如果确实有一个孩子
if (nullptr != child) {
child->parent_ = cur->parent_;
if (cur->parent_ == nullptr) {
// 说明待删除节点是根节点
root_ = child;
/*setColor(root_, BLACK);
return;*/
} else {
if (cur->parent_->left_ == cur) {
// 说明cur是其父亲的左孩子
cur->parent_->left_ = child;
} else {
// 说明cur是其父亲的右孩子
cur->parent_->right_ = child;
}
}
Color deleteColor = cur->color_;
delete cur;
// 删除的节点是黑色节点才要检查
if (BLACK == deleteColor) {
fixAfterRemove(child);
}
} else {
// child = nullptr
// 删除的节点是叶子节点
if (nullptr == cur->parent_) {
// 而且是根节点
delete cur;
root_ = nullptr;
return;
} else {
if (color(cur) == BLACK) {
// 先调整,再删除 ???
fixAfterRemove(cur);
}
if (cur->parent_->left_ == cur) {
cur->parent_->left_ = nullptr;
} else {
cur->parent_->right_ = nullptr;
}
delete cur;
}
}
}
// 开始检查
void fixAfterRemove(Node* node) {
while (node != root_ && color(node) == BLACK) {
if (left(parent(node)) == node) {
// 待删除的黑色节点在左子树
Node* brother = right(parent(node));
if (color(brother) == RED) {
// 情况4
setColor(parent(node), RED); // 把父亲染红
setColor(brother, BLACK); // 把兄弟染黑
leftRoate(parent(node)); // 左旋父节点
brother = right(parent(node)); // 重置兄弟
}
if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
// 情况3
setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
node = parent(node); // 迭代检查
} else {
// 情况1、2 进入这里 执行完就会退出
if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
// 情况2
/* rightRoate(brother);
brother = right(parent(node));
setColor(brother, BLACK);
setColor(right(brother), RED);*/
setColor(brother, RED);
setColor(left(brother), BLACK);
rightRoate(brother);
brother = right(parent(node));
}
//情况1
setColor(brother, color(parent(node)));
setColor(parent(node), BLACK);
setColor(right(brother), BLACK);
leftRoate(parent(node));
break;
}
} else {
// 待删除的黑色节点在右子树 把上面涉及方向的操作都反一下就行
Node* brother = left(parent(node));
if (color(brother) == RED) {
// 情况4
setColor(parent(node), RED); // 把父亲染红
setColor(brother, BLACK); // 把兄弟染黑
rightRoate(parent(node)); // 左旋父节点
brother = left(parent(node)); // 重置兄弟
}
if (color(left(brother)) == BLACK && color(right(brother)) == BLACK) {
// 情况3
setColor(brother, RED); // 拉兄弟下水,染兄弟为红色
node = parent(node); // 迭代检查
} else {
// 情况1、2 进入这里 执行完就会退出
if (color(left(brother)) == RED && color(right(brother)) == BLACK) {
// 情况2
/*leftRoate(brother);
brother = left(parent(node));
setColor(brother, BLACK);
setColor(left(brother), RED);*/
setColor(brother, RED);
setColor(right(brother), BLACK);
leftRoate(brother);
brother = left(parent(node));
}
//情况1
setColor(brother, color(parent(node)));
setColor(parent(node), BLACK);
setColor(left(brother), BLACK);
rightRoate(parent(node));
break;
}
}
}
// 如果发现node指向的节点是红色,直接涂成黑色,调整结束
setColor(node, BLACK);
}
// 返回根节点
Node* getRoot() {
return root_;
}
private:
Node* root_;
};
void printTree(Node* root) {
std::queue<Node*> que;
if (root) que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
Node* cur = que.front(); que.pop();
if (cur != nullptr) {
std::cout << cur->val_ << "(" << (cur->color_ == 0 ? "B" : "R") << ")" << " ";
que.push(cur->left_);
que.push(cur->right_);
} else {
std::cout << "NULL ";
}
}
std::cout << std::endl;
}
}
int main() {
{ // 测试删除操作
std::cout << "==========插入============" << std::endl;
RB_tree rbTree;
for (int i = 1; i <= 10; ++i) {
rbTree.insert(i);
}
printTree(rbTree.getRoot());
std::cout << "==========删除============" << std::endl;
rbTree.remove(9);
rbTree.remove(10);
printTree(rbTree.getRoot());
}
return 0;
}
5.测试结果
可以验证,结果完全正确: