简易STL实现 | 红黑树的实现

news2024/9/29 18:09:47

1、原理

红黑树(Red-Black Tree)是一种自平衡的二叉搜索树
红黑树具有以下特性,这些特性保持了树的平衡:

  • 节点颜色: 每个节点要么是红色,要么是黑色
  • 根节点颜色: 根节点是黑色的。
  • 叶子节点(NIL 节点)颜色: 所有叶子节点(NIL 节点)都是黑色的
  • 相邻节点颜色: 如果一个节点是红色的,则它的两个子节点都是黑色的
  • 路径黑高度相等: 从任意节点到其每个叶子节点的简单路径上,黑色节点的数量相同

在最坏情况下的查找、插入和删除操作的时间复杂度都是 O(log n)

1.1 节点结构

键值(Key): 保存实际数据的值,用于比较和排序
颜色信息: 用于红黑树的平衡,标记节点的颜色
左子节点和右子节点指针: 指向左右子节点的指针
父节点指针: 指向父节点的指针

1.2 插入操作

定位插入位置: 从根节点开始,按照二叉查找树的规则,找到合适的插入位置
插入新节点: 在插入位置创建一个新节点,并将其颜色设为红色
修复红黑树性质: 如果新节点的父节点是红色,需要通过一系列旋转 和 重新着色操作来保持红黑树的平衡

1.3 删除操作

标记节点: 将要删除的节点标记为“被删除”,而不是立即删除它
删除节点: 根据情况删除节点,并用子节点替代它的位置
修复红黑树性质: 如果删除的节点是黑色 或 替代节点是红色,或者删除的节点是根节点,可能需要通过一系列旋转和重新着色操作 来保持红黑树的平衡

2、代码实现

#include <iostream>
#include <sstream>
#include <string>

enum class Color { Black, Red }; // 最后加分号

template <typename Key, typename Value> // 两个typename
class RedBlackTree {
    class Node {
    public:
        Color color;
        Key key; // 比较和排序的依据
        Value value;
        Node* left;
        Node* right;
        Node* parent;

        Node()
            :color(Color::Black), left(nullptr), right(nullptr), parent(nullptr) {
            // 只初始化了部分变量
        }
        // 注意使用Color中定义类型的办法
        // 如果枚举类型的值较小,直接传递值的开销非常低(不涉及昂贵的拷贝操作)
        // 对于像 int 或 enum 这样的小型数据类型,直接传值的成本很低,甚至比传递引用更有效(引用本质上是一个隐式指针)。因为传递引用(或指针)涉及额外的间接访问,可能引发更多的内存访问操作或指针解引用的开销
        Node(const Key& k, const Value& v, Color c, Node* p = nullptr)
            : key(k), value(v), color(c), left(nullptr), right(nullptr), parent(p) {
        }
    };

    Node* root;
    size_t size;
    Node* Nil;

    // 查询结点(通过key)
    Node* lookUp(const Key& key) {
        Node* currNode = root;
        while (currNode) {
            if (currNode->key == key) {
                return currNode;
            }
            else if (currNode->key > key) {
                currNode = currNode->left;
            }
            else {
                currNode = currNode->right;
            }
        }
        return currNode;
    }

    // 左旋,旋转就是3个过程
    void leftRotate(Node* cur) {
        Node* rSon = cur->right;
        // if (rSon != nullptr) rSon / cur 为nullptr转不了了
        rSon->parent = cur->parent;
        if (!cur->parent) { // 注意先判断是否为空,再使用指针
            root = rSon;
        }
        else if (cur->parent->left == cur) {
            cur->parent->left = rSon;
        }
        else if (cur->parent->right == cur) {
            cur->parent->right == rSon;
        }
        cur->parent = rSon;
        cur->right = rSon->left;
        if (rSon->left)
            rSon->left->parent = cur;
        rSon->left = cur;
    }

    // 右旋
    void rightRotate(Node* cur) {
        Node* lSon = cur->left;
        if (!cur->parent) {
            root = lSon;
        }
        else if (cur->parent->left == cur) {
            cur->parent->left = lSon;
        }
        else if (cur->parent->right == cur) {
            cur->parent->right = lSon;
        }
        lSon->parent = cur->parent;
        cur->parent = lSon;
        cur->left = lSon->right;
        if (lSon->right) {
            lSon->right->parent = cur;
        }
        lSon->right = cur;
    }

