查找 -- 红黑树的删除操作

在红黑树里面最复杂的操作就是对红黑树里面的节点进行删除了。不仅要进行二叉搜索树的删除操作,还需要进行对红黑树性质的保持。

我们先定义删除操作的函数原型:

void RBTree<Key, Value>::erase(Key key)

函数接受一个Key类型的值,树会搜索含有这个值的节点,并且删除这个节点,如果找不到,不会对树进行任何的操作。

删除操作也需要一些辅助函数:

void rotateLeft(Node<Key, Value>* node);
void rotateRight(Node<Key, Value>* node);
Node<Key, Value>* findNext(Node<Key, Value>* node);
Node<Key, Value>* findMin(Node<Key, Value>* node);
void dealOneChild(Node<Key, Value>* node, Node<Key, Value>* child);
void adjustDelete(Node<Key, Value>* node, Node<Key, Value>* realParent);

上面的函数中我们需要关注的函数只有adjustDelete这个函数,这个是用来在删除一个节点之后,用来调整红黑树的。


其他的操作依次为:①左旋 ②右旋 ③找到node节点中序的下一个节点 ④找到以node为根的树的最小节点 ⑤交换两个节点的位置(指针操作,并不是交换节点内部的成员)


我们可以想象:删除一个节点会出现四种情况:删除的节点(del)替代的节点(rep)
① del是红色的,rep是红色的。
② del是红色的,rep是黑色的。
③ del是黑色的,rep是红色的。
④ del是黑色的,rep是黑色的。

当rep是红色的时候,无论del是红是黑,只要简单地把rep的颜色改成黑色就可以保持所有的红黑树性质。
所以我们只需要对rep是黑色的那两种情况进行讨论即可。

在二叉搜索树中,我们删除一个节点会分成三种情况:①被删除节点的左子女为空 ②被删除节点的右子女为空 ③被删除的节点左右子女都不为空。
同样的,我们在红黑树的删除中,也需要考虑这三种情况,并且要对这三种情况进行相应的处理(因为涉及到一些指针的保存)

删除操作跟插入操作不一样,不是对于父节点的兄弟节点进行判断,而是对于rep的兄弟节点来进行判断,因为rep是黑色的,所以在rep到它祖先节点的所有节点上,第五条规则都会被破坏掉,我们需要对rep的兄弟节点的颜色来进行不同的处理。(这些处理都是对于第五条规则的维护)
注意:这里的rep是已经把del替换之后的rep,而不是替换之前的rep


同样的,我们也需要对bro的左右进行判断,并且左右也是对称的,跟插入一样。

①当rep的兄弟节点 (bro)为红色的时候:
这里写图片描述
我们可以通过把bro的颜色变黑,把rep和bro的父节点变红,进行一次旋转。

②当rep的兄弟节点为黑色的时候:
在这种情况中,我们还需要对bro的子女的颜色进行判断:双黑,左边红色,右边红色。
(1)双黑:因为rep那边已经少了一个黑色节点,我们需要把bro变成红色,达到平衡效果
(2)左边红色:我们需要把bro的左子女变成黑色,bro变成红色,进行一次旋转变成(3)这种情况
(3)右边红色:bro变成红色,rep的父节点变成黑色,进行一次旋转。

经过最多三次变化,我们就可以维持住红黑性质。
这里写图片描述

下面是主要的代码(adjustDelete,erase)

erase:

template <class Key, class Value>
void RBTree<Key, Value>::erase(Key key) {
    Node<Key, Value>* target = find(key);
    if (target == NULL) {
        cout << "Can not delete node not exist" << endl;
        return ;
    }
    int originalColor = target->color;
    //next节点是用来替换被删除的节点的,replaceNode是用来替换next节点的
    //我们还需要一个额外的节点来保存replaceNode的父节点,因为replaceNode的值可能是NULL
    //额外的节点也要当做参数传到adjustDelete函数里面
    Node<Key, Value>* next = target;
    Node<Key, Value>* replaceNode = NULL; 
    Node<Key, Value>* replaceParent = NULL;
    // 先处理要删除的节点只有一个子女的情况
    if (target->left == NULL) {
        replaceNode = target->right;
        dealOneChild(target, replaceNode);
        replaceParent = target->parent;
    } else if (target->right == NULL) {
        replaceNode = target->left;
        dealOneChild(target, replaceNode);
        replaceParent = target->parent;
    } else {        //这里开始处理删除节点有两个子女的情况
        next = findNext(target);
        originalColor = next->color;
        replaceNode = next->right;  //只能是右子女,左子女的话,那么就不是最小的节点了

        //要处理next的右子树的根不为后继的情况,这里主要是处理后继节点替换
        //如果右子树的根为后继的话,那么直接就开始替换被删除的节点了
        if (next->parent != target) {
            dealOneChild(next, next->right);
            next->right = target->right;
            target->right->parent = next;
            replaceParent = next->parent;      //注意这个赋值
        } else {
            replaceParent = next;
        }
        dealOneChild(target, next);
        next->left = target->left;
        target->left->parent = next;
        next->color = target->color;
    }
    if (originalColor == BLACK) {
        adjustDelete(replaceNode, replaceParent);
    }
    delete target;
    target = NULL;
}

adjustDelete:

template <class Key, class Value>
void RBTree<Key, Value>::adjustDelete(Node<Key, Value>* node, Node<Key, Value>* nodeParent) {
    while (node != root && (node == NULL || node->color == BLACK)) {
        if (node == nodeParent->left) {
            Node<Key, Value>* bro = nodeParent->right;
            if (bro->color == RED) {         //node的兄弟节点为红色,情况1
                bro->color = BLACK;
                bro->parent->color = RED;
                rotateLeft(bro->parent);
                bro = nodeParent->right;
            } 

            //node兄弟节点为黑色
            if ((bro->left == NULL || bro->left->color == BLACK)
                && (bro->right == NULL || bro->right->color == BLACK)) {   //兄弟节点的两个子节点都为黑色,情况2
                bro->color = RED;
                node = nodeParent;
                nodeParent = nodeParent->parent;
            } else {       //兄弟节点其中一个节点为红色
                if (bro->right == NULL || bro->right->color == BLACK) {    //兄弟节点的左子女为红色,情况3
                    if (bro->left != NULL) {
                        bro->left->color = BLACK;
                    }
                    bro->color = RED;
                    rotateRight(bro);
                    bro = nodeParent->right;
                }
                // 兄弟节点的右子女为红色,情况4 
                bro->color = RED;
                nodeParent->color = BLACK;
                if (bro->right != NULL) {
                    bro->right->color = BLACK;
                }
                rotateLeft(nodeParent);
                node = root;
            }
        } else {
            Node<Key, Value>* bro = nodeParent->left;
            if (bro->color == RED) {
                bro->color = BLACK;
                bro->parent->color = RED;
                rotateRight(bro->parent);
                bro = nodeParent->left;
            }
            if ((bro->left == NULL || bro->left->color == BLACK)
                && (bro->right == NULL || bro->right->color == BLACK)) {
                bro->color = RED;
                node = nodeParent;
                nodeParent = nodeParent->parent;
            } else {
                if (bro->left == NULL || bro->left->color == BLACK) {
                    if (bro->right != NULL) {
                        bro->right->color = BLACK;
                    }
                    bro->color = RED;
                    rotateLeft(bro);
                    bro = nodeParent->left;
                }
                bro->color = RED;
                nodeParent->color = BLACK;
                if (bro->left != NULL) {
                    bro->left->color = BLACK;
                }
                rotateRight(nodeParent);
                node = root;
            }
        }
    }
    if (node != NULL) {
        node->color = BLACK;
    }
}

上面的代码中,需要注意的是replaceParent这个指针变量,因为我们在寻找到next之后,next->right这个值可能是null,所以不能简单地通过next->right->parent这个值来找到真正的next的父节点,所以对不同的情况我们需要分开赋值(在替换节点的时候)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值