跳到主要内容
极客日志极客日志
首页博客AI提示词GitHub精选代理工具
搜索
|注册
博客列表
C++算法

C++ 伸展树与红黑树详解及实现

综述由AI生成探讨了 C++ 中的两种自平衡二叉搜索树:伸展树与红黑树。伸展树利用局部性原理,通过旋转将频繁访问节点移至根部,适合缓存场景;红黑树则通过颜色约束保证最长路径不超过最短路径的两倍,提供稳定的对数级性能,是 STL map/set 的基础。文章详细分析了它们的性质、操作复杂度,并给出了红黑树的完整 C++ 实现,包括插入、查找、验证及哨兵节点处理,辅以 LeetCode 相关题目解析,帮助读者深入理解平衡树的核心机制。

JavaCoder发布于 2026/3/22更新于 2026/5/98 浏览
C++ 伸展树与红黑树详解及实现

C++ 伸展树与红黑树详解及实现

在之前的讨论中,我们了解了 AVL 树的实现。接下来我们将深入探讨 C++ 中的两种自平衡二叉搜索树:伸展树(Splay Tree)与红黑树(Red-Black Tree)。这两类结构在工程实践中各有不可替代的价值。

1. 伸展树介绍

伸展树是一种改进的二叉搜索树,由 John Edward Hopcroft 和 Robert Endre Tarjan 于 1985 年共同发明。它属于自调整数据结构,与 AVL 树不同,伸展树不追求严格的平衡,而是利用'局部性原理'优化频繁访问的场景。

核心思想

  1. 单一旋转:将经常访问的节点上移,使其靠近根节点。
  2. 移动到根部:假设当前访问的节点会再次被高频访问,通过反复旋转将其移至根节点。

每当执行搜索、插入或删除操作时,伸展树都会对目标节点执行一次'展开'(Splaying)过程,将其移至根节点。如果删除节点,则将其父节点展开到根节点。

旋转类型

一次'展开'由一组旋转组成,主要包括三种情况:

  1. 单旋转:当被访问节点的父结点是根结点时执行。
  2. 一字形旋转:当节点与其父、祖父结点呈同构形状(如左 -左或右 -右)时执行的双旋转。
  3. 之字形旋转:当节点与其父、祖父结点呈异构形状(如左 -右或右 -左)时执行的双旋转。

这些操作使得访问最频繁的结点靠近树结构的根部,从而减少平均访问代价。虽然单次操作可能花费 O(n) 时间,但在 m >= n 次操作的序列中,总时间复杂度为 O(m log₂n),均摊时间复杂度为 O(log₂n)。

// 伸展算法伪代码描述
void splaying(Node* g, Node* p, Node* s) {
    // g 是 p 的父结点,p 是 s 的父结点。算法将 s 移到根结点位置
    while (s != root) {
        if (s->parent == root) {
            // 单旋转
            rotate(s);
        } else if ((s->parent->left == s && s->grandfather->left == s->parent) ||
                   (s->parent->right == s && s->grandfather->right == s->parent)) {
            // 一字形双旋转
            zig_zig(s);
        } else {
            // 之字形双旋转
            zig_zag(s);
        }
    }
}

2. 红黑树介绍

2.1 红黑树的概念

红黑树是一棵二叉搜索树,每个节点增加一个存储位表示颜色(红色或黑色)。通过对路径颜色的约束,确保没有一条路径比其他路径长出两倍,从而实现近似平衡。

2.2 红黑树的性质

  1. 根结点和所有外部结点(NIL)的颜色是黑色。
  2. 从根结点到外部结点的途中没有连续两个结点的颜色是红色。
  3. 所有从根到外部结点的路径上都有相同数目的黑色结点。

基于这些性质,可以推导出红黑树的高度 h 与节点数 n 的关系:h ≤ 2 log₂(n+1)。这意味着搜索、插入、删除操作的时间复杂度均为 O(log₂n)。

2.3 为什么最长路径不超过最短路径的 2 倍?

  1. 最短路径是全黑路径,长度为黑高度 bh。
  2. 由于不能有两个连续红节点,最长路径由一黑一红交替组成,长度约为 2*bh。
  3. 因此,任意路径长度 x 满足 bh ≤ h ≤ 2*bh。