    // 插入修正
    void insertFix(Node* cur) {
        if (cur->parent && cur->parent->color == Color::Red) {
            // 只有父结点是红色才需要调整
            if (cur->parent->parent && cur->parent->parent->left == cur->parent) {// 1.1 & 1.2
                if (cur->parent->parent->right->color == Color::Red) {// 1.1
                    cur->parent->color = Color::Black;
                    cur->parent->parent->right->color = Color::Black;
                    cur->parent->parent->color = Color::Red;
                    insertFix(cur->parent->parent);
                }
                // 1.2
                else if (cur->parent->parent->right == nullptr || cur->parent->parent->right->color == Color::Black) {
                    if (cur->parent->right == cur) {
                        leftRotate(cur->parent);
                    }
                    cur->parent->color = Color::Black;
                    cur->parent->parent->color = Color::Red;
                    rightRotate(cur->parent->parent);
                }
            }
            else if (cur->parent->parent && cur->parent->parent->right == cur->parent) {
                // 2.1 && 2.2 
                if (cur->parent->parent->left->color == Color::Red) {// 2.1
                    cur->parent->color = Color::Black;
                    cur->parent->parent->left->color = Color::Black;
                    cur->parent->parent->color = Color::Red;
                    insertFix(cur->parent->parent);
                }
                // 2.2
                else if (cur->parent->parent->left == nullptr || cur->parent->parent->left->color == Color::Black) {
                    if (cur->parent->right == cur) {
                        leftRotate(cur->parent);
                    }
                    cur->parent->color = Color::Black;
                    cur->parent->parent->color = Color::Red;
                    leftRotate(cur->parent->parent);
                }
            }
        }
        root->color = Color::Black; // 确保根节点一定是黑色的
    }

    // 插入节点
    void insertNode(const Key& key, const Value& value) {
        Node* tar = new Node(key, value, Color::Red);
        Node* cur = root;
        if (cur == nullptr) { // 加入的第一个节点
            root = tar;
        }
        Node* par = nullptr;
        while (cur) {
            par = cur;
            if (cur->key > tar->key)
                cur = cur->left;
            else if (cur->key < tar->key)
                cur = cur->right;
            else {
                // 最后相等的时候,不插入了
                delete tar;
                return;
            }
        }
        size++;
        if (par) {
            if (par->key > key) {
                par->left = tar;
                tar->parent = par;
            }
            else {
                par->right = tar;
                tar->parent = par;
            }
        }
        insertFix(tar);
    }

    // 中序遍历
    void inorderTraverse(Node* cur) {
        if (cur) {
            inorderTraverse(cur->left);
            std::cout << cur->key << " ";
            std::cout << cur->value << " ";
            inorderTraverse(cur->right);
        }
    }

    // 下面进行删除相关的操作,删除需要用到 Nil哨兵

    // 辅助函数 是为了处理删除中特有的遇到空节点情况
    // 辅助函数,获取颜色,使空指针变为黑色
    Color getColor(Node* cur) {
        if (cur == nullptr) {
            return Color::Black;
        }
        else
            return cur->color;
    }

    // 辅助函数,设置颜色
    void setColor(Node* cur, Color col) {
        if (cur == nullptr) {
            return;
        }
        cur->color = col;
    }

    // 辅助函数,断开与哨兵的连接
    void disconnectNil() {
        if (Nil == nullptr) {
            return;
        }
        if (Nil->parent != nullptr) {
            if (Nil->parent->left == Nil) {
                Nil->parent->left = nullptr;
            }
            else {
                Nil->parent->right = nullptr;
            }
        }
    }

    // 辅助函数,用新结点替换旧结点
    void replaceNode(Node* oldNode, Node* newNode) {
        if (!oldNode->parent) {
            root = newNode;
        }
        else if (oldNode->parent->left == oldNode) {
            oldNode->parent->left = newNode;
            newNode->parent = oldNode->parent;
        }
        else {
            oldNode->parent->right = newNode;
            newNode->parent = oldNode->parent;
        }
        // 不考虑孩子节点,因为后面处理不同
    }

    // 辅助函数,寻找以某个节点为根结点的子树中的最小结点
    Node* findMin(Node* cur) {
        while (cur->left) {
            cur = cur->left;
        }
        return cur;
    }

    // 删除修正,需要哨兵Nil
    // 删除修正四种情况的核心是:兄弟结点的子节点有没有红色结点(有红节点转到父结点的位置上就OK了,如果到根结点还是没有也OK了)?之后是兄弟节点有没有红色节点 /  兄弟结点子节点的红色节点在哪
    void removeFix(Node* cur) { // cur就是x
        while (cur != root && getColor(cur) == Color::Black) // 一旦碰到红的就结束了
        {
            // 先处理图中所示的左边的情况,右边是对称的
            if (cur->parent->left == cur) {
                if (getColor(cur->parent->right) == Color::Red) { // (a)转(b)/(c)/(d)
                    setColor(cur->parent, Color::Red);
                    setColor(cur->parent->right, Color::Black);
                    leftRotate(cur->parent);
                }
                else if (getColor(cur->parent->right->left) == Color::Black && getColor(cur->parent->right->right) == Color::Black) {
                    setColor(cur->parent->right, Color::Red);
                    cur = cur->parent;
                }
                else if (getColor(cur->parent->right->left) == Color::Red && getColor(cur->parent->right->right) == Color::Black) {
                    setColor(cur->parent->right, Color::Red);
                    setColor(cur->parent->right->left, Color::Black);
                    rightRotate(cur->parent->right);
                }
                else {
                    setColor(cur->parent->right, getColor(cur->parent));
                    setColor(cur->parent, Color::Black);
                    setColor(cur->parent->right->right, Color::Black);
                    leftRotate(cur->parent);
                }
            }
            else {
                Node* bro = cur->parent->left;
                if (getColor(bro) == Color::Red) { // (a)转(b)/(c)/(d)
                    setColor(cur->parent, Color::Red);
                    setColor(bro, Color::Black);
                    rightRotate(cur->parent);
                }
                else if (getColor(bro->left) == Color::Black && getColor(bro->right) == Color::Black) {
                    setColor(bro, Color::Red);
                    cur = cur->parent;
                }
                else if (getColor(bro->left) == Color::Red && getColor(bro->right) == Color::Black) {
                    setColor(bro, Color::Red);
                    setColor(bro->left, Color::Black);
                    rightRotate(bro);
                }
                else {
                    setColor(bro, getColor(cur->parent));
                    setColor(cur->parent, Color::Black);
                    setColor(bro->right, Color::Black);
                    rightRotate(cur->parent);
                }
            }
        }
        setColor(cur, Color::Black);
    }

