详解红黑树 c++实现

红黑树

红黑树是一种高效的自平衡二叉查找树,通过特定规则和操作确保平衡,从而保证各项操作的时间复杂度为O(log n)。
红黑树因其在动态插入 / 删除操作与查询效率之间的平衡特性,被广泛应用于多种系统和框架中:

  • C++ STLstd::mapstd::set基于红黑树实现。
  • Java集合框架TreeMapTreeSet使用红黑树。
  • 数据库索引:部分数据库的索引结构采用红黑树或其变种。
  • Linux内核:进程调度、虚拟内存管理等模块使用红黑树管理数据。
    红黑树与 AVL 树的核心差异在于平衡严格性与动态操作效率的权衡,AVL 树通过严格平衡因子(任意节点左右子树高度差≤1)保证绝对平衡,查询效率略高,但插入 / 删除时可能需要 O (log n) 次旋转调整,导致较高的维护成本;红黑树则通过颜色规则(如红节点子节点必为黑)维持近似平衡,插入最多 2 次旋转、删除最多 3 次,更适应频繁更新的场景。因此,AVL 树适合查询密集型任务(如数据库索引),而红黑树在插入 / 删除频繁的系统中表现更优(如 STL 的 map、Java 的 TreeMap)。
    红黑树满足以下五个核心性质:
  1. 根节点为黑色
  2. 每个节点要么是红色,要么是黑色
  3. 叶子节点(NIL节点)均为黑色。这里的叶子结点是最后的null结点
  4. 红色节点的子节点必须为黑色(即不存在连续两个红色节点)。
  5. 从任意节点到其所有叶子节点的路径包含相同数量的黑色节点(称为“黑高”一致)。
    这些性质保证了最长路径不会超过最段路径两倍,最长路径是红黑相间的,最段路径是连续黑的
    下面是红黑树的结点结构,相较于普通的二叉搜索树它多了颜色与父节点指针的属性,颜色很简单因为我们的红黑树,所有的结点非黑即红所以直接使用bool来表示就可以了,parent父结点的作用是为后续操作提供可能,后期会经常需要知道插入或删除结点的,叔叔结点或兄弟结点的颜色,我们需要通过父结点和爷爷结点来获得这些信息
struct BinaryTreeNode {
    bool isRed{true};
    int height{1};
    int data;
    BinaryTreeNode* left;
    BinaryTreeNode* right;
    BinaryTreeNode* parent;
    BinaryTreeNode(int val, bool c = true) 
        : isRed(c),data(val),left(nullptr),right(nullptr),parent{nullptr} {}
};

插入

插入的基本操作与二叉搜索树一样,我们只需要在插入完成后,检查插入的结点是否符合一定规则。新插入的节点默认颜色为红色,这是因为将新节点设为红色更有可能不破坏红黑树的性质 5(黑色高度平衡),即使破坏了性质 4(不能有两个连续的红色节点),也相对容易调整。
插入新节点后,需要检查红黑树的性质是否被破坏。若破坏了性质 4(新节点的父节点是红色),就需要进行调整,调整过程根据不同情况分为以下两种情形:

  1. 叔叔节点是红色
    • 条件:新节点 z 的父节点 p 是红色,且 z 的叔叔节点 u(父节点的兄弟节点)也是红色。
    • 调整操作
      • 将父节点 p 、叔叔节点 u 和祖父节点 g 的颜色反转。
      • 把当前节点 z 指向祖父节点 g,继续向上检查和调整。
  2. 叔叔节点是黑色
    • 条件:新节点 z 的父节点 p 是红色,叔叔节点 u 是黑色,如果叔叔结点是null也认为是黑色
    • 调整操作
      • 检测祖父结点的的平衡因子,进行相应的旋转,旋转完成后将旋转点与旋转中心点的颜色反转
        下面是我们的检测函数
void BinarySearchTree::_fixInsertion(BinaryTreeNode *node) {
    if (node == _rootNode){
        node->isRed = false;
        return;
    }
    BinaryTreeNode* father = node->parent;
    if (!father->isRed) return;
    BinaryTreeNode* grandfather = father->parent;
    if (grandfather == nullptr) return;
    BinaryTreeNode* uncle = father == grandfather->left ? 
		grandfather->right : grandfather->left;

    if (uncle == nullptr || uncle->isRed == false){
        if (grandfather == _rootNode) _rootNode = spinTree(grandfather);
        else {
            grandfather->parent->left == grandfather ?
                grandfather->parent->left = spinTree(grandfather) :
                grandfather->parent->right = spinTree(grandfather);
        }
    }else{
        grandfather->isRed = !grandfather->isRed;
        uncle->isRed = !uncle->isRed;
        father->isRed = !father->isRed;
        _fixInsertion(grandfather);
    }
}

这个函数一开始会检测传入的结点是不是跟结点,如果是就将其颜色改为黑色并退出,如果不是后续会获得当前结点的,父结点祖父结点叔叔结点三个结点,如果叔叔黑色我们就进行对应的旋转操作,spinTree函数我们会在后续详细介绍,如果叔叔结点是红色,我们反转三者的颜色后再检测一次祖父结点
下面是旋转操作的实现,红黑树的旋转逻辑与avl树一样,详细可查看详细介绍平衡二叉树 c++实现-优快云博客

