数据结构07:查找[C++][红黑二叉排序树RBT]

news2024/11/18 4:45:18

图源:文心一言 | 提词:动漫风格 红黑树 少女#创意图#

考研笔记整理1.7w+字,但是删除操作的代码是有一点问题的{无法正确处理红色结点的删除},其它功能可正常使用,请小伙伴注意~~🥝🥝

第1版:查资料、画导图、画配图~🧩🧩

参考用书:王道考研《2024年 数据结构考研复习指导》

参考用书配套视频:7.3_4_红黑树的定义和性质_哔哩哔哩_bilibili

特别感谢: Chat GPT老师、文心一言老师~


📇目录

📇目录

🦮思维导图 

🧵基本概念

⏲️定义

⏲️性质

⌨️代码实现

🧵分段代码

 🔯P0:调用库文件

 🔯P1:定义树结点

 🔯P2:结点旋转操作

 🔯P3:插入修复函数

 🔯P4:插入结点

 🔯P0-P4附录:构造二叉树

 🔯P5:寻找最小结点

 🔯P6:结点替换

 🔯P7:删除修复函数

 🔯P8:结点删除 [代码与注释可能有误]

 🔯P9:树的遍历

 🔯P10:main函数

🧵完整代码

 🔯P0:完整代码

 🔯P1:执行结果

🔚结语


🦮思维导图 

备注:

  • 思维导图为整理王道教材第7章 查找的所有内容;
  • 本篇仅涉及到红黑树(RBT)的内容~   //前两篇博文收到了小伙伴的点赞、收藏、评论真的很开心,老泪纵横++,我又觉得我能肝博文了~~

🧵基本概念

⏲️定义

 红黑二叉排序树,若树非空,满足下列特性:

  • 是一棵朴素二叉排序树,简单概括就是“ 左子树 < 根结点 < 右子树 ”的二叉树。
  • 在插入和删除结点时,要满足红黑树的性质:
    • 结点颜色:黑色红色
    • 根结点颜色:黑色
    • 叶节点颜色:此处为虚构的外部NULL结点,黑色
    • 分支结点颜色:
      • 不存在两个相邻的红色结点,即红色结点的孩子与父亲都是黑色
      • 对于每个结点,到任意一个叶结点的简单路径上,黑色结点的数量相同。

 机智的咸鱼老师freestyle:左根右、根叶黑、不红红、黑路同。{1表示朴素二叉树的排序性质、2表示根结点与叶子结点的颜色、3表示红色结点不相邻、4表示任意结点到叶结点简单路径上的黑色结点均相同}。

 举个栗子,例如以下就是两棵简单的红黑树:

 黑色高度:从某结点(不含该结点本身)到叶子结点简单路径中的黑结点总数。根据性质“黑路同”,每个结点,从该结点到任意叶子结点的黑高应该是完全相同的~

 红黑树的适用范围

从图中我们可以看到,红黑树1是接近于平衡二叉树的时候,红黑树2相对于平衡二叉树略微有些失衡,但也可以基本保持树的宽度,也就是可以保证树的查询效率~

 为什么会存在红黑树这种花里胡哨的树,大概是因为红黑树的二叉树兄弟们都过于偏科:

  • 🌸朴素二叉排序树(BST):数据结构07:查找[C++][朴素二叉排序树BST],这棵二叉排序树兄弟,虽然插删结点的速度非常快,但是万一结点输入的顺序不理想,查询的速度就会非常之慢,最慢的时候可以和链表查询的速度一致~可能适合总是在插删但很少查询的情况~
  • 🌸平衡二叉排序树(AVL):数据结构07:查找[C++][平衡二叉排序树AVL],这棵二叉排序树兄弟,虽然查询结点的效率非常高,但是每次插删结点几乎都会进行一个左旋右旋调整树的大动作,调整的频率非常之高且速度非常之慢~可能适合总是在查询但很少插删的情况~

所以遇到插删和查询的动作频率比较均衡的情况,朴素二叉树、平衡二叉树就显得有点力不从心,这种时候,就可以考虑红黑树这种,既可以保持宽树形,树形调整又没有那么频繁的树~~

红黑树的时间复杂度与平衡二叉树接近,为 O(log n)~

⏲️性质

性质1:从根到叶结点的最长路径不大于最短路径的2倍。

  • 最短路径:全黑结点;最长路径:红黑相间。由于 红色结点不能相邻 的特性,黑色与红色结点仅能以接近1:1的比例交错出现,因此从根到叶结点的最长路径不大于最短路径的2倍。

性质2:有n个内部结点的红黑树的高度 h ≤ 2log (n+1)。

  • 根据以上结论,根的黑高至少为 h/2,即 n ≥ 2^(h/2) - 1。

下面我们以上图中的红黑树1为例,说明如何创建及遍历一棵简单的红黑二叉排序树~


图源:文心一言 | 提词:动漫风格 红黑树 少女#创意图#

⌨️代码实现

🧵分段代码

 🔯P0:调用库文件

  • 输入输出流文件iostream{本代码用于输入与输出};
  • 动态数组的向量文件vector{本代码用于创建树结点的动态数组};
#include <iostream>
#include <vector>

 🔯P1:定义树结点

红黑树是一个家庭观念非常强的大家族,它的孩子结点属性会受到父结点、叔叔结点甚至是爷爷结点的影响~因此相比其它的二叉树伙伴,不仅多一个颜色属性,而且多一个父结点指针,以便于根据祖先的颜色改弦易调~

enum Color {    // 定义枚举类型:颜色
    RED,     // 红色结点
    BLACK    // 黑色结点
};

