删除
因为根据BST中的规则,选择该结点的左子树中最大值和右子树中最小值替代掉原本要删除的点的值,再将改点删掉即可,所以这里只会讨论那个删掉的点。
分为以下情况:
- 删除结点的左右子结点均为空,则将其直接删除即可;
- 删除结点的左右子结点其中一方为空,则将存在的那一方的子结点替代掉删除结点即可。
- 删除结点的左右子结点均不为空,首先选择该结点的替代结点(可以是其左子树中的最大值,也可以是其右子树中的最小值,可以肯定的是替代结点必然最多只有一个子结点),接着将替代结点替换掉删除结点,同时把删除结点删掉,删掉后的结果则分为好几种情况(下面的结点为替代替代结点后的结点):
- 结点为新的根,此时只是将所有的路径中都去除一个黑色结点,所以依然保持平衡;
- 结点的兄弟结点为红色;
- 结点的兄弟结点为黑色,同时其子结点也均为黑色;
- 结点的兄弟结点为黑色,同时兄弟结点的左子结点为红色,右子结点为黑色;
- 结点的兄弟结点为黑色,同时兄弟结点的右子结点为红色,左子结点为红色;
假设N为替换后的结点,P为N的父亲结点,S为N的兄弟结点,还有S左子结点为Sl和右子结点Sr,其中N为P的左子结点,S为右子结点;
3.1,因为子结点已经成为了新的根结点,满足所有条件;
3.2,因为删除后导致通过N的黑色结点减一,无法满足性质5,此时将兄弟结点染黑,父亲结点染红,接着对兄弟结点进行一个左旋转,旋转后如下图,变成了以S为相对根结点,P为红色左子结点的树,现在黑色结点的数目还是没有发生改变,不过可以发现的是,变成了情况3;
3.3,和上面同样的问题,将兄弟结点渲染成红色,导致结点 P两边的黑色结点数量均少了一,但是这会出现另一个问题-不通过P的比通过P的黑色结点的数量多一,此时则需要往上递归,将结点P从情况一开始判断;
3.4,(假设情况4跟情况5均是当前结点为父亲结点的左子结点的情况)此时兄弟结点为父亲结点的右子结点,形成一个右左的形状,接着要对S进行一个右旋转,变成了情况5,如下图:
3.5,此时是一个右右的形状,进行一个左旋即可,需要注意的是,在示意图中的白色节点可以是红色或黑色,但是在变换前后都必须指定相同的颜色,可以发现此时满足所有情况;
实现
需要注意情况4和情况5,情况4中对兄弟结点进行的旋转;而情况5中则对一开始兄弟结点的父亲结点进行的旋转,还有并不需要关心情况5中父亲结点的颜色,只需要保证左边数量加一,右边数量不变即可。
还有出了情况1、4、5,其余情况均是要处理完后往上再重新进行情况的判定。
还有别忘了在处理各种情况完后,将根结点的颜色设置为黑色!
首先对删除结点进行操作:
void RBTree::_del(struct RBTreeRoot *root, struct RBTreeNode *node) {
struct RBTreeNode *child, *parent;
RBColor color;
// 只有一个子结点
if (!node->rb_left || !node->rb_right) {
if (!node->rb_left) {
child = node->rb_right;
}
else {
child = node->rb_left;
}
parent = rb_parent(node);
color = rb_color(node);
if (child) {
rb_set_parent(child, parent);
}
// 是否是根
if (parent) {
if (rb_is_left(node, parent)) {
rb_set_left(parent, child);
}
else {
rb_set_right(parent, child);
}
}
else {
root->rb_node = child;
}
if (color == RB_BLACK) {
this->_del_node(this->_root, parent, child);
}
delete node;
}
else { // 有双子结点
struct RBTreeNode *replace = this->get_small_node(node);
// 用替代结点替换删除结点
struct RBTreeNode *old_parent = rb_parent(node);
if (old_parent) {
if (rb_is_left(node, old_parent)) {
rb_set_left(old_parent, replace);
}
else {
rb_set_right(old_parent, replace);
}
}
else {
this->_root->rb_node = replace;
}
child = replace->rb_right; // 当前替代结点为最多只有右结点
parent = rb_parent(replace);
color = rb_color(node);
// 删除替代结点
if (parent == node) { // 替代结点的父亲结点为删除结点
parent = replace;
}
else {
if (child) {
rb_set_parent(child, parent);
}
parent->rb_left = child;
// 放到里面是因为避免出现删除结点是替代结点的父亲的情况
// 从而导致替代结点的右子结点指向自己
replace->rb_right = node->rb_right;
rb_set_parent(node->rb_right, replace);
}
// 替换结点变为删除结点
replace->rb_parent = node->rb_parent;
replace->rb_color = node->rb_color;
replace->rb_left = node->rb_left;
node->rb_left->rb_parent = replace;
if (color == RB_BLACK) {
this->_del_node(_root, parent, child);
}
delete node;
// 可以发现,替代删除结点为替代结点一共有6个操作,分别是设置左子结点(2个),右子结点(2个),父亲结点(2个)
// 删除替代结点一共有2个操作。
}
}
接着就是对替代的结点进行的操作
void RBTree::_del_node(struct RBTreeRoot *root, struct RBTreeNode *parent, struct RBTreeNode *node) {
struct RBTreeNode *subling; // 兄弟结点
// 满足性质4和性质5
while ((!node || rb_is_black(node) && node != root->rb_node)) {
if (rb_is_left(node, parent)) {
subling = parent->rb_right;
/**
* 分为四种情况,分别是
* 1.兄弟结点是红色
* 2.兄弟结点是黑色,其子结点也是黑色
* 3.兄弟结点是黑色,其左子结点为红色,右子结点为黑色
* 4.兄弟结点是黑色,其左子结点为黑色,右子结点为红色
*/
// case 1
if (rb_is_red(subling)) {
// 将兄弟结点变为黑色,父亲结点变为红色,进行左旋
// 生成一个以黑色的兄弟结点为相对顶点,父亲结点为红色子结点的树
rb_set_black(subling);
rb_set_red(parent);
_left_rotate(root, parent);
subling = parent->rb_right; // 此时兄弟结点的位置
}
// case 2
if ((!subling->rb_left || rb_is_black(subling->rb_left)) && (!subling->rb_right || rb_is_black(subling->rb_right))) {
// 将兄弟结点染红,设置当前结点为父亲结点
rb_set_red(subling);
node = parent;
rb_set_parent(node, rb_parent(parent));
}
else {
// case 3 兄弟结点的左子结点为红色
if (!subling->rb_right || rb_is_black(subling->rb_right)) {
// 交换兄弟结点和兄弟结点的左子结点的颜色,进行右旋
// 形成以兄弟结点的左子结点为相对顶点的树
rb_set_black(subling->rb_left);
rb_set_red(subling);
_right_rotate(root, subling);
subling = parent->rb_right; // 此时兄弟结点为原先兄弟结点的左子结点,同时也是相对根结点
}
// case 4 兄弟结点的右子结点为红色
// 设置兄弟结点为黑色,并且将兄弟结点进行左旋
// 形成以兄弟结点为相对根结点,父亲结点为兄弟结点的左子结点的树
rb_set_black(parent);
rb_set_black(subling->rb_right);
rb_set_color(subling, rb_color(parent)); // 原来的父亲结点是什么颜色的,兄弟结点就是什么颜色的
_left_rotate(root, parent); // 此时是对父亲结点进行旋转
node = root->rb_node;
break;
}
}
else {
subling = parent->rb_left;
/**
* 分为四种情况,分别是
* 1.兄弟结点是红色
* 2.兄弟结点是黑色,其子结点也是黑色
* 3.兄弟结点是黑色,其左子结点为红色,右子结点为黑色
* 4.兄弟结点是黑色,其左子结点为黑色,右子结点为红色
*/
// case 1
if (rb_is_red(subling)) {
// 将兄弟结点变为黑色,父亲结点变为红色,进行左旋
// 生成一个以黑色的兄弟结点为相对顶点,父亲结点为红色子结点的树
rb_set_black(subling);
rb_set_red(parent);
_right_rotate(root, parent);
subling = parent->rb_right; // 此时兄弟结点的位置
}
// case 2
if ((!subling->rb_left || rb_is_black(subling->rb_left)) && (!subling->rb_right || rb_is_black(subling->rb_right))) {
// 将兄弟结点染红,设置当前结点为父亲结点
rb_set_red(subling);
node = parent;
parent = rb_parent(node);
}
else {
// case 3 兄弟结点的左子结点为红色
if (!subling->rb_left || rb_is_black(subling->rb_left)) {
// 交换兄弟结点和兄弟结点的左子结点的颜色,进行右旋
// 形成以兄弟结点的左子结点为相对顶点的树
rb_set_black(subling->rb_right);
rb_set_red(subling);
_left_rotate(root, subling);
subling = parent->rb_left;
}
// case 4 兄弟结点的右子结点为红色
// 设置兄弟结点为黑色,并且将兄弟结点进行左旋
// 形成以兄弟结点为相对根结点,父亲结点为兄弟结点的左子结点的树
rb_set_black(parent);
rb_set_black(subling->rb_left);
rb_set_color(subling, rb_color(parent)); // 原来的父亲结点是什么颜色的,兄弟结点就是什么颜色的
_right_rotate(root, parent);
node = root->rb_node;
break;
}
}
}
if (node) {
rb_set_black(node);
}
}
删除的操作相对添加的要更困难一些。