跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 伸展树与红黑树原理及实现

深入解析伸展树与红黑树两种平衡二叉搜索树。伸展树基于局部性原理,通过旋转优化频繁访问节点;红黑树通过颜色约束确保路径长度平衡,提供稳定的对数级性能,广泛应用于 STL 容器。内容涵盖核心概念、性质证明、插入删除算法细节及完整代码实现,并结合典型算法题进行实战演练。

全栈工匠发布于 2026/3/27更新于 2026/6/819 浏览
C++ 伸展树与红黑树原理及实现

C++ 伸展树与红黑树原理及实现

1. 伸展树介绍

伸展树(Splay Tree)是一种自调整的二叉搜索树,由 John Edward Hopcroft 和 Robert Endre Tarjan 于 1985 年提出。与 AVL 树不同,伸展树不追求严格的平衡,而是利用'局部性原理',将最近访问的节点通过旋转操作移至根节点。

这种策略使得频繁访问的数据始终靠近根部,从而在均摊意义上达到 O(log n) 的时间复杂度。虽然单次操作可能耗时 O(n),但在 m >= n 次操作的序列中,总时间复杂度为 O(m log n)。伸展树非常适合缓存、热点数据查询等场景。

1.1 伸展操作

每当访问(搜索、插入或删除)一个节点 s 时,执行一次'伸展'过程,将其移至根节点。如果删除节点 s,则将其父节点伸展至根。伸展操作包含三种旋转类型:

  1. 单旋转:当被访问节点的父节点是根节点时执行。保持二叉搜索树特性,s 成为新根。
  2. 一字形旋转(Zig-Zig):当 s 与其父节点 p、祖父节点 g 同构(如左 -左或右 -右)时执行。先围绕 p 旋转 g,再围绕 s 旋转 p。
  3. 之字形旋转(Zig-Zag):当 s 与 p、g 异构(如左 -右或右 -左)时执行。先围绕 s 旋转 p,再围绕 s 旋转 g。

之字形旋转有助于减少树的高度,而一字形旋转主要提升访问节点的位置。

// 伪代码逻辑示意
void splaying(Node* s) {
    while (s->parent != nullptr) {
        Node* p = s->parent;
        if (p->parent == nullptr) {
            // 情况 1: 单旋转
            rotate(p);
        } else {
            Node* g = p->parent;
            if ((p->left == s && g->left == p) || (p->right == s && g->right == p)) {
                // 情况 2: 一字形双旋转
                rotate(g); rotate(p);
            } else {
                // 情况 3: 之字形双旋转
                rotate(s); rotate(g);
            }
        }
    }
}

2. 红黑树介绍

2.1 红黑树的概念

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

2.2 红黑树的性质

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

基于这些性质,可以推导出红黑树的高度 h 满足:h ≤ 2 log₂(n + 1)。这意味着增删查改的最坏时间复杂度稳定在 O(log n)。

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

  • 最短路径:全为黑色节点,长度为黑高度 bh。
  • 最长路径:红黑相间,长度为 2 * bh。
  • 由于不存在连续红节点,任何路径长度都在 [bh, 2*bh] 之间。

2.4 红黑树的效率

相比 AVL 树,红黑树对平衡的控制较宽松,插入时的旋转次数更少,更适合写多读少的场景。AVL 树在纯搜索场景下略优,但两者整体效率处于同一档次。

3. 红黑树的实现

3.1 红黑树的结构

我们需要定义节点结构,包含键值对、左右子指针、父指针以及颜色属性。

#pragma once
#include <utility>

enum Color { RED, BLACK };

template<class K, class V>
struct RBTreeNode {
    std::pair<K, V> _kv;
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _parent;
    Color _col;

    RBTreeNode(const std::pair<K, V>& kv)
        : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {}
};

3.2 红黑树的搜索

搜索逻辑与普通二叉搜索树完全一致,无需关心颜色信息。