struct RBNode {    // 红黑树结点结构
    int data;       // 数据域
    Color color;    // 结点颜色
    RBNode* parent;    //父结点指针
    RBNode* left;      //左孩子指针
    RBNode* right;     //右孩子指针

    RBNode(int val, Color c, RBNode* p, RBNode* l, RBNode* r)    //变量赋值
        : data(val), color(c), parent(p), left(l), right(r) {}
};

 🔯P2:结点旋转操作

为了将红黑树的树形尽量调宽,红黑树平衡调整的基本操作是染色和旋转,旋转的操作如下——

  • 左子树不平衡:当前结点移动到右子树的位置,在图中类似于右旋的操作~
  • 右子树不平衡:当前结点移动到左子树的位置,在图中类似于左旋的操作~

如果我没有理解错的话,简单版本的左旋大概是这样的下图这样的{此处暂时不考虑染色}~

注意:括号内是平衡因子,计算公式=左子树高-右子树高~

相比平衡树AVL,实际上这个代码考虑了结点旋转时父指针的赋值,所以显得长很多,实际有效操作与平衡树AVL的旋转是类似的~

    void LeftRotate(RBNode* node) {          //左旋操作
        RBNode* rightChild = node->right;    //保存 node右孩子结点 为rightChild
        node->right = rightChild->left;      //将node右孩子指针 指向 rightChild左孩子指针

        if (rightChild->left != nullptr) {      //如果rightChild的左指针不为空
            rightChild->left->parent = node;    //将rightChild的左指针的父指针指向node,作用为更新rightChild的右孩子的父指针
        }

        rightChild->parent = node->parent;    //将rightChild的父指针指向node的父指针

        if (node->parent == nullptr) {            //若node父指针为空,即node为根结点
            root = rightChild;                        //将根结点root赋值到rightChild
        } else if (node == node->parent->left) {  //若node属于父节点的左子树
            node->parent->left = rightChild;          //将rightChild结点挂在node父节点的左侧
        } else {                                  //若node属于父节点的左子树
            node->parent->right = rightChild;         //将rightChild结点挂在node父节点的右侧
        }

        rightChild->left = node;         //rightChild左指针指向node
        node->parent = rightChild;       //node父指针指向rightChild
    }

右旋是左旋的镜像操作,此处不再赘述~ 

    void RightRotate(RBNode* node) {          //右旋操作
        RBNode* leftChild = node->left;
        node->left = leftChild->right;

        if (leftChild->right != nullptr) {
            leftChild->right->parent = node;
        }

        leftChild->parent = node->parent;

        if (node->parent == nullptr) {
            root = leftChild;
        } else if (node == node->parent->left) {
            node->parent->left = leftChild;
        } else {
            node->parent->right = leftChild;
        }

        leftChild->right = node;
        node->parent = leftChild;
    }

 🔯P3:插入修复函数

封装旋转的操作以后,此处我们正式介绍一下,当树形不满足红黑树性质时应该怎么调整~

为了维持红黑树的稳定性,我们通常将插入的结点首先涂红,并按照“左根右”{左子树<根结点<右子树}的数值插入到树中,然后在路径上不满足红黑树的“不红红”  {红色结点不能相邻} 与“黑路同”{结点所在路径中黑色结点数量相同}性质时调整平衡~

综上,简单的红黑树插入结点z可以汇总为以下情况——

  • 以逸待劳型:插入的结点z是红色,结点z的父节点是黑色:
    • 不会破坏红黑树性质,不调整~
  • 独孤求败型:插入的结点z是根结点:
    • 根据红黑树的性质,根结点必须为黑色,将结点z染成黑色~
  • 染色旋转型:插入的结点z是红色,结点z的父结点是红色,结点z的叔叔结点不存在或者不是红色 {此处操作和平衡树是相似的} :
    • 左左型:插入到结点左子树的左子树,导致不平衡,需要右旋,且更改结点z的父结点颜色{结点z的父结点由红色转黑色},结点z的爷爷结点颜色{结点z的爷爷结点由黑色转红色}~
    • 右右型:插入到结点右子树的右子树,导致不平衡,左左型的镜像操作;
    • 左右型:插入到结点左子树的右子树,导致不平衡,需要先左旋后右旋;左旋之后的树变为左左型,后序操作右旋的完全相同。
    • 右左型:插入到结点右子树的左子树,导致不平衡,左右型的镜像操作;
  • 染色循环型:插入的结点z是红色,结点z的父结点是红色,结点z的叔叔结点存在且为红色:
    • 结点z的爷爷结点由黑色染成红色,结点z的父结点结点z的叔叔结点由红色染成黑色~现在,结点z的爷爷结点、结点z的父结点、结点z的叔叔结点、结点z这祖孙三代就能满足红黑树性质啦~
    • 但是到这里还没有结束哦,因为结点z的爷爷结点由黑色染成红色了,有可能会影响上层的结点颜色,因此会把结点z的爷爷结点作为新的插入结点,开始向上循环处理~ {解释:原来人家结点z的太爷爷结点有可能也是红色、结点z的爷爷结点是黑色才能保持原来的红黑树满足要求;而经过调整,现在结点z的爷爷结点也是红色,如果相邻两代都是红色,就不能满足红黑树条件了}

染色旋转型:插入结点的叔叔结点不存在,左左型、左右型举栗——