BinaryTreeNode* BinarySearchTree::spinTree(BinaryTreeNode *node){
    if (node == nullptr) return nullptr;

    int balance = getBalanceFactor(node);
    int leftBalance = getBalanceFactor(node->left);
    int rightBalance = getBalanceFactor(node->right);
    if (balance > 1 && leftBalance >= 0) {
        std::cout << "Tree is unbalanced LL:  " << node->data << std::endl;
        node->isRed = !node->isRed;// 旋转点
        node->left->isRed = !node->left->isRed;// 旋转中心点
        return spinTreeLL(node);
    }
    if (balance > 1 && leftBalance < 0) {
        std::cout << "Tree is unbalanced LR:  " << node->data << std::endl;
        node->isRed = !node->isRed;// 旋转点
        return spinTreeLR(node);
    }
    if (balance < -1 && rightBalance <= 0) {
        std::cout << "Tree is unbalanced RR:  " << node->data << std::endl;
        node->isRed = !node->isRed;// 旋转点
        node->right->isRed = !node->right->isRed;// 旋转中心点
        return spinTreeRR(node);
    }
    if (balance < -1 && rightBalance > 0) {
        std::cout << "Tree is unbalanced RL:  " << node->data << std::endl;
        node->isRed = !node->isRed;// 旋转点
        return spinTreeRL(node);
    }
    return node;
}
BinaryTreeNode* BinarySearchTree::spinTreeLR(BinaryTreeNode *root){
    root->left = spinTreeRR(root->left);
    root->left->isRed = !root->left->isRed;// 旋转中心点
    return spinTreeLL(root);
}

BinaryTreeNode *BinarySearchTree::spinTreeRL(BinaryTreeNode *root){
    root->right = spinTreeLL(root->right);
    root->right->isRed = !root->right->isRed;// 旋转中心点
    return spinTreeRR(root);
}

删除

红黑树中插入一个结点最容易出现两个连续的红色结点,违背红黑树的性质3(红黑树中不存在两个相邻的红色结点)。而删除操作,最容易造成子树黑高(Black Height)的变化(删除黑色结点可能导致根结点到叶结点黑色结点的数目减少,即黑高降低)。
这是相当麻烦的事情要比插入难很多,红黑树的删除操作是建立在二叉搜索树的基础上的,删除完后检测几种情况进行调整,总体分为下面几种情况

  • 删除节点是叶子节点并且是红色:这种情况什么也不需要干,直接删除就可以了
  • 删除节点只有一个孩子:这种情况我们只需要将替换上来的节点变黑就可以
  • 删除节点是黑色节点:这是最麻烦的情况,因为会删除会违反第五条规则,我们想要根据下面的规则进行调整
    1. 删除节点的兄弟是黑节点且有红孩子:我们需要检查父节点的平衡因子进行相应的旋转,旋转后再变色
    2. 删除节点的兄弟是黑节点且孩子都是黑色的:兄弟变红后再检查父节点是否符合规则
    3. 删除节点的兄弟是红节点:兄弟与父节点变色再进行对应的旋转
      ![[红黑树删除规则.jpg]]