    void deleteNode(Node* tar) {
        Color oriColor = tar->color; // 被删除的颜色(不一定就是被删除结点的颜色)
        Node* adjust = nullptr; // 调整的结点
        Node* parentRP = nullptr;
        // 1.对于删除结点同时有左右子节点的,记录替代结点代替之前位置的父结点,为插入Nil结点做准备
        // 2.对于删除结点只有一个子节点的,显然不能记录代替之前的父结点(就是被删除的那个),应该使用删除节点的父结点
        if (!tar->left) {
            adjust = tar->right;
            parentRP = tar->parent;//2
            replaceNode(tar, tar->right);
            oriColor = getColor(tar->right); // 始终记录替代节点的颜色
        }
        else if (!tar->right) {
            adjust = tar->left;
            parentRP = tar->parent;//2
            replaceNode(tar, tar->left);
            oriColor = getColor(tar->left);
        }
        else {
            Node* nextNode = findMin(tar->right); // 后继节点
            oriColor = getColor(nextNode);
            if (nextNode == tar->right) {
                // 如果替代节点是删除节点的直接右孩子
                // 不需要把替换节点和替换 替换节点 两部分分开来,一起干就行
                adjust = nextNode->right; // 替换 替换节点 调整的结点
                replaceNode(tar, tar->right);
                // 处理替换结点的子节点
                nextNode->left = tar->left;
                if (nextNode->left)
                    nextNode->left->parent = nextNode; // 任何更改肯定是两句话
                parentRP = nextNode->parent;//1

            }
            else if (!tar->parent) // 删除的结点是根结点 
            {
                root = tar;
                tar->parent = nullptr;
            }
            else {
                // 两步:替换节点和替换 替换节点
                // 替换 替换节点
                replaceNode(nextNode, nextNode->right);
                // 替换节点,处理替换结点的子节点
                replaceNode(tar, nextNode);
                // 右子节点
                nextNode->right = tar->right;
                tar->right->parent = nextNode;
                // 左子节点
                nextNode->left = tar->left;
                tar->left->parent = nextNode;
                parentRP = nextNode->parent;//1
            }
            // 如果替代节点存在,更新其颜色为删除节点的颜色
            if (nextNode != nullptr) {
                nextNode->color = tar->color;
            }
            // 如果替代节点不存在,将删除节点的颜色赋给origCol变量
            else {
                oriColor = tar->color;
            }
        }
        if (oriColor == Color::Black) {
            if (adjust == nullptr)
                removeFix(adjust);
            // Nil节点是黑色: Nil节点默认是黑色的,这是红黑树的基本性质之一。所有的空节点(叶子节点的左右孩子)都被视为黑色节点
            // 根据红黑树的定义,在删除节点时,如果替换的子节点是Nil,它依然保持黑色,从而不破坏黑色节点的数量,也就不会破坏红黑树的性质
            // 为了能顺利插入虚拟Nil叶子结点,所以 需要时刻保持记录 替代节点的父节点
            else {
                Nil->parent = parentRP;
                // 如果替代节点的父节点存在,设置其对应的孩子指针为Nil节点
                if (parentRP != nullptr) {
                    if (parentRP->left == nullptr) {
                        parentRP->left = Nil;
                    }
                    else {
                        parentRP->right = Nil;
                    }
                }
                // 进行修复操作
                removeFix(Nil);
                // 断开Nil节点与树的连接,因为在红黑树中Nil节点通常是单独存在的
                disconnectNil();
            }
        }
        // 删除节点
        delete tar;
    }
public:
    // 构造函数
    RedBlackTree() : root(nullptr), size(0), Nil(new Node()) {
        Nil->color = Color::Black; // Nil指针的颜色始终是黑色的
    }

    // 插入
    void insert(const Key& key, const Value& value) { insertNode(key, value); }

    // 删除
    void remove(const Key& key) {
        Node* nodeToBeRemoved = lookUp(key);
        if (nodeToBeRemoved != nullptr) {
            deleteNode(nodeToBeRemoved);
            size--;
        }
    }