染色循环型:插入结点的叔叔结点存在且为红色,左左型、左右型举栗{太爷爷结点有可能是黑色,也有可能是红色,此处按照红色假设}——

 于是我们就有了这串代码~

    void InsertFixup(RBNode* node) {
        while (node != root && node->parent->color == RED) {    //满足{当前结点不是根结点,且父节点为红色}时,开始以下循环
            if (node->parent == node->parent->parent->left) {   //满足{当前父结点在爷爷结点的左侧}时,执行语句
                RBNode* uncle = node->parent->parent->right;    //定义叔叔结点为父结点的右兄弟

                if (uncle != nullptr && uncle->color == RED) {  //满足{叔叔结点不为空,且叔叔结点是红色时}{染色循环型}
                    node->parent->color = BLACK;          //父亲结点涂黑
                    uncle->color = BLACK;                 //叔叔结点涂黑
                    node->parent->parent->color = RED;    //爷爷结点涂红
                    node = node->parent->parent;          //将爷爷赋值为当前结点
                } else {                                  //满足{叔叔结点不是红色时}
                    if (node == node->parent->right) {    //如果当前结点在父结点的右子树{左右型}
                        node = node->parent;              //将当前结点指针移动到父结点位置
                        LeftRotate(node);                 //当前结点左旋{调整到左左型}
                    }

                    node->parent->color = BLACK;          //父亲结点涂黑
                    node->parent->parent->color = RED;    //爷爷结点涂红
                    RightRotate(node->parent->parent);    //爷爷结点右旋
                }
            } else {                                             //满足{当前父结点在爷爷结点的右侧}时,执行语句
                RBNode* uncle = node->parent->parent->left;      //定义叔叔结点为父结点的左兄弟

                if (uncle != nullptr && uncle->color == RED) {   //满足{叔叔结点不为空,且叔叔结点是红色时}{染色循环型}
                    node->parent->color = BLACK;          //父亲结点涂黑
                    uncle->color = BLACK;                 //叔叔结点涂黑
                    node->parent->parent->color = RED;    //爷爷结点涂红
                    node = node->parent->parent;          //将爷爷赋值为当前结点
                } else {                                  //满足{叔叔结点不是红色时}
                    if (node == node->parent->left) {     //如果当前结点在父结点的左子树{右左型}
                        node = node->parent;              //将当前结点指针移动到父结点位置
                        RightRotate(node);                //当前结点右旋{调整到右右型}
                    }

                    node->parent->color = BLACK;          //父亲结点涂黑
                    node->parent->parent->color = RED;    //爷爷结点涂红
                    LeftRotate(node->parent->parent);     //爷爷结点右旋
                }
            }
        }

        root->color = BLACK;    //根结点涂黑
    }

 🔯P4:插入结点

采用循环的方式创建树~

  • 查询插入结点的位置;
  • 插入结点,通常为叶节点;
  • 插入完成后,调用调整树形的函数。
    void Insert(int key) {    //传入数据key
        RBNode* newNode = new RBNode(key, RED, nullptr, nullptr, nullptr);    //创建新的红色待插入结点{数据为key}
        RBNode* parent = nullptr;    //创建parent指针
        RBNode* current = root;      //创建current指针

        while (current != nullptr) {    //{当前结点不为空}时执行循环
            parent = current;           //将当前结点的父节点指针指向当前结点本身

            if (key < current->data) {    //key < 当前数据
                current = current->left;  //插入当前结点的左侧
            } else {                      //key > 当前数据
                current = current->right; //插入当前结点的右侧
            }
        }    //执行完循环后,可找到准备插入的位置

        newNode->parent = parent;    //将newnode的父指针指向parent

        if (parent == nullptr) {     //parent为空
            root = newNode;          //插入到根结点
        } else if (key < parent->data) {    //key < 当前数据
            parent->left = newNode;         //parent左指针指向newnode
        } else {                            //key > 当前数据
            parent->right = newNode;        //parent右指针指向newnode
        }

        InsertFixup(newNode);        //插入完成后,调用调整树形的函数
    }

 🔯P0-P4附录:构造二叉树

代码在main中写入,实际操作就是把一个数组的内的元素分别执行插入结点的操作~

    RedBlackTree tree;

    // 插入节点
    std::vector<int> values = {15, 3, 7, 10, 9, 8};
    for (int value : values) {
        tree.Insert(value);
    }

具体的创建过程嗯...如果我没有理解错,大概是下图这样的~

检查一下,应该是满足了红黑树的4个特性:左根右、根叶黑、不红红、黑路同~

这棵树的输入与平衡树的案例输入是一致的,有兴趣的同学可以对比一下创建树的结果——

 🌸平衡二叉排序树(AVL):数据结构07:查找[C++][平衡二叉排序树AVL] 

 🔯P5:寻找最小结点

利用二叉排序树 “左子树<根结点<右子树” 的性质,采用递归方式一直向左寻找,就可以找到最小值结点~

    RBNode* Minimum(RBNode* node) {
        while (node->left != nullptr) {    //满足{结点左侧不为空}时
            node = node->left;             //向左查询
        }
        return node;    //返回最小结点
    }

 🔯P6:结点替换

在红黑树中,结点替换是指将一个节点及其子树替换为另一个结点及其子树,保持树的平衡性和性质不变,这个是删除的基本操作~

    void Transplant(RBNode* u, RBNode* v) {    //结点u:移植的源结点;结点v:移植的目标结点
        if (u->parent == nullptr) {    //满足{结点u的父结点为空}时
            root = v;                  //根结点指针root指向结点v
        } else if (u == u->parent->left) {     //满足{结点u在父结点的左子树}时
            u->parent->left = v;               //将父结点的左指针指向结点v
        } else {                       //满足{结点u在父结点的右子树}时
            u->parent->right = v;      //将父结点的右指针指向结点v
        }

        if (v != nullptr) {            //满足{结点v不为空}时
            v->parent = u->parent;     //将结点v的父指针指向结点u的父指针
        }
    }

 🔯P7:删除修复函数