Node* Find(const K& key) {
    Node* cur = _root;
    while (cur) {
        if (cur->_kv.first < key) {
            cur = cur->_right;
        } else if (cur->_kv.first > key) {
            cur = cur->_left;
        } else {
            return cur;
        }
    }
    return nullptr;
}

3.3 红黑树的插入

插入分为两步:按 BST 规则找到位置并插入,然后修复红黑树性质。

  1. 空树:新节点设为黑色作为根。
  2. 非空树:新节点设为红色。若父节点为黑色,无需调整;若父节点为红色,则违反性质,需根据叔叔节点颜色进行调整。

调整策略:

  • 变色:叔叔节点存在且为红色 -> 父、叔变黑,祖父变红 -> 继续向上处理。
  • 旋转:叔叔节点不存在或为黑色 -> 根据相对位置进行单旋或双旋,并配合变色。

3.4 红黑树的插入代码实现

以下是核心的插入与修复逻辑,包含了旋转函数和颜色调整。

// 右旋
void RotateR(Node* parent) {
    Node* subL = parent->_left;
    Node* subLR = subL->_right;
    
    parent->_left = subLR;
    if (subLR) subLR->_parent = parent;
    
    Node* parentParent = parent->_parent;
    subL->_right = parent;
    parent->_parent = subL;
    
    if (parent == _root) {
        _root = subL;
        subL->_parent = nullptr;
    } else {
        if (parentParent->_left == parent) {
            parentParent->_left = subL;
        } else {
            parentParent->_right = subL;
        }
        subL->_parent = parentParent;
    }
}

// 左旋
void RotateL(Node* parent) {
    Node* subR = parent->_right;
    Node* subRL = subR->_left;
    
    parent->_right = subRL;
    if (subRL) subRL->_parent = parent;
    
    Node* parentParent = parent->_parent;
    subR->_left = parent;
    parent->_parent = subR;
    
    if (parent == _root) {
        _root = subR;
        subR->_parent = nullptr;
    } else {
        if (parentParent->_left == parent) {
            parentParent->_left = subR;
        } else {
            parentParent->_right = subR;
        }
        subR->_parent = parentParent;
    }
}