    Value* at(const Key& key) {
        auto ans = lookUp(key);
        if (ans != nullptr) {
            return &ans->value;
        }
        return nullptr;
    }

    int getSize() { return size; }

    bool empty() { return size == 0; }

    // 中序遍历打印
    void print() {
        inorderTraverse(root);
        std::cout << std::endl;
    }

    void clear() {
        deleteNode(root);
        size = 0;
    }

    // 析构函数
    ~RedBlackTree() {
        // 释放节点内存
        deleteTree(root);
    }

private:
    // 递归释放节点内存
    void deleteTree(Node* node) {
        if (node) {
            deleteTree(node->left);
            deleteTree(node->right);
            delete node;
        }
    }

};

int main() {
    // 创建红黑树实例
    RedBlackTree<int, int> rbTree;

    int N;
    std::cin >> N;
    getchar();

    std::string line;
    for (int i = 0; i < N; i++)
    {
        std::getline(std::cin, line);
        std::istringstream iss(line);
        std::string command;
        iss >> command;

        int key;
        int value;

        if (command == "insert")
        {
            iss >> key >> value;
            rbTree.insert(key, value);
        }

        if (command == "size")
        {
            std::cout << rbTree.getSize() << std::endl;
        }

        if (command == "at")
        {
            iss >> key;
            int* res = rbTree.at(key);
            if (res == nullptr)
            {
                std::cout << "not exist" << std::endl;
            }
            else
            {
                std::cout << *res << std::endl;
            }
        }

        if (command == "remove")
        {
            iss >> key;
            rbTree.remove(key);
        }

        if (command == "print")
        {
            if (rbTree.empty())
            {
                std::cout << "empty" << std::endl;
            }
            else
            {
                rbTree.print();
            }
        }
    }
    return 0;
}


2.1 旋转

1)更换与父结点连接的结点(两步,parent 和 left / right)
2)新的根结点更换 右子结点 / 左子节点
3)旧根结点的左子树 接上 新根结点原来的 左子节点 / 右子节点
在这里插入图片描述

// 左旋,旋转就是3个过程
    void leftRotate(Node* cur) {
        Node* rSon = cur->right;
        // if (rSon != nullptr) rSon / cur 为nullptr转不了了
        rSon->parent = cur->parent;
        if (!cur->parent) { // 注意先判断是否为空,再使用指针
            root = rSon;
        }
        else if (cur->parent->left == cur) {
            cur->parent->left = rSon;
        }
        else if (cur->parent->right == cur) {
            cur->parent->right == rSon;
        }
        cur->parent = rSon;
        cur->right = rSon->left;
        if (rSon->left)
            rSon->left->parent = cur;
        rSon->left = cur;
    }

2.2 插入结点和插入修正

1、插入结点
如果 树中已经有 相等的key的时候,就不插入了
根据 最后结点大小关系 确定是插在 左子树还是右子树
不管颜色

// 插入节点
void insertNode(const Key& key, const Value& value) {
    Node* tar = new Node(key, value, Color::Red);
    Node* cur = root;
    if (cur == nullptr) { // 加入的第一个节点
        root = tar;
    }
    Node* par = nullptr;
    while (cur) {
        par = cur;
        if (cur->key > tar->key)
            cur = cur->left;
        else if (cur->key < tar->key)
            cur = cur->right;
        else {
            // 最后相等的时候,不插入了
            delete tar;
            return;
        }
    }
    size++;
    if (par) {
        if (par->key > key) {
            par->left = tar;
            tar->parent = par;
        }
        else {
            par->right = tar;
            tar->parent = par;
        }
    }
    insertFix(tar);
}

2、插入修正
只有父结点是红色 才需要调整,只有 4种可能的情况(父子两代结点都是红色时 一共有 8 种可能(2 * 4),插入结点是 左 / 右子树 都算一种情况,所以 一共4种)
1.1 / 1.2 的区别是插入节点 父结点的兄弟结点是 红色 / 黑色或不存在
1.1 只要变个颜色就行,就可以继续往上判断
1.2 中 插入结点是右子节点的 转成 左子节点情况操作,然后改变颜色,再旋转
在这里插入图片描述
换成父结点为其父结点的 右子树的情况,跟前面两种情况对称,就是转的时候方向相反
在这里插入图片描述