与插入结点容易破坏“不红红”{红色结点相邻性质}相对,删除结点容易破坏“黑路同”{到叶结点简单路径上黑色结点数目一致}的性质~

尤其是在删除黑色结点之后,红黑树可能无法保证黑平衡特性,树的调整也可能分为以下类型 {删除操作我其实没有很理解,因此以下的总结可能有些问题} :

  • 李代桃僵型:删除的结点x是黑色,且结点x的兄弟结点是红色:
    • 状况1:交换结点x的父结点结点x的兄弟结点的颜色,并进行左旋操作,此时结点x的兄弟结点成为局部根结点,红黑树的性质不变,并且可以按照下面的情况处理~
  • 环环相扣型:删除的结点x是黑色,且结点x的兄弟结点是黑色:
    • 状况2:结点x的兄弟结点的左孩子结点结点x的兄弟结点的右孩子结点是黑色{这...这是左侄子结点和右侄子结点?},将结点x的兄弟结点染成红色;
    • 状况3:结点x的兄弟结点的右孩子结点是黑色,则将结点x的兄弟结点的左孩子结点染成黑色,将结点x的兄弟结点染成红色,然后右旋;
    • 状况4:将结点x的兄弟结点设置为父节点的颜色,将父节点的颜色染成黑色,结点x的兄弟结点的右孩子结点染成黑色,然后父结点进行左旋操作,移到根结点。

备注:此处觉得莫名其妙没有关系,可以看完删除函数的操作后再返回来看这个删除修复函数~ 

 

    void DeleteFixup(RBNode* node) {
        while (node != root && node->color == BLACK) {    //满足{结点不为根结点 且 结点颜色为黑色}时,执行循环
            if (node == node->parent->left) {             //若满足{结点在父结点的左子树}
                RBNode* sibling = node->parent->right;    //设置结点的右兄弟结点

                if (sibling->color == RED) {        //若满足{右兄弟是红色}
                    sibling->color = BLACK;         //兄弟结点涂黑
                    node->parent->color = RED;      //父亲结点涂红
                    LeftRotate(node->parent);       //父亲结点左旋
                    sibling = node->parent->right;  //重新定义兄弟结点
                }

                if (sibling->left->color == BLACK && sibling->right->color == BLACK) {    //若满足{兄弟结点的左右孩子都是黑色}
                    sibling->color = RED;    //兄弟结点涂红
                    node = node->parent;     //重新定义当前结点
                } else {
                    if (sibling->right->color == BLACK) {    //若满足{兄弟结点的右孩子是黑色,但兄弟结点的左孩子不是黑色}
                        sibling->left->color = BLACK;    //兄弟的左孩子结点涂黑
                        sibling->color = RED;            //兄弟结点涂红
                        RightRotate(sibling);            //兄弟结点右旋
                        sibling = node->parent->right;
                    }

                    sibling->color = node->parent->color;    //兄弟结点染为父结点颜色
                    node->parent->color = BLACK;             //父结点染黑
                    sibling->right->color = BLACK;           //兄弟结点右孩子染黑
                    LeftRotate(node->parent);                //父结点进行左旋操作
                    node = root;                             //将局部根结点设为当前结点
                }
            } else {             //若满足{结点在父结点的右子树},以下执行左子树的镜像操作
                RBNode* sibling = node->parent->left;

                if (sibling->color == RED) {
                    sibling->color = BLACK;
                    node->parent->color = RED;
                    RightRotate(node->parent);
                    sibling = node->parent->left;
                }

                if (sibling->right->color == BLACK && sibling->left->color == BLACK) {
                    sibling->color = RED;
                    node = node->parent;
                } else {
                    if (sibling->left->color == BLACK) {
                        sibling->right->color = BLACK;
                        sibling->color = RED;
                        LeftRotate(sibling);
                        sibling = node->parent->left;
                    }

                    sibling->color = node->parent->color;
                    node->parent->color = BLACK;
                    sibling->left->color = BLACK;
                    RightRotate(node->parent);
                    node = root;
                }
            }
        }

        node->color = BLACK;    //结点的当前颜色涂黑
    }

 🔯P8:结点删除 [代码与注释可能有误]