bool Insert(const std::pair<K, V>& kv) {
    if (_root == nullptr) {
        _root = new RBTreeNode<K, V>(kv);
        _root->_col = BLACK;
        return true;
    }

    Node* parent = nullptr;
    Node* cur = _root;
    while (cur) {
        if (cur->_kv.first < kv.first) {
            parent = cur;
            cur = cur->_right;
        } else if (cur->_kv.first > kv.first) {
            parent = cur;
            cur = cur->_left;
        } else {
            return false; // 已存在
        }
    }

    cur = new RBTreeNode<K, V>(kv);
    cur->_col = RED;
    if (parent->_kv.first < kv.first) {
        parent->_right = cur;
    } else {
        parent->_left = cur;
    }
    cur->_parent = parent;

    // 修复红黑树性质
    while (parent && parent->_col == RED) {
        Node* grandfather = parent->_parent;
        if (grandfather->_left == parent) {
            Node* uncle = grandfather->_right;
            if (uncle && uncle->_col == RED) {
                // 情况 1: 变色
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;
                cur = grandfather;
                parent = cur->_parent;
            } else {
                // 情况 2/3: 旋转 + 变色
                if (cur == parent->_left) {
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    RotateL(parent);
                    RotateR(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        } else {
            Node* uncle = grandfather->_left;
            if (uncle && uncle->_col == RED) {
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;
                cur = grandfather;
                parent = cur->_parent;
            } else {
                if (cur == parent->_right) {
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                } else {
                    RotateR(parent);
                    RotateL(grandfather);
                    cur->_col = BLACK;
                    grandfather->_col = RED;
                }
                break;
            }
        }
    }
    _root->_col = BLACK;
    return true;
}

3.5 红黑树的验证

验证需要检查三条性质:根为黑、无连续红、黑高度一致。

bool Check(Node* cur, int blackNum, const int blackNumRef) {
    if (cur == nullptr) {
        if (blackNum != blackNumRef) {
            return false;
        }
        return true;
    }
    if (cur->_col == RED && cur->_parent && cur->_parent->_col == RED) {
        return false;
    }
    if (cur->_col == BLACK) ++blackNum;
    return Check(cur->_left, blackNum, blackNumRef) && 
           Check(cur->_right, blackNum, blackNumRef);
}

bool IsBalance() {
    if (_root == nullptr) return true;
    if (_root->_col == RED) return false;
    
    int refNum = 0;
    Node* leftMost = _root;
    while (leftMost) {
        if (leftMost->_col == BLACK) ++refNum;
        leftMost = leftMost->_left;
    }
    return Check(_root, 0, refNum);
}

3.6 红黑树的删除

删除逻辑较为复杂,涉及双重黑色的处理。若删除的是红色节点,直接移除即可;若删除的是黑色节点,会导致黑高度减少,需引入'额外黑色'概念并通过旋转和变色消除。

具体包括:

  • 兄弟节点为红色:交换颜色并旋转。
  • 兄弟节点为黑色:根据侄子节点颜色进行变色或旋转,将双重黑色向上传递或消除。

(注:删除实现篇幅较长,核心思路与插入类似,重点在于维护黑高度平衡)

4. 相关算法题实战

为了巩固理解,可以参考以下经典题目进行练习:

  1. 二叉搜索树中的插入操作:考察基本的 BST 插入逻辑。
  2. 将二叉搜索树变平衡:考察如何构建平衡树,通常使用中序遍历转数组再递归构建。
  3. 判断是否为平衡二叉树:考察树的高度计算与平衡因子判断。
  4. 二叉搜索树迭代器:考察中序遍历的非递归实现。
  5. 二叉树中的最大路径和:考察树形 DP 与递归回溯。

这些题目涵盖了从基础插入到复杂平衡调整的各个层面,建议结合手写代码加深理解。

目录

  1. C++ 伸展树与红黑树原理及实现
  2. 1. 伸展树介绍
  3. 1.1 伸展操作
  4. 2. 红黑树介绍
  5. 2.1 红黑树的概念
  6. 2.2 红黑树的性质
  7. 2.3 为什么最长路径不超过最短路径的 2 倍?
  8. 2.4 红黑树的效率
  9. 3. 红黑树的实现
  10. 3.1 红黑树的结构
  11. 3.2 红黑树的搜索
  12. 3.3 红黑树的插入
  13. 3.4 红黑树的插入代码实现
  14. 3.5 红黑树的验证
  15. 3.6 红黑树的删除
  16. 4. 相关算法题实战
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

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

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

更多推荐文章

查看全部
  • LLM 大模型技术实战:入门大模型开发框架 LangChain
  • 黑客圈子真的都是闷声发大财的土豪吗?
  • LLaMA-Factory 本地部署与微调指南
  • C++ 特殊类设计:拷贝控制、内存分配与单例模式
  • FPGA 实现 IIC 通信原理与 Verilog 代码详解
  • Windows 7 系统运行最新 Python 版本的方法
  • 首个检索增强 3D 生成模型 Phidias,统一文生图与 3D 生成任务
  • 基于AIA改进的移相干涉抗振算法
  • DeepSeek-R1 大模型基于 MS-Swift 框架部署、推理与微调实践
  • ComfyUI:AI 绘画与图像生成的高效工作流
  • 云计算与数据科学:突破信息泛滥的 5 步指南(上)
  • VS Code 集成 Overleaf 实现本地 AI 辅助 LaTeX 写作
  • C++ 编程核心机制与工程实践详解
  • Linux 进程间通信实战:命名管道(FIFO)详解
  • Android 开发无工作经验求职指南
  • C++ 栈模拟验证栈序列:LeetCode 946 题解
  • 程序员为何要尽早掌握基础知识与设计模式
  • 企业级黑词分析组件:双面板交互与进度监控实现
  • Midjourney Imagine API 接入指南与实战
  • C++ 继承机制详解:从基础概念到多继承实战

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如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