// 插入修正
void insertFix(Node* cur) {
    if (cur->parent && cur->parent->color == Color::Red) {
        // 只有父结点是红色才需要调整
        if (cur->parent->parent && cur->parent->parent->left == cur->parent) {// 1.1 & 1.2
            if (cur->parent->parent->right->color == Color::Red) {// 1.1
                cur->parent->color = Color::Black;
                cur->parent->parent->right->color = Color::Black;
                cur->parent->parent->color = Color::Red;
                insertFix(cur->parent->parent);
            }
            // 1.2
            else if (cur->parent->parent->right == nullptr || cur->parent->parent->right->color == Color::Black) {
                if (cur->parent->right == cur) {
                    leftRotate(cur->parent);
                }
                cur->parent->color = Color::Black;
                cur->parent->parent->color = Color::Red;
                rightRotate(cur->parent->parent);
            }
        }
        else if (cur->parent->parent && cur->parent->parent->right == cur->parent) {
            // 2.1 && 2.2 
            if (cur->parent->parent->left->color == Color::Red) {// 2.1
                cur->parent->color = Color::Black;
                cur->parent->parent->left->color = Color::Black;
                cur->parent->parent->color = Color::Red;
                insertFix(cur->parent->parent);
            }
            // 2.2
            else if (cur->parent->parent->left == nullptr || cur->parent->parent->left->color == Color::Black) {
                if (cur->parent->right == cur) {
                    leftRotate(cur->parent);
                }
                cur->parent->color = Color::Black;
                cur->parent->parent->color = Color::Red;
                leftRotate(cur->parent->parent);
            }
        }
    }
    root->color = Color::Black; // 确保根节点一定是黑色的
}

2.3 删除操作和删除修正

1、删除操作
设置辅助函数 是为了 处理删除中特有的遇到空节点情况(把那个结点删掉了,空节点都作为黑色处理,因为 Nil 结点是黑色的),也是为了处理哨兵结点(修正的时候没有孩子结点 需要用到哨兵整一个叶子节点,统一逻辑)
用新结点替换旧结点时,不考虑孩子节点,因为后面处理不同

// 辅助函数,获取颜色,使空指针变为黑色
Color getColor(Node* cur) {
    if (cur == nullptr) {
        return Color::Black;
    }
    else
        return cur->color;
}

// 辅助函数,设置颜色
void setColor(Node* cur, Color col) {
    if (cur == nullptr) {
        return;
    }
    cur->color = col;
}

// 辅助函数,断开与哨兵的连接
void disconnectNil() {
    if (Nil == nullptr) {
        return;
    }
    if (Nil->parent != nullptr) {
        if (Nil->parent->left == Nil) {
            Nil->parent->left = nullptr;
        }
        else {
            Nil->parent->right = nullptr;
        }
    }
}

// 辅助函数,用新结点替换旧结点
void replaceNode(Node* oldNode, Node* newNode) {
    if (!oldNode->parent) {
        root = newNode;
    }
    else if (oldNode->parent->left == oldNode) {
        oldNode->parent->left = newNode;
        newNode->parent = oldNode->parent;
    }
    else {
        oldNode->parent->right = newNode;
        newNode->parent = oldNode->parent;
    }
    // 不考虑孩子节点,因为后面处理不同
}

// 辅助函数,寻找以某个节点为根结点的子树中的最小结点
Node* findMin(Node* cur) {
    while (cur->left) {
        cur = cur->left;
    }
    return cur;
}

删除操作代码
删除的结点 要么是 有一个子节点的,要么是 有两个子节点的,如果没有子节点直接删了就完了

调整的结点 就是代替被删除结点的结点

oriColor 是被删除的颜色(不一定就是被删除结点的颜色)

parentPR:
1.对于删除结点同时有左右子节点的,记录替代结点代替之前位置的父结点,为插入Nil结点做准备
2.对于删除结点只有一个子节点的,显然不能记录代替之前的父结点(就是被删除的那个),应该使用删除节点的父结点,为插入Nil结点做准备

如果要删除的节点 有两个子节点,那么 需要找到它的后继节点(通常是其右子树中的最小节点)或其前驱节点(通常是其左子树中的最大节点),然后将要 后继/前驱节点 代替 那个要删除的结点。需要判断 替代节点是否是删除节点的 直接孩子,因为这涉及到 需不需要 把替换节点(新位置需要一系列调整,换了一个结点) 和 替换 替换节点(旧位置同样需要一系列调整,删了一个结点,但是这个删除过程就和 只有一个子节点的一致) 两部分 分开来
如果有删除的结点 只有一个子节点,直接用这个子节点代替被删除的结点即可

关于 Nil
Nil节点是黑色: Nil节点默认是黑色的,这是红黑树的基本性质之一。所有的空节点(叶子节点的左右孩子)都被视为黑色节点
根据红黑树的定义,在删除节点时,如果替换的子节点是Nil,它依然保持黑色,从而不破坏黑色节点的数量,也就不会破坏红黑树的性质
为了能顺利插入虚拟 Nil 叶子结点,所以 需要时刻保持记录 替代节点的父节点

如果 调整的结点没有孩子,就无法用相同的逻辑调整,所以需要 Nil 结点

