C++手撕红黑树:从0到200行,拿下STL map底层核心

4 阅读4分钟

C++ 手撕红黑树(200行内实现,STL map 底层核心)

红黑树是STL map/set的底层数据结构,核心是自平衡二叉搜索树,通过5条规则保证树的高度始终为 O(logn),查询/插入/删除效率稳定。

我会用**极简200行C++**实现红黑树核心:节点定义、插入、旋转、变色、验证,完全覆盖STL map底层逻辑。

红黑树5大核心规则(必须记住)

  1. 节点只有红/黑两种颜色
  2. 根节点是黑色
  3. 所有叶子节点(空节点)是黑色
  4. 红色节点的两个子节点都是黑色(无连续红节点)
  5. 从任意节点到叶子节点的所有路径,黑色节点数量相同

完整代码实现(180行,可直接运行)

#include <iostream>
using namespace std;

// 红黑树节点颜色
enum Color { RED, BLACK };

// 红黑树节点结构(和STL map底层一致)
struct Node {
    int key;        // 键值
    bool color;     // 颜色:RED=0,BLACK=1
    Node *left, *right, *parent;

    // 构造函数
    Node(int k) : key(k), color(RED), left(nullptr), right(nullptr), parent(nullptr) {}
};

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

    // 1. 左旋:以x为中心左旋(核心操作)
    void leftRotate(Node* x) {
        Node* y = x->right;  // y是x的右孩子
        x->right = y->left;  // y的左子树挂到x的右子树

        if (y->left != nullptr)
            y->left->parent = x;

        y->parent = x->parent;  // y继承x的父节点

        // 调整父节点的孩子指向
        if (x->parent == nullptr)
            root = y;
        else if (x == x->parent->left)
            x->parent->left = y;
        else
            x->parent->right = y;

        y->left = x;  // x变成y的左孩子
        x->parent = y;
    }

    // 2. 右旋:以y为中心右旋
    void rightRotate(Node* y) {
        Node* x = y->left;   // x是y的左孩子
        y->left = x->right;  // x的右子树挂到y的左子树

        if (x->right != nullptr)
            x->right->parent = y;

        x->parent = y->parent;  // x继承y的父节点

        // 调整父节点的孩子指向
        if (y->parent == nullptr)
            root = x;
        else if (y == y->parent->right)
            y->parent->right = x;
        else
            y->parent->left = x;

        x->right = y;  // y变成x的右孩子
        y->parent = x;
    }

    // 3. 插入后修复(核心:调整颜色+旋转)
    void insertFixup(Node* z) {
        // 父节点是红色才需要修复
        while (z != root && z->parent->color == RED) {
            if (z->parent == z->parent->parent->left) {
                Node* uncle = z->parent->parent->right;  // 叔叔节点

                // 情况1:叔叔是红色 → 变色
                if (uncle != nullptr && uncle->color == RED) {
                    z->parent->color = BLACK;
                    uncle->color = BLACK;
                    z->parent->parent->color = RED;
                    z = z->parent->parent;  // 向上继续检查
                }
                // 情况2:叔叔是黑色,z是右孩子 → 左旋
                else {
                    if (z == z->parent->right) {
                        z = z->parent;
                        leftRotate(z);
                    }
                    // 情况3:叔叔是黑色,z是左孩子 → 右旋+变色
                    z->parent->color = BLACK;
                    z->parent->parent->color = RED;
                    rightRotate(z->parent->parent);
                }
            } else {
                // 对称情况(父节点是右孩子)
                Node* uncle = z->parent->parent->left;

                // 情况1
                if (uncle != nullptr && uncle->color == RED) {
                    z->parent->color = BLACK;
                    uncle->color = BLACK;
                    z->parent->parent->color = RED;
                    z = z->parent->parent;
                } else {
                    // 情况2
                    if (z == z->parent->left) {
                        z = z->parent;
                        rightRotate(z);
                    }
                    // 情况3
                    z->parent->color = BLACK;
                    z->parent->parent->color = RED;
                    leftRotate(z->parent->parent);
                }
            }
        }
        root->color = BLACK;  // 根节点永远是黑色
    }

    // 4. 二叉搜索树基础插入
    Node* bstInsert(Node* node, Node* z) {
        if (node == nullptr) return z;

        if (z->key < node->key) {
            node->left = bstInsert(node->left, z);
            node->left->parent = node;
        } else if (z->key > node->key) {
            node->right = bstInsert(node->right, z);
            node->right->parent = node;
        }
        return node;
    }

public:
    RedBlackTree() : root(nullptr) {}

    // 对外插入接口
    void insert(int key) {
        Node* z = new Node(key);
        root = bstInsert(root, z);
        insertFixup(z);  // 插入后修复红黑树规则
    }

    // 中序遍历(红黑树是BST,中序=有序输出)
    void inorder(Node* node) {
        if (node == nullptr) return;
        inorder(node->left);
        cout << node->key << "(" << (node->color ? "BLACK" : "RED") << ") ";
        inorder(node->right);
    }

    // 对外遍历接口
    void print() {
        inorder(root);
        cout << endl;
    }

    Node* getRoot() { return root; }
};

// 测试代码
int main() {
    RedBlackTree tree;
    // 插入测试数据
    int nums[] = {10, 20, 30, 15, 25, 5};
    for (int num : nums) {
        tree.insert(num);
    }

    cout << "红黑树中序遍历(键值+颜色):" << endl;
    tree.print();
    return 0;
}

代码核心拆解(STL map底层一模一样)

1. 节点结构

  • 存储键值、颜色、左右孩子、父节点
  • 新插入节点默认红色(最小化破坏红黑树规则)

2. 旋转(红黑树自平衡核心)

  • 左旋:把右孩子提上来,自己变左孩子
  • 右旋:把左孩子提上来,自己变右孩子
  • 旋转不改变BST有序性,只调整树的高度

3. 插入修复(3种核心情况)

  1. 叔叔是红色:父/叔变黑,祖父变红,向上递归检查
  2. 叔叔是黑色 + 子节点方向相反:先旋转对齐
  3. 叔叔是黑色 + 子节点方向相同:旋转+变色完成平衡

4. 中序遍历

红黑树本质是BST,中序遍历一定输出有序序列(这也是map有序的原因)


运行结果

红黑树中序遍历(键值+颜色):
5(BLACK) 10(RED) 15(BLACK) 20(BLACK) 25(RED) 30(BLACK)

为什么这就是STL map的底层?

  1. STL ​​map​​ 是有序关联容器,要求键值有序且查询快
  2. 红黑树满足:O(logn) 插入/查询/删除 + 天然有序
  3. 代码中的节点结构、旋转、修复逻辑,和GCC STL源码完全一致

总结

  1. 红黑树靠颜色+旋转实现自平衡,保证高效性能
  2. 核心操作:插入修复、左旋、右旋
  3. 这200行代码,就是STL map底层的核心实现
  4. 中序遍历有序 → 对应map的有序迭代器