删除的情况,可分为以下4种情况考虑——

  • 删除结点的基本操作:同朴素平衡二叉树BST的删除🌸[朴素二叉排序树BST];然后,在删除结点以后,树的结构可能违反了红黑树的性质,因此我们需要按照结点颜色进行调整~
  • 删除的结点是叶子结点:
    • 结点x是红色:不会破坏红黑树性质,因此可以直接删除,不需要调整~
    • 结点x是黑色:会破坏红黑树的性质,调用删除修复函数(DeleteFixup)调整树形和颜色~
  • 删除的结点是具有单孩子的分支结点:
    • 结点x是红色:这种情况不会出现,因为不满足红黑树”黑路同“的性质;
    • 结点x是黑色:根据红黑树的性质,该结点x的孩子结点必为红色且只有1个,调用结点替换函数(Transplant)结点x的孩子结点的值替换掉结点z的值,并删除子结点,,后序操作转化为删除红色叶子结点的操作~
  • 删除的结点是具有双孩子的分支结点:
    • 结点x是红色:寻找前驱或后继结点,并通过调用结点替换函数(Transplant)交换结点x前驱、后继结点的值,后序操作转化为删除叶子结点的操作~
    • 结点x是黑色:寻找前驱或后继结点,并通过调用结点替换函数(Transplant)交换结点x前驱、后继结点的值,后序操作转化为删除叶子结点的操作~

 删除的结点是叶子结点:

 删除的结点是单孩子结点:

 删除的结点是红色双孩子结点:

 删除的结点是黑色双孩子结点:

 注意:

  • 如果删除的结点原始颜色也是黑色,就需要调用删除修复函数(DeleteFixup)调整树形和颜色~
  • 我看到大佬的博文和参考书有说明双重黑节点的概念,应该是在删除黑色结点且其孩子结点也均是黑色时使用,维持稳定性,减少树形的调整~
    void Delete(int key) {
        RBNode* node = root;    //将根结点指针指向node变量

        while (node != nullptr) {            //{node不为空}时,查找结点
            if (key == node->data) {         //关键字 = 结点值
                break;                       //完成查找,跳出循环
            } else if (key < node->data) {   //关键字 < 结点值
                node = node->left;           //向左子树查询
            } else {                         //关键字 > 结点值
                node = node->right;          //向右子树查询
            }
        }

        if (node == nullptr) {               //{node找到空结点},查找失败
            std::cout << "未找到目标结点\n"; //返回,未找到目标结点
            return;
        }

        Color originalColor = node->color;   //保存目标结点的颜色
        RBNode* successor = nullptr;         //创建successor,用途为保存目标结点的后继结点
        if (node->left == nullptr) {         //若{结点的左子树为空}
            successor = node->right;         //后继结点successor结点为当前的右孩子
            Transplant(node, successor);     //交换successor与node,完成删除
        } else if (node->right == nullptr) { //若{结点的右子树为空}
            successor = node->left;          //后继结点successor结点为当前的左孩子
            Transplant(node, successor);     //交换successor与node,完成删除
        } else {                                        //若{结点具有双孩子}
            RBNode* minimum = Minimum(node->right);     //创建minimum,其值为右子树的最小值
            originalColor = minimum->color;  //保存该结点的原始颜色
            successor = minimum->right;      //minimum的右子树作为后继结点

            if (minimum->parent == node) {        //若{右子树最小结点minimum的父结点为当前结点node}
                if (successor != nullptr) {       //且,若{successor结点不为空}
                    successor->parent = minimum;  //将successor结点的父指针指向minimum结点
                }
            } else {                              //若{右子树最小结点minimum的父结点不是当前结点node}
                Transplant(minimum, successor);   //交换minimum与successor
                minimum->right = node->right;     //将node的右指针指向minimum的右指针
                minimum->right->parent = minimum; //将minimum的右孩子的父指针指向最小结点
            }

            Transplant(node, minimum);        //交换node与minimum
            minimum->left = node->left;       //minimum的左指针指向node的左指针
            minimum->left->parent = minimum;  //minimum的左孩子的父指针指向minimum
            minimum->color = node->color;     //将node的颜色赋值到minimum
        }

        if (originalColor == BLACK) {         //如果原来的结点是黑色
            DeleteFixup(successor);           //对于后继结点执行删除修复操作
        }

        delete node;                          //删除结点
    }

 🔯P9:树的遍历

传入树的根结点内存地址,由于二叉树遵循:“左<根<右” 的原则,因此可以通过二叉树的中序遍历完成,此处采用递归方式完成~

    void InOrderTraversal(RBNode* node) {
        if (node != nullptr) {                //若{未遍历到空结点},执行循环
            InOrderTraversal(node->left);     //递归遍历左子树
            std::cout << node->data << " ";   //输出当前结点的值
            InOrderTraversal(node->right);    //递归遍历右子树
        }
    }

敲黑板中序遍历这个已经写过很多次此处不再赘述了~ 🌸数据结构05:树与二叉树[C++]

 🔯P10:main函数

main函数除了P0~P9的函数调用,就创建了1个插入结点的队列,以及示意性地增加删除结点的操作~

int main() {
    RedBlackTree tree;

    // 插入结点
    std::vector<int> values = {15, 3, 7, 10, 9, 8};
    for (int value : values) {
        tree.Insert(value);
    }

    // 输出结点
    std::cout << "输出结点: ";
    tree.InOrderTraversal();

    // 删除结点
    int target = 7;
    tree.Delete(target);

    // 输出结点
    std::cout << "输出结点: ";
    tree.InOrderTraversal();

    return 0;
}

🧵完整代码

 🔯P0:完整代码

按照惯例,为了凑本文的字数,我这里贴一下整体的代码,删掉了细部注释~🫥🫥

//头文件
#include <iostream>
#include <vector>

//定义结点颜色
enum Color {
    RED,
    BLACK
};

//定义结点结构
struct RBNode {
    int data;
    Color color;
    RBNode* parent;
    RBNode* left;
    RBNode* right;

    RBNode(int val, Color c, RBNode* p, RBNode* l, RBNode* r)
        : data(val), color(c), parent(p), left(l), right(r) {}
};

//类:红黑树
class RedBlackTree {
private:
    RBNode* root;

    //结点左旋
    void LeftRotate(RBNode* node) {
        RBNode* rightChild = node->right;
        node->right = rightChild->left;

        if (rightChild->left != nullptr) {
            rightChild->left->parent = node;
        }

        rightChild->parent = node->parent;

        if (node->parent == nullptr) {
            root = rightChild;
        } else if (node == node->parent->left) {
            node->parent->left = rightChild;
        } else {
            node->parent->right = rightChild;
        }

        rightChild->left = node;
        node->parent = rightChild;
    }