void deleteNode(Node* tar) {
    Color oriColor = tar->color; // 被删除的颜色(不一定就是被删除结点的颜色)
    Node* adjust = nullptr; // 调整的结点
    Node* parentRP = nullptr;
    // 1.对于删除结点同时有左右子节点的,记录替代结点代替之前位置的父结点,为插入Nil结点做准备
    // 2.对于删除结点只有一个子节点的,显然不能记录代替之前的父结点(就是被删除的那个),应该使用删除节点的父结点,为插入Nil结点做准备
    if (!tar->left) {
        adjust = tar->right;
        parentRP = tar->parent;//2
        replaceNode(tar, tar->right);
        oriColor = getColor(tar->right); // 始终记录替代节点的颜色
    }
    else if (!tar->right) {
        adjust = tar->left;
        parentRP = tar->parent;//2
        replaceNode(tar, tar->left);
        oriColor = getColor(tar->left);
    }
    else {
        Node* nextNode = findMin(tar->right); // 后继节点
        oriColor = getColor(nextNode);
        if (nextNode == tar->right) {
            // 如果替代节点是删除节点的直接右孩子
            // 不需要把替换节点和替换 替换节点 两部分分开来,一起干就行
            adjust = nextNode->right; // 替换 替换节点 调整的结点
            replaceNode(tar, tar->right);
            // 处理替换结点的子节点
            nextNode->left = tar->left;
            if (nextNode->left)
                nextNode->left->parent = nextNode; // 任何更改肯定是两句话
            parentRP = nextNode->parent;//1

        }
        else if (!tar->parent) // 删除的结点是根结点 
        {
            root = tar;
            tar->parent = nullptr;
        }
        else {
            // 两步:替换节点和替换 替换节点
            // 替换 替换节点
            replaceNode(nextNode, nextNode->right);
            // 替换节点,处理替换结点的子节点
            replaceNode(tar, nextNode);
            // 右子节点
            nextNode->right = tar->right;
            tar->right->parent = nextNode;
            // 左子节点
            nextNode->left = tar->left;
            tar->left->parent = nextNode;
            parentRP = nextNode->parent;//1
        }
        // 如果替代节点存在,更新其颜色为删除节点的颜色
        if (nextNode != nullptr) {
            nextNode->color = tar->color;
        }
        // 如果替代节点不存在,将删除节点的颜色赋给origCol变量
        else {
            oriColor = tar->color;
        }
    }
    if (oriColor == Color::Black) {
        if (adjust == nullptr)
            removeFix(adjust);
        // Nil节点是黑色: Nil节点默认是黑色的,这是红黑树的基本性质之一。所有的空节点(叶子节点的左右孩子)都被视为黑色节点
        // 根据红黑树的定义,在删除节点时,如果替换的子节点是Nil,它依然保持黑色,从而不破坏黑色节点的数量,也就不会破坏红黑树的性质
        // 为了能顺利插入虚拟Nil叶子结点,所以 需要时刻保持记录 替代节点的父节点
        else {
            Nil->parent = parentRP;
            // 如果替代节点的父节点存在,设置其对应的孩子指针为Nil节点
            if (parentRP != nullptr) {
                if (parentRP->left == nullptr) {
                    parentRP->left = Nil;
                }
                else {
                    parentRP->right = Nil;
                }
            }
            // 进行修复操作
            removeFix(Nil);
            // 断开Nil节点与树的连接,因为在红黑树中Nil节点通常是单独存在的
            disconnectNil();
        }
    }
    // 删除节点
    delete tar;
}

2、删除修正
x 总是指向 一个具有双重黑色的非根结点。(一旦黑红直接 涂黑色就完事了)要判断 x 是其父结点 x.p 的左孩子还是右孩子。保持指针 w 指向 x 的兄弟。由于结点 x 是双重黑色的,故 w 不可能是 T.nil,因为否则,从 x.p 至(单黑色)叶子 w 的简单路径上的黑结点个数 就会小于从 x.p 到 x 的简单路径上的黑结点数

以 x 为 adjust(需要调整的结点,被删除结点的子节点),w 为其兄弟节点
需要修正的 就是双重黑节点的 多一重的黑色(代替红色的结点 总是合法的)
当w是红色结点时,5个节点的颜色只有一种可能性(图中标白的表示 红黑都有可能)

情况1和2是穷尽了兄弟的孩子结点均为黑色的情况,情况3和4加在一起 穷尽了兄弟结点的子节点 含有红色结点的情况

情况3和情况4是同一种情况(3会转成4),兄弟结点的子节点存在红色结点 就可以使 两个子树成功出现高度差了,双重黑色 自然就解决了

情况1,3,4都是借助兄弟子树的红色结点 消除双重黑色结点,情况1转成下面的2,3,4,情况2 / 4 都是最终情况,2是把x上移了,到根结点就结束了;4是直接完成了
在这里插入图片描述

