刪除
因為根據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);
}
}
刪除的操作相對添加的要更困難一些。