2.4 红黑树的效率

相比 AVL 树,红黑树对平衡的控制没那么严格,插入相同数量的结点时,旋转次数通常更少。这使得它在需要频繁插入/删除且查询性能要求稳定的场景下表现优异,例如 C++ STL 中的 std::map 和 std::set。

3. 红黑树的实现

3.1 红黑树的结构

我们需要定义节点结构,包含键值对、左右指针、父指针以及颜色属性。为了简化边界处理,通常使用哨兵节点(Sentinel Node)代替 NULL。

#include <iostream>
#include <vector>
using namespace std;

enum Color { RED, BLACK };

template<typename K, typename V>
struct RBNode {
    K key;
    V value;
    Color color;
    RBNode* parent;
    RBNode* left;
    RBNode* right;

    RBNode(const K& k, const V& v, Color c = RED)
        : key(k), value(v), color(c), parent(nullptr), left(nullptr), right(nullptr) {}
};

template<typename K, typename V>
class RBTree {
private:
    using Node = RBNode<K, V>;
    Node* root;
    Node* nil; // 哨兵节点

    // 左旋
    void left_rotate(Node* x) {
        Node* y = x->right;
        x->right = y->left;
        if (y->left != nil) y->left->parent = x;
        y->parent = x->parent;
        if (x->parent == nil) root = y;
        else if (x == x->parent->left) x->parent->left = y;
        else x->parent->right = y;
        y->left = x;
        x->parent = y;
    }

    // 右旋
    void right_rotate(Node* y) {
        Node* x = y->left;
        y->left = x->right;
        if (x->right != nil) x->right->parent = y;
        x->parent = y->parent;
        if (y->parent == nil) root = x;
        else if (y == y->parent->left) y->parent->left = x;
        else y->parent->right = x;
        x->right = y;
        y->parent = x;
    }

    // 插入后修复红黑树性质
    void insert_fixup(Node* z) {
        while (z->parent->color == RED) {
            if (z->parent == z->parent->parent->left) {
                Node* uncle = z->parent->parent->right;
                if (uncle->color == RED) {
                    z->parent->color = BLACK;
                    uncle->color = BLACK;
                    z->parent->parent->color = RED;
                    z = z->parent->parent;
                } else {
                    if (z == z->parent->right) {
                        z = z->parent;
                        left_rotate(z);
                    }
                    z->parent->color = BLACK;
                    z->parent->parent->color = RED;
                    right_rotate(z->parent->parent);
                }
            } else {
                Node* uncle = z->parent->parent->left;
                if (uncle->color == RED) {
                    z->parent->color = BLACK;
                    uncle->color = BLACK;
                    z->parent->parent->color = RED;
                    z = z->parent->parent;
                } else {
                    if (z == z->parent->left) {
                        z = z->parent;
                        right_rotate(z);
                    }
                    z->parent->color = BLACK;
                    z->parent->parent->color = RED;
                    left_rotate(z->parent->parent);
                }
            }
        }
        root->color = BLACK;
    }

    // 二叉搜索树插入逻辑
    Node* bst_insert(const K& key, const V& value) {
        Node* parent = nil;
        Node* curr = root;
        while (curr != nil) {
            parent = curr;
            if (key < curr->key) curr = curr->left;
            else if (key > curr->key) curr = curr->right;
            else {
                curr->value = value;
                return curr;
            }
        }
        Node* new_node = new Node(key, value, RED);
        new_node->parent = parent;
        new_node->left = nil;
        new_node->right = nil;
        if (parent == nil) root = new_node;
        else if (key < parent->key) parent->left = new_node;
        else parent->right = new_node;
        return new_node;
    }

public:
    RBTree() {
        nil = new Node(K(), V(), BLACK);
        root = nil;
    }

    ~RBTree() {
        delete nil;
    }

    void insert(const K& key, const V& value) {
        Node* new_node = bst_insert(key, value);
        if (new_node->color == RED) {
            insert_fixup(new_node);
        }
    }