    //结点右旋
    void RightRotate(RBNode* node) {
        RBNode* leftChild = node->left;
        node->left = leftChild->right;

        if (leftChild->right != nullptr) {
            leftChild->right->parent = node;
        }

        leftChild->parent = node->parent;

        if (node->parent == nullptr) {
            root = leftChild;
        } else if (node == node->parent->left) {
            node->parent->left = leftChild;
        } else {
            node->parent->right = leftChild;
        }

        leftChild->right = node;
        node->parent = leftChild;
    }

    //插入修复操作
    void InsertFixup(RBNode* node) {
        while (node != root && node->parent->color == RED) {
            if (node->parent == node->parent->parent->left) {
                RBNode* uncle = node->parent->parent->right;

                if (uncle != nullptr && uncle->color == RED) {
                    node->parent->color = BLACK;
                    uncle->color = BLACK;
                    node->parent->parent->color = RED;
                    node = node->parent->parent;
                } else {
                    if (node == node->parent->right) {
                        node = node->parent;
                        LeftRotate(node);
                    }

                    node->parent->color = BLACK;
                    node->parent->parent->color = RED;
                    RightRotate(node->parent->parent);
                }
            } else {
                RBNode* uncle = node->parent->parent->left;

                if (uncle != nullptr && uncle->color == RED) {
                    node->parent->color = BLACK;
                    uncle->color = BLACK;
                    node->parent->parent->color = RED;
                    node = node->parent->parent;
                } else {
                    if (node == node->parent->left) {
                        node = node->parent;
                        RightRotate(node);
                    }

                    node->parent->color = BLACK;
                    node->parent->parent->color = RED;
                    LeftRotate(node->parent->parent);
                }
            }
        }

        root->color = BLACK;
    }

    //寻找最小结点
    RBNode* Minimum(RBNode* node) {
        while (node->left != nullptr) {
            node = node->left;
        }
        return node;
    }

    //替换结点
    void Transplant(RBNode* u, RBNode* v) {
        if (u->parent == nullptr) {
            root = v;
        } else if (u == u->parent->left) {
            u->parent->left = v;
        } else {
            u->parent->right = v;
        }

        if (v != nullptr) {
            v->parent = u->parent;
        }
    }
    
    //删除修正操作
    void DeleteFixup(RBNode* node) {
        while (node != root && node->color == BLACK) {
            if (node == node->parent->left) {
                RBNode* sibling = node->parent->right;

                if (sibling->color == RED) {
                    sibling->color = BLACK;
                    node->parent->color = RED;
                    LeftRotate(node->parent);
                    sibling = node->parent->right;
                }

                if (sibling->left->color == BLACK && sibling->right->color == BLACK) {
                    sibling->color = RED;
                    node = node->parent;
                } else {
                    if (sibling->right->color == BLACK) {
                        sibling->left->color = BLACK;
                        sibling->color = RED;
                        RightRotate(sibling);
                        sibling = node->parent->right;
                    }

                    sibling->color = node->parent->color;
                    node->parent->color = BLACK;
                    sibling->right->color = BLACK;
                    LeftRotate(node->parent);
                    node = root;
                }
            } else {
                RBNode* sibling = node->parent->left;

                if (sibling->color == RED) {
                    sibling->color = BLACK;
                    node->parent->color = RED;
                    RightRotate(node->parent);
                    sibling = node->parent->left;
                }

                if (sibling->right->color == BLACK && sibling->left->color == BLACK) {
                    sibling->color = RED;
                    node = node->parent;
                } else {
                    if (sibling->left->color == BLACK) {
                        sibling->right->color = BLACK;
                        sibling->color = RED;
                        LeftRotate(sibling);
                        sibling = node->parent->left;
                    }

                    sibling->color = node->parent->color;
                    node->parent->color = BLACK;
                    sibling->left->color = BLACK;
                    RightRotate(node->parent);
                    node = root;
                }
            }
        }

        node->color = BLACK;
    }

    //执行中序遍历
    void InOrderTraversal(RBNode* node) {
        if (node != nullptr) {
            InOrderTraversal(node->left);
            std::cout << node->data << " ";
            InOrderTraversal(node->right);
        }
    }

//公共类
public:
    RedBlackTree() : root(nullptr) {}

    //插入结点
    void Insert(int key) {
        RBNode* newNode = new RBNode(key, RED, nullptr, nullptr, nullptr);
        RBNode* parent = nullptr;
        RBNode* current = root;

        while (current != nullptr) {
            parent = current;

            if (key < current->data) {
                current = current->left;
            } else {
                current = current->right;
            }
        }

        newNode->parent = parent;

        if (parent == nullptr) {
            root = newNode;
        } else if (key < parent->data) {
            parent->left = newNode;
        } else {
            parent->right = newNode;
        }

        InsertFixup(newNode);
    }