// 删除修正,需要哨兵Nil
// 删除修正四种情况的核心是:兄弟结点的子节点有没有红色结点(有红节点转到父结点的位置上就OK了,如果到根结点还是没有也OK了)?之后是兄弟节点有没有红色节点 /  兄弟结点子节点的红色节点在哪
void removeFix(Node* cur) { // cur就是x
    while (cur != root && getColor(cur) == Color::Black) // 一旦碰到红的就结束了
    {
        // 先处理图中所示的左边的情况,右边是对称的
        if (cur->parent->left == cur) {
            if (getColor(cur->parent->right) == Color::Red) { // (a)转(b)/(c)/(d)
                setColor(cur->parent, Color::Red);
                setColor(cur->parent->right, Color::Black);
                leftRotate(cur->parent);
            }
            else if (getColor(cur->parent->right->left) == Color::Black && getColor(cur->parent->right->right) == Color::Black) {
                setColor(cur->parent->right, Color::Red);
                cur = cur->parent;
            }
            else if (getColor(cur->parent->right->left) == Color::Red && getColor(cur->parent->right->right) == Color::Black) {
                setColor(cur->parent->right, Color::Red);
                setColor(cur->parent->right->left, Color::Black);
                rightRotate(cur->parent->right);
            }
            else {
                setColor(cur->parent->right, getColor(cur->parent));
                setColor(cur->parent, Color::Black);
                setColor(cur->parent->right->right, Color::Black);
                leftRotate(cur->parent);
            }
        }
        else {
            Node* bro = cur->parent->left;
            if (getColor(bro) == Color::Red) { // (a)转(b)/(c)/(d)
                setColor(cur->parent, Color::Red);
                setColor(bro, Color::Black);
                rightRotate(cur->parent);
            }
            else if (getColor(bro->left) == Color::Black && getColor(bro->right) == Color::Black) {
                setColor(bro, Color::Red);
                cur = cur->parent;
            }
            else if (getColor(bro->left) == Color::Red && getColor(bro->right) == Color::Black) {
                setColor(bro, Color::Red);
                setColor(bro->left, Color::Black);
                rightRotate(bro);
            }
            else {
                setColor(bro, getColor(cur->parent));
                setColor(cur->parent, Color::Black);
                setColor(bro->right, Color::Black);
                rightRotate(cur->parent);
            }
        }
    }
    setColor(cur, Color::Black);
}

红黑树不是完全平衡的二叉树
完全平衡的二叉树(如 AVL 树)要求二叉树的每个节点的左右子树高度差最多为一

3、与标准库的差异

性能和优化
异常处理
模板特化和配置: C++ STL的容器是可配置和可特化的,允许用户提供自定义的比较器、分配器等
迭代器和算法: C++ STL中的容器通常配有迭代器,方便使用STL算法
内存管理: C++ STL标准库中的实现通常使用高效的内存管理技术

RedBlackTree<int> mySet;
// 插入元素
mySet.insert(42);
mySet.insert(63);
mySet.insert(10);
mySet.insert(4);
mySet.insert(30);
mySet.insert(36);

内容在此基础上整理补充:
算法导论 总结索引 | 第三部分 第十三章:红黑树
https://kamacoder.com/ 手写简单版本STL

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

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

相关文章

信息收集---CDN指纹识别

1. 什么是CDN 在渗透测试过程中&#xff0c;经常会碰到网站有CDN的情况。CDN即内容分发网络&#xff0c;主要解决因传输距离和不同运营商节点造成的网络速度性能低下的问题。说的简单点&#xff0c;就是一组在不同运营商之间的对接点上的高速缓存服务器&#xff0c;把用户经常访…

蓝牙技术|蓝牙6.0技术或将实现厘米级精确查找定位功能

蓝牙技术联盟发布蓝牙 6.0 技术规范&#xff0c;引入了一项名为蓝牙“信道探测”的新功能&#xff0c;可以在两个蓝牙 LE 设备之间实现双向测距&#xff0c;有望为电子设备上的“查找”功能带来更精确的定位能力。蓝牙技术联盟表示&#xff0c;这项新技术将为数十亿未来的蓝牙设…

深度学习:调整学习率

目录 前言 一、什么是调整学习率&#xff1f; 二、调整学习率的作用 三、怎么调整学习率 1.有序调整 2.自适应调整 3.自定义调整 4.调整示例 前言 在深度学习中&#xff0c;调整学习率是非常重要的&#xff0c;它对模型的训练效果和收敛速度有显著影响。 一、什么是调整…

前端问答:如何用 JavaScript 让 HTML Canvas全屏显示

哈喽&#xff0c;大家好&#xff01;今天要跟大家分享一个非常实用的小技巧&#xff0c;适合那些正在学习前端开发的朋友们。你是不是也遇到过这样的问题&#xff1a;在制作一些网页小游戏、炫酷的网页动画或者数据可视化时&#xff0c;想让画布&#xff08;Canvas&#xff09;…

Ubuntu24.04 yum安装

安装yum&#xff1a; sudo apt-get install yum 执行报错 E: Package yum has no installation candidate 解决&#xff1a;更换镜像源&#xff0c;找到自己的系统版本&#xff08;如本系统为Ubuntu24.04&#xff09;用vim进行更换&#xff0c;网址&#xff1a; ubuntu | 镜…

26 基于STM32的智能门禁系统(指纹、蓝牙、刷卡、OLED、电机)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STM32单片机&#xff0c;六个按键&#xff0c;分别代表指纹、蓝牙、刷卡的正确进门与错误进门&#xff1b; 比如第一个按键按下&#xff0c;表示指纹正确&#xff0c;OLED显示指纹正确&#x…

linux服务器部署filebeat

# 下载filebeat curl -L -O https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.17.23-linux-x86_64.tar.gz # 解压 tar xzvf filebeat-7.17.23-linux-x86_64.tar.gz# 所在位置&#xff08;自定义&#xff09; /opt/filebeat-7.17.23-linux-x86_64/filebeat.ym…

