红黑树详解

目录

一、红黑树概述

二、红黑树的五大性质

三、节点结构设计

四、插入操作核心逻辑

[4.1 插入流程图](#4.1 插入流程图)

[4.2 插入代码分段详解](#4.2 插入代码分段详解)

步骤1:空树处理

步骤2:BST插入

步骤3:调整红黑树性质

五、插入调整的三种情况详解

情况定义

[5.1 情况一:叔叔节点为红色](#5.1 情况一:叔叔节点为红色)

[5.2 情况二:叔叔节点为黑色或不存在(cur是parent的右孩子)](#5.2 情况二:叔叔节点为黑色或不存在(cur是parent的右孩子))

[5.3 情况三:叔叔节点为黑色或不存在(cur是parent的左孩子)](#5.3 情况三:叔叔节点为黑色或不存在(cur是parent的左孩子))

[5.4 对称情况](#5.4 对称情况)

六、旋转操作详解

[6.1 左单旋(RotateLeft)](#6.1 左单旋(RotateLeft))

[6.2 右单旋(RotateRight)](#6.2 右单旋(RotateRight))

七、平衡性检查(IsBalanceTree)

[7.1 检查流程](#7.1 检查流程)

[7.2 递归检查函数](#7.2 递归检查函数)

八、红黑树与AVL树对比

九、完整代码实现


一、红黑树概述

红黑树是一种自平衡的二叉搜索树 ,它在每个节点上增加一个存储位表示节点的颜色(红或黑)。通过对任何一条从根到叶子的路径上节点颜色的约束,红黑树确保最长路径不超过最短路径的2倍,从而保持近似平衡。

二、红黑树的五大性质

性质 描述 代码中的体现
性质1 每个节点是红色或黑色 enum Colour { RED, BLACK };
性质2 根节点是黑色 _root->_col = BLACK; 强制保证
性质3 所有叶子节点(NIL)是黑色 代码中 nullptr 视为黑色节点
性质4 红色节点的子节点必须是黑色 Check() 函数检查连续红色
性质5 从任一节点到其每个叶子的路径包含相同数量的黑色节点 Check() 函数验证每条路径黑色节点数相等

三、节点结构设计

cpp 复制代码
template<class K, class V>
struct RBTreeNode
{
    pair<K, V> _kv;           // 键值对
    RBTreeNode<K, V>* _left;   // 左孩子
    RBTreeNode<K, V>* _right;  // 右孩子
    RBTreeNode<K, V>* _parent; // 父节点(重要!)
    Colour _col;               // 颜色
    
    RBTreeNode(const pair<K, V>& kv)
        :_kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _col(RED)  // 新节点默认为红色
    {}
};

为什么新节点默认为红色?

  • 如果插入黑色节点,会破坏性质5(黑色节点数量),影响所有路径

  • 插入红色节点只可能破坏性质4(连续红色),影响范围更小,更容易调整

四、插入操作核心逻辑

4.1 插入流程图

4.2 插入代码分段详解

步骤1:空树处理
cpp 复制代码
if (_root == nullptr)
{
    _root = new Node(kv);
    _root->_col = BLACK;  // 根节点必须为黑色
    return true;
}
步骤2:BST插入
cpp 复制代码
Node* parent = nullptr;
Node* cur = _root;
while (cur)
{
    if (kv.first < cur->_kv.first)
    {
        parent = cur;
        cur = cur->_left;
    }
    else if (kv.first > cur->_kv.first)
    {
        parent = cur;
        cur = cur->_right;
    }
    else
    {
        return false;  // 键值重复,插入失败
    }
}

cur = new Node(kv);  // 创建红色新节点
if (kv.first < parent->_kv.first)
    parent->_left = cur;
else
    parent->_right = cur;
cur->_parent = parent;
步骤3:调整红黑树性质

这是最核心的部分,我们根据父节点是祖父的左孩子还是右孩子 分两种情况,每种情况再根据叔叔节点的颜色分三种子情况。

五、插入调整的三种情况详解

情况定义

  • cur:当前新插入节点(红色)

  • parent:父节点(红色,需要调整)

  • grandfather:祖父节点(必为黑色)

  • uncle:叔叔节点(祖父的另一子)

5.1 情况一:叔叔节点为红色

特征uncle 存在且为红色

处理方式

  1. parentuncle 改为黑色

  2. grandfather 改为红色

  3. cur 指向 grandfather,继续向上调整

代码实现

cpp 复制代码
if (uncle && uncle->_col == RED)
{
    parent->_col = BLACK;
    uncle->_col = BLACK;
    grandfather->_col = RED;
    
    // 继续向上处理
    cur = grandfather;
    parent = cur->_parent;
}

5.2 情况二:叔叔节点为黑色或不存在(cur是parent的右孩子)

特征

  • uncle 不存在或为黑色

  • curparent 的右孩子(需要双旋)

处理方式

  1. parent 进行左单旋

  2. 交换 parentcur 指针

  3. 转化为情况三

代码实现

cpp 复制代码
if (cur == parent->_right)
{
    RotateLeft(parent);  // 左旋
    swap(parent, cur);   // 交换,转化为情况三
}
// 接着执行情况三...

5.3 情况三:叔叔节点为黑色或不存在(cur是parent的左孩子)

特征

  • uncle 不存在或为黑色

  • curparent 的左孩子(单旋即可)

处理方式

  1. grandfather 进行右单旋

  2. parent 改为黑色

  3. grandfather 改为红色

代码实现

cpp 复制代码
RotateRight(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
break;  // 调整结束

5.4 对称情况

parentgrandfather 的右孩子时,处理方式对称:

  • 情况二:cur 是左孩子时,先右旋

  • 情况三:cur 是右孩子时,左旋

六、旋转操作详解

6.1 左单旋(RotateLeft)

适用场景:右子树过高,需要降低

cpp 复制代码
void RotateLeft(Node* parent)
{
    Node* subR = parent->_right;      // 右孩子
    Node* subRL = subR->_left;        // 右孩子的左子树
    
    // 1. 将subRL挂到parent的右边
    parent->_right = subRL;
    if (subRL)
        subRL->_parent = parent;
    
    // 2. 将parent挂到subR的左边
    subR->_left = parent;
    parent->_parent = subR;
    
    // 3. 处理父指针
    Node* ppNode = parent->_parent;
    if (ppNode == nullptr)
    {
        _root = subR;
        subR->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
            ppNode->_left = subR;
        else
            ppNode->_right = subR;
        subR->_parent = ppNode;
    }
}

6.2 右单旋(RotateRight)

适用场景:左子树过高,需要降低

cpp 复制代码
void RotateRight(Node* parent)
{
    Node* subL = parent->_left;       // 左孩子
    Node* subLR = subL->_right;       // 左孩子的右子树
    
    // 1. 将subLR挂到parent的左边
    parent->_left = subLR;
    if (subLR)
        subLR->_parent = parent;
    
    // 2. 将parent挂到subL的右边
    subL->_right = parent;
    parent->_parent = subL;
    
    // 3. 处理父指针
    Node* ppNode = parent->_parent;
    if (ppNode == nullptr)
    {
        _root = subL;
        subL->_parent = nullptr;
    }
    else
    {
        if (ppNode->_left == parent)
            ppNode->_left = subL;
        else
            ppNode->_right = subL;
        subL->_parent = ppNode;
    }
}

七、平衡性检查(IsBalanceTree)

7.1 检查流程

cpp 复制代码
bool IsBalanceTree()
{
    if (_root == nullptr) return true;
    
    // 1. 检查根节点颜色
    if (_root->_col == RED) return false;
    
    // 2. 计算参考值(最左路径的黑色节点数)
    int refNum = 0;
    Node* cur = _root;
    while (cur)
    {
        if (cur->_col == BLACK) ++refNum;
        cur = cur->_left;
    }
    
    // 3. 递归检查所有路径
    return Check(_root, 0, refNum);
}

7.2 递归检查函数

cpp 复制代码
bool Check(Node* root, int blackNum, const int refNum)
{
    if (root == nullptr)
    {
        // 到达叶子,检查黑色节点数是否等于参考值
        if (refNum != blackNum)
        {
            cout << "存在黑色结点数量不相等的路径" << endl;
            return false;
        }
        return true;
    }
    
    // 检查是否有连续红色节点
    if (root->_col == RED && root->_parent && root->_parent->_col == RED)
    {
        cout << root->_kv.first << "存在连续的红色结点" << endl;
        return false;
    }
    
    // 统计黑色节点
    if (root->_col == BLACK)
        blackNum++;
    
    // 递归检查左右子树
    return Check(root->_left, blackNum, refNum)
        && Check(root->_right, blackNum, refNum);
}

八、红黑树与AVL树对比

特性 红黑树 AVL树
平衡条件 最长路径 ≤ 2×最短路径 左右子树高度差 ≤ 1
平衡程度 近似平衡 严格平衡
查询效率 O(log n),略慢 O(log n),更快
插入/删除效率 旋转次数少,更快 旋转次数多,较慢
适用场景 插入删除频繁 查询频繁

九、完整代码实现

cpp 复制代码
#pragma once

#include <iostream>
using namespace std;

// 枚举值表示颜色
enum Colour
{
    RED,
    BLACK
};

// 这里我们默认按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
    // 这里更新控制平衡也要加入parent指针
    pair<K, V> _kv;
    RBTreeNode<K, V>* _left;
    RBTreeNode<K, V>* _right;
    RBTreeNode<K, V>* _parent;
    Colour _col;

    RBTreeNode(const pair<K, V>& kv)
        :_kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _col(RED)  // 新节点默认为红色
    {}
};

template<class K, class V>
class RBTree
{
    typedef RBTreeNode<K, V> Node;
public:
    bool Insert(const pair<K, V>& kv)
    {
        // 1. 空树:直接创建根节点,颜色为黑色
        if (_root == nullptr)
        {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        // 2. 按照二叉搜索树规则插入
        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            if (kv.first < cur->_kv.first)
            {
                parent = cur;
                cur = cur->_left;
            }
            else if (kv.first > cur->_kv.first)
            {
                parent = cur;
                cur = cur->_right;
            }
            else
            {
                return false; // 键值重复,插入失败
            }
        }

        // 创建新节点(红色)
        cur = new Node(kv);
        if (kv.first < parent->_kv.first)
            parent->_left = cur;
        else
            parent->_right = cur;
        cur->_parent = parent;

        // 3. 调整颜色和旋转,维持红黑树性质
        while (parent && parent->_col == RED)
        {
            Node* grandfather = parent->_parent;
            if (grandfather == nullptr)
                break;

            // 情况一:parent 是 grandfather 的左孩子
            if (parent == grandfather->_left)
            {
                Node* uncle = grandfather->_right;

                // 情况1:叔叔存在且为红
                if (uncle && uncle->_col == RED)
                {
                    // 变色
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    // 继续向上处理
                    cur = grandfather;
                    parent = cur->_parent;
                }
                else // 情况2/3:叔叔不存在或为黑
                {
                    // 情况2:cur 是 parent 的右孩子 -> 左单旋
                    if (cur == parent->_right)
                    {
                        RotateLeft(parent);
                        swap(parent, cur);
                    }

                    // 情况3:cur 是 parent 的左孩子 -> 右单旋
                    RotateRight(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    break; // 调整结束
                }
            }
            else // 情况二:parent 是 grandfather 的右孩子(对称情况)
            {
                Node* uncle = grandfather->_left;

                // 情况1:叔叔存在且为红
                if (uncle && uncle->_col == RED)
                {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;

                    cur = grandfather;
                    parent = cur->_parent;
                }
                else
                {
                    // 情况2:cur 是 parent 的左孩子 -> 右单旋
                    if (cur == parent->_left)
                    {
                        RotateRight(parent);
                        swap(parent, cur);
                    }

                    // 情况3:cur 是 parent 的右孩子 -> 左单旋
                    RotateLeft(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    break;
                }
            }
        }

        // 确保根节点为黑色
        _root->_col = BLACK;
        return true;
    }

    // 左单旋
    void RotateLeft(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;

        parent->_right = subRL;
        if (subRL)
            subRL->_parent = parent;

        Node* ppNode = parent->_parent;
        subR->_left = parent;
        parent->_parent = subR;

        if (ppNode == nullptr)
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (ppNode->_left == parent)
                ppNode->_left = subR;
            else
                ppNode->_right = subR;
            subR->_parent = ppNode;
        }
    }

    // 右单旋
    void RotateRight(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;

        parent->_left = subLR;
        if (subLR)
            subLR->_parent = parent;

        Node* ppNode = parent->_parent;
        subL->_right = parent;
        parent->_parent = subL;

        if (ppNode == nullptr)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (ppNode->_left == parent)
                ppNode->_left = subL;
            else
                ppNode->_right = subL;
            subL->_parent = ppNode;
        }
    }

    void InOrder()
    {
        _InOrder(_root);
        cout << endl;
    }

    bool IsBalanceTree()
    {
        if (_root == nullptr)
            return true;

        if (_root->_col == RED)
            return false;

        // 计算最左路径的黑色节点数作为参考值
        int refNum = 0;
        Node* cur = _root;
        while (cur)
        {
            if (cur->_col == BLACK)
                ++refNum;
            cur = cur->_left;
        }

        return Check(_root, 0, refNum);
    }

private:
    Node* _root = nullptr;

    void _InOrder(Node* root)
    {
        if (root == nullptr)
            return;

        _InOrder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << " ";
        _InOrder(root->_right);
    }

    bool Check(Node* root, int blackNum, const int refNum)
    {
        if (root == nullptr)
        {
            if (refNum != blackNum)
            {
                cout << "存在黑色结点数量不相等的路径" << endl;
                return false;
            }
            return true;
        }

        // 检查是否有连续红色节点
        if (root->_col == RED && root->_parent && root->_parent->_col == RED)
        {
            cout << root->_kv.first << "存在连续的红色结点" << endl;
            return false;
        }

        if (root->_col == BLACK)
            blackNum++;

        return Check(root->_left, blackNum, refNum)
            && Check(root->_right, blackNum, refNum);
    }
};
相关推荐
迈巴赫车主2 小时前
蓝桥杯20560逃离高塔
java·开发语言·数据结构·算法·职场和发展·蓝桥杯
泯仲2 小时前
Ragent项目7种设计模式深度解析:从源码看设计模式落地实践
java·算法·设计模式·agent
dulu~dulu2 小时前
算法---寻找和为K的子数组
笔记·python·算法·leetcode
moonsea02032 小时前
【无标题】
算法
佑白雪乐3 小时前
<ACM进度212题>[2026-3-1,2026-3-26]
算法·leetcode
穿条秋裤到处跑3 小时前
每日一道leetcode(2026.03.26):等和矩阵分割 II
算法·leetcode·矩阵
平凡灵感码头3 小时前
C语言 printf 数据打印格式速查表
c语言·开发语言·算法
哔哔龙3 小时前
Android OpenCV 实战:图片轮廓提取与重叠轮廓合并处理
android·算法
hz_zhangrl3 小时前
CCF-GESP 等级考试 2026年3月认证C++三级真题解析
c++·算法·程序设计·gesp·gesp2026年3月·gesp c++三级