BinaryTreeNode *BinarySearchTree::_remove(BinaryTreeNode *root, int data) {
    if (root == nullptr) return nullptr;

    if (data < root->data) {
        BinaryTreeNode* temp = _remove(root->left, data);
        if (root != _rootNode)root->left = temp;
    } else if (data > root->data) {
        BinaryTreeNode* temp = _remove(root->right, data);
        if (root != _rootNode)root->right = temp;
    } else {
        // 找到要删除的节点
        if (root->left == nullptr || root->right == nullptr) {
            BinaryTreeNode *temp = root->left ? root->left : root->right;
            if (temp == nullptr) {// 没有孩子的红结点直接删除,黑结点比较麻烦
                temp = root;
                root = nullptr;
                _fixDelete(temp);
                std::cout << "f222ather->parent->left: " << _rootNode->left->data << " father->l: " << _rootNode->right->data << std::endl;
            } else {// 只有一个孩子的结点,替换后变黑
                // *root = *temp;
                root->data = temp->data;
                root->height = temp->height;
                root->left = temp->left;
                root->right = temp->right;
                root->isRed = false;
            }
            delete temp;
        } else {
            BinaryTreeNode *temp = _findMin(root->right);
            root->data = temp->data;
            root->right = _remove(root->right, temp->data);
        }
    }

    if (root == nullptr) return nullptr;

    // 更新节点高度
    root->height = 1 + std::max(getHeight(root->left), getHeight(root->right));
    return root;
}
void BinarySearchTree::_fixDelete(BinaryTreeNode *node) {
    if (node->isRed) return;
    BinaryTreeNode* father = node->parent;
    BinaryTreeNode* brother = father->left == node ? father->right : father->left;
    std::cout << "father: " << father->data << " brother: " << brother->data << " node: " << node->data << std::endl;

    //兄弟是红色
    if (brother->isRed){
        brother->isRed = false;
        father->isRed = !father->isRed;
        if (father->left == node){
            if (father == _rootNode){
                _rootNode = spinTreeRR(father);
            }else {
                father->parent->left == father ? 
                    father->parent->left = spinTreeRR(father) : 
                    father->parent->right = spinTreeRR(father);
            }
        }else{
            if (father == _rootNode){
                _rootNode = spinTreeLL(father);
            }else {
                father->parent->left == father ? 
                    father->parent->left = spinTreeLL(father) : 
                    father->parent->right = spinTreeLL(father);
            }
        }
        _fixDelete(node);
        return;
    }

    //兄弟是黑色并且有两个黑孩子
    if ((brother->left == nullptr || !brother->left->isRed) && (brother->right == nullptr || !brother->right->isRed)){
        if (!father->isRed){
            brother->isRed = true;
            _fixDelete(father);
        }else{
            brother->isRed = true;
            father->isRed = false;
        }
        return;
    }

    _fixDeleteBrother(node, brother, father);
}
void BinarySearchTree::_fixDeleteBrother(BinaryTreeNode *node, BinaryTreeNode *brother, BinaryTreeNode *father){
    if (node == father->left){
        father->left = nullptr;
        if (brother->right == nullptr || !brother->right->isRed) {
            brother->left->isRed = father->isRed;
            father->isRed = false;
            std::cout << "_fixDelete RL" << std::endl;
            brother->left->isRed = father->isRed;
            father->isRed = false;
            if (father == _rootNode){
                _rootNode = spinTreeRLNoColor(father);
            }else {
                father->parent->left == father ? 
                    father->parent->left = spinTreeRLNoColor(father) : 
                    father->parent->right = spinTreeRLNoColor(father);
            }
            return;
        }
        std::cout << "_fixDelete RR" << std::endl;
        brother->isRed = father->isRed;
        father->isRed = false;
        brother->right->isRed = brother->isRed;
        if (father == _rootNode){
            _rootNode = spinTreeRR(father);
        }else {
            father->parent->left == father ? 
                father->parent->left = spinTreeRR(father) : 
                father->parent->right = spinTreeRR(father);
        }
    }else{
        father->right = nullptr;
        if (brother->left == nullptr || !brother->left->isRed) {
            std::cout << "_fixDelete LR" << std::endl;
            brother->right->isRed = father->isRed;
            father->isRed = false;
            if (father == _rootNode){
                _rootNode = spinTreeLRNoColor(father);
            }else {
                father->parent->left == father ? 
                    father->parent->left = spinTreeLRNoColor(father) : 
                    father->parent->right = spinTreeLRNoColor(father);
            }
            return;
        }
        std::cout << "_fixDelete LL" << std::endl;
        brother->isRed = father->isRed;
        father->isRed = false;
        brother->left->isRed = brother->isRed;
        if (father == _rootNode){
            _rootNode = spinTreeLL(father);
        }else {
            father->parent->left == father ? 
                father->parent->left = spinTreeLL(father) : 
                father->parent->right = spinTreeLL(father);
        }
    }
}

这段代码实现了红黑树的删除操作,核心函数是_remove,它接收一个根节点和要删除的数据作为参数,递归地在树中查找并删除目标节点,同时维持树的平衡。在_remove函数中,先判断目标数据与当前节点数据的大小关系,若小于当前节点数据则递归删除左子树中的目标节点,若大于则递归删除右子树中的目标节点。当找到目标节点时,根据其孩子节点的情况进行不同处理。若节点只有一个孩子或没有孩子,就用孩子节点替换该节点,对于没有孩子的黑色节点,会调用_fixDelete函数进行调整。若节点有两个孩子,就用其右子树中的最小节点替换该节点,并递归删除右子树中的最小节点。
_fixDelete函数用于处理删除黑色节点后的红黑树平衡问题。它接收一个要删除的节点作为参数,先判断节点是否为红色,若是红色则直接返回,因为删除红色节点不会破坏红黑树的性质。若为黑色,找到其兄弟节点,根据兄弟节点的颜色和孩子节点的颜色进行不同处理。若兄弟节点为红色,就将兄弟节点变黑,父节点变红,并进行相应的旋转操作,然后递归调用_fixDelete函数。若兄弟节点为黑色且有两个黑色孩子,根据父节点的颜色进行不同调整,若父节点为黑色则将兄弟节点变红并递归调整父节点,若父节点为红色则将兄弟节点变红、父节点变黑。若以上情况都不满足,则调用_fixDeleteBrother函数进一步处理。
_fixDeleteBrother函数根据要删除节点是父节点的左孩子还是右孩子进行不同的处理。若要删除节点是左孩子,当兄弟节点的右孩子为黑色时进行 RL 旋转,否则进行 RR 旋转;若要删除节点是右孩子,当兄弟节点的左孩子为黑色时进行 LR 旋转,否则进行 LL 旋转。旋转操作中会调整节点的颜色和父节点的指针,以保证红黑树的性质。节点结构BinaryTreeNode包含了节点的颜色、高度、数据、左右孩子指针和父节点指针,方便在删除操作中进行节点的替换和树的调整。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值