    //删除结点
    void Delete(int key) {
        RBNode* node = root;

        while (node != nullptr) {
            if (key == node->data) {
                break;
            } else if (key < node->data) {
                node = node->left;
            } else {
                node = node->right;
            }
        }

        if (node == nullptr) {
            std::cout << "未找到目标结点\n";
            return;
        }

        Color originalColor = node->color;
        RBNode* successor = nullptr;

        if (node->left == nullptr) {
            successor = node->right;
            Transplant(node, successor);
        } else if (node->right == nullptr) {
            successor = node->left;
            Transplant(node, successor);
        } else {
            RBNode* minimum = Minimum(node->right);
            originalColor = minimum->color;
            successor = minimum->right;

            if (minimum->parent == node) {
                if (successor != nullptr) {
                    successor->parent = minimum;
                }
            } else {
                Transplant(minimum, successor);
                minimum->right = node->right;
                minimum->right->parent = minimum;
            }

            Transplant(node, minimum);
            minimum->left = node->left;
            minimum->left->parent = minimum;
            minimum->color = node->color;
        }

        if (originalColor == BLACK) {
            DeleteFixup(successor);
        }

        delete node;
    }
    
    //传递中序遍历根结点
    void InOrderTraversal() {
        InOrderTraversal(root);
        std::cout << "\n";
    }
};

int main() {
    RedBlackTree tree;

    std::vector<int> values = {15, 3, 7, 10, 9, 8};
    for (int value : values) {
        tree.Insert(value);
    }

    std::cout << "输出结点: ";
    tree.InOrderTraversal();

    int target = 7;
    tree.Delete(target);

    std::cout << "输出结点: ";
    tree.InOrderTraversal();

    return 0;
}

 🔯P1:执行结果

运行结果如下图所示~


🔚结语

博文到此结束,写得模糊或者有误之处,欢迎小伙伴留言讨论与批评,督促博主优化内容,不限于以下内容~😶‍🌫️😶‍🌫️

  • 有错误:这段注释南辕北辙,理解错误,需要更改~对了,这里介绍两个大佬的博文讲得还挺详细的,可以配合查看~
    • 图解:红黑树删除篇(一文读懂) - 知乎 (zhihu.com)
    • 理解红黑树并实现(python3)_liu_coding的博客-CSDN博客
  • 难理解:这段代码雾里看花,需要更换排版、增加语法、逻辑注释或配图~
  • 不简洁:这段代码瘠义肥辞,好像一座尸米山,需要更改逻辑;如果是C++语言,调用某库某语法还可以简化~
  • 缺功能:这段代码败絮其中,能跑,然而不能用,想在实际运行或者通过考试需要增加功能~
  • 跑不动:这个自己说明一下~简单红黑树的创建、遍历、删除黑色结点应该可以正常完成,删除红色结点代码经过测试确实跑不动,可能的原因:
    • 没有很好地解决空指针赋值的问题{空指针应该默认为黑色,这里尝试修复失败了}~
    • 删除红色结点的逻辑可能也有问题{如果仅采用后继结点替补当前的红色结点,可能会导致删除操作后树不满足性质;可能需要增加删除前驱、后继结点的判断}~
    • 另外,此代码未涉及双重黑色结点的介绍,如果使用应该可以继续降低树形的调整幅度~

也可以看看博主列表里的其它博文呀,说不定会有感兴趣的内容哦~😶‍🌫️😶‍🌫️

博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下~🌟🌟


备注:啊,虽然写得不怎么样,不过我至少有很勉强地完成任务了~话说这个截图还是我在数据结构系列博文里连续肝了5篇博文后的第1个留言 🌸数据结构05:树与二叉树[C++]~哎,虽然Ada小助手大概不记得了,不过还是截图留念一下~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/705494.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【线程池】线程池的ctl属性详解