FreeRTOS——任务调度、任务状态

任务调度 调度器就是使用相关的调度算法来决定当前需要执行哪个任务。 FreeRTOS一共支持三种任务调度方式&#xff1a; 抢占式调度&#xff1a;主要是针对优先级不同的任务&#xff0c;每个任务都有一个优先级&#xff0c;优先级高的任务可以抢占优先级低的任务。&#xff08…

word批量裁剪图片,并调整图片大小,不锁定纵横比

在word中有若干图片待处理&#xff0c;裁剪出指定内容&#xff0c;调整成指定大小。如下是待处理的图片&#xff1a; 这时&#xff0c;选择视图&#xff0c;选择宏&#xff0c;查看宏 选择创建宏 添加cut_picture代码如下&#xff0c;其中上、下、左、右裁剪的橡塑尺寸根据自己…

【2】图像视频的加载和显示

文章目录 【2】图像视频的加载和显示一、代码在哪写二、创建和显示窗口&#xff08;一&#xff09;导入OpenCV的包cv2&#xff08;二&#xff09;创建窗口&#xff08;三&#xff09;更改窗口大小 & 显示窗口&#xff08;四&#xff09;等待用户输入补充&#xff1a;ord()函…

24最新ComfyUI搭建使用教程

前言 ComfyUI 是一个基于节点流程式的stable diffusion AI 绘图工具WebUI&#xff0c; 通过将stable diffusion的流程拆分成节点&#xff0c;实现了更加精准的工作流定制和完善的可复现性。 ComfyUI因为内部生成流程做了优化&#xff0c;生成图片时的速度相较于WebUI有10%~25…

关于字节 c++

字节的介绍 字节是计算机中最小的存储单位&#xff0c;通常由8个二进制位组成&#xff0c;用来存储一个字符。在C中&#xff0c;字节也是基本数据类型之一&#xff0c;用关键字"byte"来表示。字节主要用于存储一些较小的数据&#xff0c;如整数、字符等。字节的大小…

音频转MP3格式困难?如何轻松实现wav转mp3?

格式多样化为我们带来了灵活性和创意的无限可能&#xff0c;但同时&#xff0c;不同格式间的转换也成为了不少用户面临的难题。尤其是当你手握珍贵的WAV音频文件&#xff0c;却希望它们能在更多设备上流畅播放或节省存储空间时&#xff0c;wav转mp3的需求便应运而生。WAV以其无…

网络安全中的 EDR 是什么:概述和功能

专业知识&#xff1a;EDR、XDR、NDR 和 MDR_xdr edr ndr-CSDN博客 端点检测和响应 (EDR) 是一种先进的安全系统&#xff0c;用于检测、调查和解决端点上的网络攻击。它可以检查事件、检查行为并将系统恢复到攻击前的状态。EDR 使用人工智能、机器学习和威胁情报来避免再次发生攻…

c语言实现:链表创建、插入、删除、翻转

#include <stdio.h> #include <stdlib.h>// 链表创建 typedef struct Node{int data;struct Node* next; } Node;// 创建一个节点 Node* createNode(int data){Node* newNode (Node* )malloc(sizeof(Node));newNode->data data;newNode->next NULL;return…

35岁java转大模型笔记,大模型智能体(LLM Agent)学习笔记

\1. 什么是大模型&#xff1f; 大模型对应的英文是Large Language Model&#xff08;LLM&#xff09;&#xff0c;即大语言模型&#xff0c;简称大模型。技术层面讲&#xff0c;大模型是一种基于深度学习技术的机器学习模型。 为什么叫大模型呢&#xff1f;它是相对于小模型而…

万界星空科技铜拉丝行业MES系统,实现智能化转型

一、铜拉丝行业生产管理的难点主要体现在以下几个方面&#xff1a; 1、标准严格&#xff1a;铜线产品对质量的要求极高&#xff0c;特别是在电气性能、导电性、耐腐蚀性等方面&#xff0c;任何微小的瑕疵都可能影响产品的使用效果和安全性。 2、过程监控&#xff1a;生产过程…

点赞10万+,1分钟教会你,用AI生成的宠物带娃视频

今天刷到了这样的宠物带娃视频&#xff0c;最近这种视频爆火&#xff0c;出现了很多爆款&#xff0c;今天就拆解一下&#xff0c;教大家学会这种视频用AI如何生成。 我们先看一下这类视频的数据&#xff0c;很多账号都在做&#xff0c;对于不了解AI的人来说&#xff0c;会觉得…

轻松构建便民平台小程序源码系统 带完整的安装代码包以及搭建部署教程

系统概述 轻松构建便民平台小程序源码系统是一款集成了多项实用功能的模块化小程序开发框架。它基于当前最流行的小程序开发技术栈&#xff0c;如微信小程序、支付宝小程序等&#xff0c;通过预制的组件和模块&#xff0c;极大地简化了开发流程&#xff0c;降低了技术门槛。无…