    Node* find(const K& key) const {
        Node* curr = root;
        while (curr != nil) {
            if (key < curr->key) curr = curr->left;
            else if (key > curr->key) curr = curr->right;
            else return curr;
        }
        return nullptr;
    }

    void inorder() const {
        inorder_traversal(root);
        cout << endl;
    }

private:
    void inorder_traversal(Node* node) const {
        if (node == nil) return;
        inorder_traversal(node->left);
        cout << "[" << node->key << ":" << node->value << "," 
             << (node->color == RED ? "红" : "黑") << "] ";
        inorder_traversal(node->right);
    }
};

3.2 红黑树的搜索

红黑树本质是二叉搜索树,搜索过程不需要颜色信息,直接沿路径向下查找即可。时间复杂度为 O(log n)。

3.3 红黑树的插入

插入新节点时,默认标记为红色。如果父节点是黑色,则无需调整;如果父节点是红色,则违反性质,需要通过变色或旋转来修复。主要分为三种情况:

  1. 叔叔节点为红色:变色处理,将祖父节点变为红色,继续向上检查。
  2. 叔叔节点为黑色(单旋):根据节点位置进行单旋并变色。
  3. 叔叔节点为黑色(双旋):先旋转调整为单旋情况,再处理。

3.4 红黑树的验证

验证红黑树是否合法主要检查两点:

  1. 根节点是否为黑色。
  2. 任意路径上的黑色节点数量是否一致,且无连续红色节点。
bool Check(Node* root, int blackNum, const int refNum) {
    if (root == nil) {
        return blackNum == refNum;
    }
    if (root->color == RED && root->parent->color == RED) {
        return false;
    }
    if (root->color == BLACK) blackNum++;
    return Check(root->left, blackNum, refNum) && 
           Check(root->right, blackNum, refNum);
}

3.5 红黑树的删除

删除操作较为复杂。如果被删节点是红色,直接删除不影响黑高度;如果是黑色,则需要重新平衡。通常将删除后的空位视为'双重黑色',通过旋转和变色消除双重黑色,恢复红黑树特性。

4. 相关 OJ 题解析

4.1 二叉搜索树中的插入操作

这是 BST 的基础操作,找到合适位置插入即可。

class Solution {
public:
    TreeNode* insertIntoBST(TreeNode* root, int val) {
        if (!root) return new TreeNode(val);
        TreeNode* cur = root;
        while (cur) {
            if (val < cur->val) {
                if (!cur->left) { cur->left = new TreeNode(val); break; }
                cur = cur->left;
            } else {
                if (!cur->right) { cur->right = new TreeNode(val); break; }
                cur = cur->right;
            }
        }
        return root;
    }
};

4.2 将二叉搜索树变平衡

利用中序遍历得到有序数组,再递归构建平衡树。

class Solution {
private:
    void inorder(TreeNode* root, vector<int>& nums) {
        if (!root) return;
        inorder(root->left, nums);
        nums.push_back(root->val);
        inorder(root->right, nums);
    }
    TreeNode* build(const vector<int>& nums, int start, int end) {
        if (start > end) return nullptr;
        int mid = start + (end - start) / 2;
        TreeNode* node = new TreeNode(nums[mid]);
        node->left = build(nums, start, mid - 1);
        node->right = build(nums, mid + 1, end);
        return node;
    }
public:
    TreeNode* balanceBST(TreeNode* root) {
        vector<int> nums;
        inorder(root, nums);
        return build(nums, 0, nums.size() - 1);
    }
};

4.3 判断是否为平衡二叉树

递归计算高度,若子树不平衡则返回 -1。

class Solution {
private:
    int getHeight(TreeNode* node) {
        if (!node) return 0;
        int leftHeight = getHeight(node->left);
        if (leftHeight == -1) return -1;
        int rightHeight = getHeight(node->right);
        if (rightHeight == -1) return -1;
        if (abs(leftHeight - rightHeight) > 1) return -1;
        return max(leftHeight, rightHeight) + 1;
    }
public:
    bool isBalanced(TreeNode* root) {
        return getHeight(root) != -1;
    }
};