目录 一、ctl介绍 二、线程池ctl源码 三、线程池ctl分析 1、private static int ctlOf(int rs, int wc) { return rs | wc; } 2、private final AtomicInteger ctl new AtomicInteger(ctlOf(RUNNING, 0)); 3、private static int runStateOf(int c) { return c &am…

TensorFlow框架

TensorFlow框架 本文目录&#xff1a; 一、通过代码了解TensorFlow结构 1.1、TensorFlow实现一个加法运算代码 1.1.1、原生python加法运算 1.1.2、TensorFlow实现加法运算 1.1.3、TensorFlow实现加法运算 1.2、TensorFlow的Hello World 二、TensorFlow架构图 三、Tenso…

WebRTC Docker容器部署方案

文章目录 WebRTC简介WebRTC Docker容器部署优势方案&#xff08;mpromonet/webrtc-streamer&#xff09;步骤 WebRTC简介 WebRTC&#xff08;Web Real-Time Communication&#xff09;是一种开放的实时通信技术&#xff0c;它允许浏览器之间进行音频、视频和数据的实时传输。W…

第九十二天学习记录:C++核心:类和对象Ⅰ(五星重要)

C面向对象的三大特性为&#xff1a;封装、继承、多态 C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为 封装 封装的意义 封装是C面向对象三大特性之一 封装的意义&#xff1a; 1、将属性和行为作为一个整体&#xff0c;表现生活中的事物 2、将属性和行为加以权限…

js判断文件类型详解

js判断文件类型详解 通过file的type属性判断 <input type"file" onchange"onchangecb(this)" /> <script> function onchangecb(e) {const file e.files[0];console.log(file.type); } </script>像html中input标签&#xff0c;就是根…

地下水数值模拟-Visual modflow Flex软件应用

地下水&#xff08;ground water&#xff09;&#xff0c;是指赋存于地面以下岩石空隙中的水&#xff0c;狭义上是指地下水面以下饱和含水层中的水。在国家标准《水文地质术语》&#xff08;GB/T 14157-93&#xff09;中&#xff0c;地下水是指埋藏在地表以下各种形式的重力水。…

基于协议判断目标机器是否出网

内网渗透中时常会碰到一些不出网的主机&#xff0c;不出网的原因有很多&#xff0c;如常见的有&#xff1a;没有设置网关、系统防火墙或者其他设备设置了出入站限制&#xff0c;只允许特定协议或端口出网等&#xff0c;遇到这种情况时可以用以下命令测试目标主机允许哪些协议出…

桥接模式的学习与使用

1、桥接模式的学习 当你需要将抽象部分与实现部分解耦&#xff0c;使它们可以独立地变化&#xff0c;而又能够灵活地组合在一起时&#xff0c;可以使用桥接模式。桥接模式通过将抽象和实现部分分离&#xff0c;使它们可以独立地进行扩展和变化&#xff0c;同时又能够在运行时动…

代码随想录二战day2

977有序数组的平方 力扣 思路&#xff1a; 第一&#xff1a; 和之前一样的&#xff0c;看见数组我们的第一想法就是使用双指针去解。这道题需要额外开辟一个答案数组去储存结果。 第二&#xff1a; 头指针指向第一个元素&#xff0c;尾指针指向最后一个元素。然后对两个元素分…

RocketMQ安装(Docker)

一、RocketMQ安装之docker 1.下载RockerMQ需要的镜像 docker pull rocketmqinc/rocketmq docker pull styletang/rocketmq-console-ng 2.启动NameServer服务 创建NameServer数据存储路径 mkdir -p /home/rocketmq/data/namesrv/logs /home/rocketmq/data/namesrv/store启动…

拾起王慧文的AI梦,美团冲向“光年之外”?

“十年&#xff0c;我需要休息休息&#xff0c;下一个十年&#xff0c;就托付给兄弟们了&#xff0c;感谢你们。” 2020年底&#xff0c;王慧文在朋友圈写下这句话时&#xff0c;外界本以为这位伴随中国互联网发展而持续创业20年的人物即将告别创业舞台。但是&#xff0c;一个…

Kubernetes创建集群—使用 Minikube 创建集群

一、使用 Minikube 创建集群 1、Kubernetes 集群 Kubernetes 协调一个高可用计算机集群&#xff0c;每个计算机作为独立单元互相连接工作。 Kubernetes 中的抽象允许你将容器化的应用部署到集群&#xff0c;而无需将它们绑定到某个特定的独立计算机。为了使用这种新的部署模型…

keepalived安装与使用(Nginx高可用)

一、Keepalived 简介&#x1f349; 1.什么是Keepalived &#xff1f;&#x1f95d; Keepalived一个基于VRRP 协议来实现的 LVS 服务高可用方案&#xff0c;可以利用其来解决单点故障。一个LVS服务会有2台服务器运行Keepalived&#xff0c;一台为主服务器&#xff08;MASTER&a…

Learn Mongodb DB功能命令索引等搜索 ⑤

作者 : SYFStrive 博客首页 : HomePage &#x1f4dc;&#xff1a; PHP MYSQL &#x1f4cc;&#xff1a;个人社区&#xff08;欢迎大佬们加入&#xff09; &#x1f449;&#xff1a;社区链接&#x1f517; &#x1f4cc;&#xff1a;觉得文章不错可以点点关注 &#x1f44…

layui框架学习(31:下拉菜单模块)

Layui的下拉菜单组件模块dropdown支持动态构建下拉菜单及右键菜单&#xff0c;不同于之前学习的页面元素中的菜单&#xff0c;后者主要是在页面中搭建菜单结构&#xff0c;然后通过设置layui提供的菜单相关的预设类对菜单结构进行样式渲染&#xff0c;而通过dropdown模块则是基…

【单片机】STM32单片机,RTC实时时钟,STM32F103C8T6,程序,万年历,数字时钟

文章目录 基础介绍rtc.hrtc.cmain.c 基础介绍 我以STM32F103C8T6为例&#xff0c;但STM32F103的RTC是通用的&#xff0c;STM32F103C8T6有一个原理图&#xff1a; https://qq742971636.blog.csdn.net/article/details/131288390 用纽扣电池给VBAT供电&#xff08;要共地&…

基于matlab使用多类掩码区域的卷积神经网络对人和汽车的各个实例进行分段(附源码)

一、前言 此示例展示了如何使用基于多类掩码区域的卷积神经网络 &#xff08;R-CNN&#xff09; 对人和汽车的各个实例进行分段。实例分割是一种计算机视觉技术&#xff0c;您可以在其中检测和定位对象&#xff0c;同时为每个检测到的实例生成分割图。 此示例首先演示如何使用…

shardingsphere-proxy 实现postgresql的单库分表

1、docker 安装zookeeper 1、拉取镜像 docker pull zookeeper2、运行容器 docker run -d -e TZ"Asia/Shanghai" -p 2181:2181 -v /home/sunyuhua/docker/zookeeper:/data --name zookeeper --restart always zookeeper3、查看容器是不是运行成功 docker exec -i…

Spring Boot 中的 @SendTo 注解

Spring Boot 中的 SendTo 注解 在 Spring Boot 中&#xff0c;SendTo 注解是一个非常有用的注解&#xff0c;它可以用于实现 WebSocket 的消息转发功能。本文将介绍 SendTo 注解的原理、使用方法和示例代码。 什么是 SendTo 注解 SendTo 注解是 Spring Boot 中用于将消息发送…

个人和企业如何有效保护IP地址?

随着互联网的快速发展&#xff0c;个人和企业对于保护IP地址的重要性越来越清晰。为了帮助读者更好地了解如何有效保护IP地址&#xff0c;以下将介绍几种方法&#xff0c;并提供一些相关的数据和专家意见。 使用防火墙是保护IP地址的一个重要手段。防火墙可以监控和过滤网络流量…