4.4 二叉搜索树迭代器

使用中序遍历将节点值存入数组,模拟迭代器行为。

class BSTIterator {
private:
    vector<int> inorderList;
    int idx;
    void inorder(TreeNode* node) {
        if (!node) return;
        inorder(node->left);
        inorderList.push_back(node->val);
        inorder(node->right);
    }
public:
    BSTIterator(TreeNode* root) {
        inorder(root);
        idx = 0;
    }
    int next() { return inorderList[idx++]; }
    bool hasNext() { return idx < inorderList.size(); }
};

4.5 二叉树中的最大路径和

DFS 遍历,计算以当前节点为最高点的路径和,更新全局最大值。

class Solution {
private:
    int helper(TreeNode* node, int& maxSum) {
        if (!node) return 0;
        int leftContribution = max(helper(node->left, maxSum), 0);
        int rightContribution = max(helper(node->right, maxSum), 0);
        int currentPathSum = node->val + leftContribution + rightContribution;
        maxSum = max(maxSum, currentPathSum);
        return node->val + max(leftContribution, rightContribution);
    }
public:
    int maxPathSum(TreeNode* root) {
        int maxSum = INT_MIN;
        helper(root, maxSum);
        return maxSum;
    }
};

通过以上实现与练习,我们可以更深入地理解平衡二叉搜索树的核心机制及其在实际开发中的应用。

目录

  1. C++ 伸展树与红黑树详解及实现
  2. 1. 伸展树介绍
  3. 核心思想
  4. 旋转类型
  5. 2. 红黑树介绍
  6. 2.1 红黑树的概念
  7. 2.2 红黑树的性质
  8. 2.3 为什么最长路径不超过最短路径的 2 倍?
  9. 2.4 红黑树的效率
  10. 3. 红黑树的实现
  11. 3.1 红黑树的结构
  12. 3.2 红黑树的搜索
  13. 3.3 红黑树的插入
  14. 3.4 红黑树的验证
  15. 3.5 红黑树的删除
  16. 4. 相关 OJ 题解析
  17. 4.1 二叉搜索树中的插入操作
  18. 4.2 将二叉搜索树变平衡
  19. 4.3 判断是否为平衡二叉树
  20. 4.4 二叉搜索树迭代器
  21. 4.5 二叉树中的最大路径和
  • 💰 8折买阿里云服务器限时8折了解详情
  • GPT-5.5 超高智商模型1元抵1刀ChatGPT中转购买
  • 代充Chatgpt Plus/pro 帐号了解详情
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ 搜索引擎通用工具类实现:文件读取与分词集成
  • C++ 测试与调试实战:保障代码质量与稳定性
  • 主流前端动画库实战指南:GSAP、Lottie、Swiper 与 AOS 对比
  • Spring Boot 2.7.18 版本概览与升级建议
  • 微信小程序 WebView 与 H5 页面双向通信实战指南
  • Andrej Karpathy 解析人工智能未来发展策略
  • C++ 继承中的同名成员隐藏规则详解
  • 开源 AI 桌宠 AIRI 部署指南
  • 构建 Vue 全局错误处理体系,实现业务与错误解耦
  • 技术拆解:用 Web Audio API 实现音乐可视化动效
  • 智慧农业-无人机枸杞树病害检测数据集 深度学习框架基于YOLOV8枸杞病害检测系统 无人机智慧农业枸杞病害巡检
  • PicDoc AI 文生图表工具使用教程与 Napkin 对比
  • LangChain 核心组件 RunnableLambda 详解与实战
  • 2026 年 3 月全球 AI 前沿动态与技术突破
  • 2026 年 3 月全球 AI 前沿动态与技术综述
  • 全国大学生智能车竞赛智慧医疗机器人惯导与避障思路分享
  • Unsloth 多场景适配:Llama、Qwen、Gemma 统一微调教程
  • OpenClaw 网络搜索与抓取工具最佳实践指南
  • GitHub Trending AI Top3 项目解析与上手指南
  • 基于 AiOnly 与 N8N 的每日 AI 资讯早报自动化搭建实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online