15.map,set下:AVL树,红黑树和map,set的封装【由浅入深-C++】

文章目录

  • [第一章:平衡的艺术------从零实现 AVL 树](#第一章:平衡的艺术——从零实现 AVL 树)
    • [1. AVL 树的"紧箍咒":定义与性质](#1. AVL 树的“紧箍咒”:定义与性质)
      • [1.1 核心性质](#1.1 核心性质)
    • [2. AVL 树的骨架:底层结构实现](#2. AVL 树的骨架:底层结构实现)
    • [3. 核心重难点:插入与平衡因子的更新](#3. 核心重难点:插入与平衡因子的更新)
      • [3.1 平衡因子的更新逻辑(大师级秘籍)](#3.1 平衡因子的更新逻辑(大师级秘籍))
    • [补充:AVL 插入中旋转的局部性](#补充:AVL 插入中旋转的局部性)
      • [1. 什么是"局部性"?](#1. 什么是“局部性”?)
      • [2. 核心基石:高度的"借"与"还"](#2. 核心基石:高度的“借”与“还”)
      • [3. 单旋与双旋:殊途同归的"高度还原"](#3. 单旋与双旋:殊途同归的“高度还原”)
      • [4. 为什么对比之下,删除操作没有绝对的局部性?](#4. 为什么对比之下,删除操作没有绝对的局部性?)
      • 总结
    • 补充:bf的更新
    • [4. 四大旋转:化腐朽为神奇](#4. 四大旋转:化腐朽为神奇)
      • [招式 1:左单旋 (Left Rotation)](#招式 1:左单旋 (Left Rotation))
      • [招式 2:右单旋 (Right Rotation)](#招式 2:右单旋 (Right Rotation))
      • [招式 3:右左双旋 (RL Rotation)](#招式 3:右左双旋 (RL Rotation))
      • [招式 4:左右双旋 (LR Rotation)](#招式 4:左右双旋 (LR Rotation))
    • [5. 如何验证你的 AVL 树是否正确?](#5. 如何验证你的 AVL 树是否正确?)
      • [补充:AVL 树的查找](#补充:AVL 树的查找)
    • [6. 给小白的避坑指南](#6. 给小白的避坑指南)
      • [祖先节点还没来得及更新 BF 呢,我这层一旋转,难道上面就自动平衡了?](#祖先节点还没来得及更新 BF 呢,我这层一旋转,难道上面就自动平衡了?)
        • [1. 核心逻辑:高度的"抵消"](#1. 核心逻辑:高度的“抵消”)
        • [2. 为什么 Insert 里的 break 如此果断?](#2. 为什么 Insert 里的 break 如此果断?)
        • [3. 一个极端的例子](#3. 一个极端的例子)
        • 小结
    • [7. 完整版代码](#7. 完整版代码)
      • [AVL 树全逻辑详细注释版](#AVL 树全逻辑详细注释版)
      • [💡 核心逻辑画龙点睛](#💡 核心逻辑画龙点睛)
    • [8. 总结:AVL 树的工程意义](#8. 总结:AVL 树的工程意义)
  • 第二章:红黑树------数据结构的珠穆朗玛峰
    • [2.1 红黑树的底层逻辑](#2.1 红黑树的底层逻辑)
    • [2.2 红黑树的"五条军规" (Properties)](#2.2 红黑树的“五条军规” (Properties))
    • [2.3 数据结构定义](#2.3 数据结构定义)
      • [💡 深度思考:为什么新节点默认是红色?](#💡 深度思考:为什么新节点默认是红色?)
    • [2.4 插入操作 (Insert) ------ 核心实战](#2.4 插入操作 (Insert) —— 核心实战)
      • 变量约定
      • [场景 1:叔叔存在且为红 (Uncle is Red) ------ 仅变色](#场景 1:叔叔存在且为红 (Uncle is Red) —— 仅变色)
      • [场景 2:旋转 + 变色 (Uncle 不存在 或 Uncle 为黑)](#场景 2:旋转 + 变色 (Uncle 不存在 或 Uncle 为黑))
        • [💡 核心推导:在本套教学推导中,Uncle 状态与 Cur 来源的关系](#💡 核心推导:在本套教学推导中,Uncle 状态与 Cur 来源的关系)
        • [情况 A:Uncle 不存在](#情况 A:Uncle 不存在)
        • [情况 B:Uncle 存在且为黑](#情况 B:Uncle 存在且为黑)
      • [2.4.3 最终总结](#2.4.3 最终总结)
    • [2.5 代码实现](#2.5 代码实现)
    • [2.6 如何验证你的红黑树是对的?](#2.6 如何验证你的红黑树是对的?)
    • [2.7 完整代码展示](#2.7 完整代码展示)
    • [2.8 总结](#2.8 总结)
  • 第三章:红黑树的封装------泛型编程的艺术
    • [3.1 改造节点结构:从 `<K, V>` 到 `<T>`](#3.1 改造节点结构:从 <K, V><T>)
    • [3.2 核心难点:如何比较大小?(KeyOfT)](#3.2 核心难点:如何比较大小?(KeyOfT))
      • [RBTree 类的改造](#RBTree 类的改造)
    • [3.3 迭代器 (Iterator) 的实现](#3.3 迭代器 (Iterator) 的实现)
    • [3.4 RBTree.h (核心底层)](#3.4 RBTree.h (核心底层))
    • [3.5 封装 MySet](#3.5 封装 MySet)
    • [3.6 封装 MyMap](#3.6 封装 MyMap)
    • [3.7 总结与思考](#3.7 总结与思考)

第一章:平衡的艺术------从零实现 AVL 树

在二叉搜索树(BST)的学习中,我们留下了一个巨大的隐患:如果数据是有序插入的,树会退化成单链表。为了解决这个"看天吃饭"的缺陷,两位苏联数学家 G.M. Adelson-VelskyE.M. Landis 在 1962 年的论文 《An algorithm for the organization of information》 中提出了一种高度平衡 的搜索树,并以两人的名字命名为 AVL 树

1. AVL 树的"紧箍咒":定义与性质

AVL 树之所以能保持平衡,是因为它给每个节点都套上了一个"高度差"的限制。

1.1 核心性质

  1. 搜索树性质 :依然满足 左子树 < 根 < 右子树。
  2. 高度限制左右子树本身也必须是 AVL 树,并且左右子树的高度差(绝对值)不超过 1 。因此,AVL 树可以递归定义为:空树是 AVL 树;非空树在满足二叉搜索树性质的同时,其左右子树都是 AVL 树,且高度差绝对值不超过 1。
  3. 平衡因子 (Balance Factor) :为了实现方便,我们给每个节点引入一个 BF 变量。(本文采用 平衡因子 = 右子树高度 - 左子树高度;采用相反定义也可以,但整套更新与旋转判断必须保持一致。)
  • BF = 0:左右高度相等。
  • BF = 1:右子树比左子树高 1 层。
  • BF = -1:左子树比右子树高 1 层。
  • BF = 2 / -2 :在插入回溯调整的临时状态 中表示该节点已经失衡,必须旋转调整;合法 AVL 树中任一节点最终的 BF 只能是 -1 / 0 / 1

为什么高度差不是强制等于 0? 因为并非所有节点数量都能组成左右等高的二叉树。例如只有 2 个节点或 4 个节点时,不可能让所有节点的左右子树高度差都为 0,因此 AVL 选择"绝对值不超过 1"作为可实现且足够严格的平衡条件。

由此可将 AVL 树高度控制在 O(log N),所以查找、插入等沿树高进行的操作可以保持在 O(log N) 量级。

2. AVL 树的骨架:底层结构实现

为了能方便地"向上回溯"更新平衡因子,本实现采用三叉链 (左、右、父)。_parent 并不是 AVL 树理论定义所强制要求的;也可以通过递归返回、显式栈等方式完成向上回溯,但三叉链在本套实现中最直观。

cpp 复制代码
template<class K, class V>
struct AVLTreeNode {
    pair<K, V> _kv;
    AVLTreeNode<K, V>* _left;
    AVLTreeNode<K, V>* _right;
    AVLTreeNode<K, V>* _parent; // 关键:指向父亲,方便回溯
    int _bf; // balance factor = 右子树高度 - 左子树高度

    AVLTreeNode(const pair<K, V>& kv)//创建新节点
        : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _bf(0)
    {}
};

用于:
Node* newNode = new Node(make_pair(1, "apple")); // 逻辑:数据 -> 节点

3. 核心重难点:插入与平衡因子的更新

插入一个节点分三步:

1.BST 插入: 按大小找到位置,链接新节点(红黑树也是这一步)。

2.更新平衡因子: 这是本 AVL 实现维护平衡的关键步骤,沿着 _parent 指针向上修改受影响祖先的 BF。

3.旋转 (Rotate): 一旦发现 BF 失控,立即执行旋转。

3.1 平衡因子的更新逻辑(大师级秘籍)

当你插入一个新节点时,可能影响从其父节点到根路径上的部分甚至全部祖先的高度;一旦遇到"子树高度不再变化"或完成失衡调整,就可以提前停止:

  • 插入在父节点的右边 ,父节点 _bf++
  • 插入在父节点的左边 ,父节点 _bf--

更新后如何处理?

  • 情况 A:parent->_bf == 0
    • 说明插入前该节点 BF 为 1 或 -1,插入后变平衡了。
    • 高度没变,对parent的上一层无影响,停止更新。
  • 情况 B:parent->_bf == 1-1

    • 说明插入前该节点 BF 为 0,插入后一侧长高了。
    • 高度变了 ,必须继续向上回溯更新 parent->_parent
  • 情况 C:parent->_bf == 2-2

    • 严重失衡! 必须进行旋转,旋转后该局部子树高度会恢复到插入前水平,因此停止继续更新祖先 BF。
    • 一次插入 操作,最多只需要进行一次平衡调整 :若是 LL/RR 型,只做 1 次基础旋转;若是 LR/RL 型,则做 2 次基础旋转组成一次双旋。调整完成后,这棵局部子树的高度恢复到插入前,因此高度增长不再继续向上传导。

补充:AVL 插入中旋转的局部性

这里讨论的 "旋转的局部性" (Locality of Rotation) ,应当严格限定在AVL 树插入导致失衡后的旋转修复 这一语境中:当首次失衡节点完成单旋或双旋后,该局部子树的高度恢复到插入前,因此不需要继续向上调整祖先的 BF。红黑树虽然也使用旋转,但其插入修复还包含可能向上传播的变色过程,不能直接套用这里的"高度恢复即停止"结论。

我们来掰开揉碎了,详细拆解这个概念。

1. 什么是"局部性"?

在计算机科学中,"局部性"通常意味着 "影响范围受限"

对于 AVL 树的插入操作 而言,所谓"旋转的局部性"是指:当插入一个新节点使某个祖先首次出现 BF = ±2 时,只需要对这个最小失衡子树做常数级的指针调整(单旋或双旋),就能让该局部子树恢复平衡,并使其总高度恢复到插入前的水平,因此更高层祖先不需要再因为这次插入继续更新 BF。

2. 核心基石:高度的"借"与"还"

要彻底搞懂局部性,我们必须盯紧一个核心指标:子树的总高度 ( H H H)

假设我们有一棵庞大的平衡树,其中有一个局部子树,其根节点为 A。在插入新节点之前 ,以 A 为根的子树高度为 h h h。

  • 第一步:打破平衡(高度 +1)
    我们在 A 的某条极深的路径上插入了一个新节点。这个新节点导致那条路径变长了。如果这个变长恰好让 A 节点的左右子树高度差达到了 2,A 就成了最小失衡树的根节点
    此时,在尚未进行旋转的这一瞬间,以 A 为根的子树,其总高度暂时 变成了 h + 1 h + 1 h+1。
  • 第二步:旋转修复(高度 -1)
    这是最关键的一步。无论是 LL、RR 单旋,还是 LR、RL 双旋,它们在空间几何上做的本质动作是:把树形压扁
    旋转操作会将原本较深的那一侧的节点"提拉"上来成为新的局部根节点,而把原本的根节点 A "下放"成为子节点。
    经过这样的"压扁"操作,这棵局部子树(由失衡时的全部原节点所组成的子树)的总高度又从 h + 1 h + 1 h+1 退回到了 h h h
  • 第三步:局部性达成(停止传导)
    现在,请站在 A 节点的父节点 (甚至是整棵大树的根节点)的视角来看:
    在插入节点前,我下面的这棵子树高度是 h h h;插入后短暂的变为h+1 ,但插入并旋转完成后,这棵子树的高度依然是 h h h
    既然高度没变,那么父节点的平衡因子(BF)就不会改变,祖父节点也不会改变......平衡的破坏被完美地"消化"在了这个局部范围内,失衡信号停止向上传导。

3. 单旋与双旋:殊途同归的"高度还原"

为了保证"局部性"绝对成立,仅仅有单旋是不够的,这也是为什么我们需要双旋(LR, RL)。

  • 单旋 (LL / RR) 的局部性:
    当新节点像一根直棍子一样插入在外侧(比如左孩子的左子树),我们只需要一次右旋(LL 型)。旋转后,原失衡根与其高侧孩子的上下级关系发生反转:高侧孩子成为新的局部根,原失衡根下沉为其子节点,同时中间子树重新挂接;中序次序保持不变,高度恢复到插入前。
  • 双旋 (LR / RL) 的局部性:
    当新节点插入在内侧(比如左孩子的右子树),树形像一个"拐点"或"膝盖"。如果仅仅做一次单旋,你会发现树的高度并没有降下来 ,失衡依然存在。
    这就是"双旋"发挥作用的地方:先对子节点做一次局部旋转(把"弯"的树形捋直),然后再对根节点做一次反向旋转。两次旋转组合在一起,其最终的数学结果依然是:子树总高度减 1,完美恢复到插入前的 h h h。

无论是哪种情况,算法的终极目标都是在局部找回那个失去的 h h h。

4. 为什么对比之下,删除操作没有绝对的局部性?

理解了插入的局部性,反过来想删除操作,逻辑就更加通透了。

当你在 AVL 树中删除一个节点时:

  1. 删除节点导致某条路径变短。
  2. 为了修复失衡,你进行旋转。
  3. 旋转虽然让当前子树平衡了,但是!旋转后的子树总高度,可能比删除前矮了 1 层。
  4. 一旦子树变矮,对于父节点来说,它的左右子树高度差就改变了,父节点可能因此失衡。
  5. 这就引发了"连锁反应",失衡信号随着高度的降低一路向上传导,导致最坏情况下需要一路旋转到根节点(最多 O(log n)次旋转)。

总结

"旋转的局部性"是算法工程师与数学达成的一次精妙妥协:我们允许局部因为插入而短暂失衡,但通过巧妙的 O ( 1 ) O(1) O(1) 空间几何变换,将由于插入而膨胀的"高度"在局部强行压缩回去。这就是平衡树之所以"平衡"且"高效"的核心秘诀。

补充:bf的更新

插入 AVL:

1. LL/RR 单旋只需将原根 + 新根,两个 BF 全部归 0。

2. LR/RL 双旋中拐点必归 0,拐点原来为 0 则三个全 0;如果拐点原 BF=±1,那么另外两个旧节点中:一个为 0,另一个为非 0;这个非 0 的 BF 一定与拐点旋转前的 BF 符号相反,具体谁取非零,就看拐点较矮的子树旋转后分配给了谁。

4. 四大旋转:化腐朽为神奇

这是 AVL 树最核心的部分,旨在通过改变链接关系,在不改变中序遍历序列的前提下,压低树的高度。

"找到底部第一个失衡节点,拎出它往下的两层路径节点(共祖孙三个),把这三大节点的指针重新接好,一次旋转搞定全局平衡。"

招式 1:左单旋 (Left Rotation)

触发条件 :右边高,且新节点插在右孩子的右边(RR型)

口诀:右孩子做新根,原根变左子,右孩子的左子变原根的右子。

cpp 复制代码
// 左单旋:用于解决 RR 型失衡(插入的节点位于失衡节点的右子树的右侧)
// 这里的 parent 就是底部第一个失衡节点(即我们之前说的"爷爷" z)
void RotateL(Node* parent) 
{
    // ========== 1. 获取关键的三层节点(祖孙三代) ==========
    // subR (y):失衡节点 parent(z)的右孩子,即路径上的"子节点"
    // 旋转完成后,subR 将晋升为这棵子树的"新根"
    Node* subR = parent->_right;
    
    // subRL (y的左子树):它属于"孙辈"节点(x)的左子树(或者说,是介于 parent 和 subR 之间的中间子树)
    // 它的值比 parent 大,但比 subR 小,旋转时需要"过继"给 parent
    Node* subRL = subR->_left;

    // ========== 2. 第一步:将"孙辈的左子树"过继给"爷爷" ==========
    // 将 subRL 挂接到 parent 的右子树位置(因为 subRL 的所有节点都大于 parent)
    // 此时,subRL 成为 parent(z)的右孩子
    parent->_right = subRL;
    // 如果 subRL 非空,更新其父指针指向 parent(认爷爷做父)
    if (subRL) 
        subRL->_parent = parent;

    // ========== 3. 第二步:保存"爷爷"的父节点,准备让"子节点"上位 ==========
    // pParent:记录原 parent(z)的父节点,用于后续将旋转后的子树接回整棵树
    Node* pParent = parent->_parent;

    // ========== 4. 第三步:核心旋转动作,让"子节点(y)"晋升为"新根" ==========
    // subR(y)的左孩子指向 parent(z),即让 z 降级成为 y 的左孩子
    subR->_left = parent;
    // 更新 parent(z)的父指针,现在它的父亲变成了原本的"儿子" subR
    parent->_parent = subR;

    // ========== 5. 第四步:将调整好的新子树(以 subR 为根),接回整棵大树 ==========
    // 判断原 parent 是不是整棵树的根节点
    if (pParent == nullptr) 
    {
        // 如果 pParent 为空,说明原 parent 是整棵树的根节点
        // 现在新根变成 subR(y),将其设为树的 _root,父指针置空
        _root = subR;
        subR->_parent = nullptr;
    } 
    else 
    {
        // 如果 pParent 非空,需要判断原 parent 是 pParent 的左孩子还是右孩子
        // 用 subR(新根)替换掉原来 parent(z)的位置
        if (pParent->_left == parent)
            pParent->_left = subR;  // 原 parent 是左子树,则新根接在左侧
        else
            pParent->_right = subR; // 原 parent 是右子树,则新根接在右侧
        
        // 更新新根 subR 的父指针,指向原来的祖父节点 pParent
        subR->_parent = pParent;
    }

    // ========== 6. 第五步:更新平衡因子 ==========
    // 旋转结束后,以 parent(原 z)和 subR(原 y)为根的子树高度完全一致
    // 因此它们的平衡因子都恢复为 0
    parent->_bf = 0;
    subR->_bf = 0;
}

招式 2:右单旋 (Right Rotation)

触发条件 :左边高,且插在左孩子的左边(LL型)。

逻辑 :与左单旋完全镜像对称。

招式 3:右左双旋 (RL Rotation)

触发条件 :右边高,但插在右孩子的左边(RL型)。

逻辑:先对右孩子做右单旋,再对父节点做左单旋。

三种情况:双旋"拐点"节点的 BF 可能为 -1 / 0 / 1,旋转后的 BF 更新不同

RotateRL(parent) 为例,设 subR = parent->_rightsubRL = subR->_left,并在旋转前保存 bf = subRL->_bf

bf parent->_bf subR->_bf subRL->_bf
0 0 0 0
1 -1 0 0
-1 0 1 0

这也是为什么双旋不能像插入场景下的单旋那样简单地把所有相关 BF 都直接设为 0。

招式 4:左右双旋 (LR Rotation)

触发条件 :左边高,但插在左孩子的右边(针对下图 90 节点而言,即 LR 型,折线形)。

逻辑 :先对左孩子做左单旋,再对父节点做右单旋。

难点 :旋转后的 BF 更新取决于"拐点"节点(subLR)旋转前的 BF。

RotateLR(parent) 为例,设 subL = parent->_leftsubLR = subL->_right,并在旋转前保存 bf = subLR->_bf

bf parent->_bf subL->_bf subLR->_bf
0 0 0 0
-1 1 0 0
1 0 -1 0

5. 如何验证你的 AVL 树是否正确?

作为一个底层大师,不能只写代码,还要会写测试。

  1. 验证搜索性质:中序遍历是否有序。
  2. 验证平衡性质:递归求每个节点的左右高度差,看绝对值是否超过 1。
  3. 验证记录的 BF 是否正确 :重新计算 rightHeight - leftHeight,检查它是否与节点中保存的 _bf 一致。空树本身也应判定为合法 AVL 树。
cpp 复制代码
int _Height(Node* root) {
    if (root == nullptr) return 0;
    return max(_Height(root->_left), _Height(root->_right)) + 1;
}

bool _IsBalance(Node* root) {
    if (root == nullptr) return true;
    int leftH = _Height(root->_left);
    int rightH = _Height(root->_right);
    if (rightH - leftH != root->_bf) {
        cout << root->_kv.first << "节点平衡因子异常" << endl;
        return false;
    }
    return abs(rightH - leftH) < 2 && _IsBalance(root->_left) && _IsBalance(root->_right);
}

补充:AVL 树的查找

AVL 树仍然是一棵二叉搜索树,因此查找逻辑与普通 BST 相同:目标 key 小于当前节点就走左子树,大于当前节点就走右子树,相等则找到。区别不在"查找代码本身",而在于 AVL 通过平衡约束把树高稳定在 O ( log ⁡ N ) O(\log N) O(logN),因此查找最坏复杂度为 O ( log ⁡ N ) O(\log N) O(logN)。完整版代码中的 Find 正是这一逻辑。

6. 给小白的避坑指南

  1. 三叉链的 _parent 链接 :每次旋转不仅要改 _left_right,必须同步更新 _parent。这是新手最容易漏掉的地方,会导致 Insert 回溯时死循环。
  2. 旋转后的 break 一旦旋转完成,这棵子树的高度就回到了插入前的状态,它对祖先不再产生高度影响,所以一定要 break,否则会过度调整。
  3. 空指针判断 :在单旋中,subRLsubLR 可能为空(当树比较简单时),在访问其 _parent 前必须判空。

祖先节点还没来得及更新 BF 呢,我这层一旋转,难道上面就自动平衡了?

答案是:是的,因为祖先节点根本"感觉不到"下面发生了变化。

我们可以把 AVL 树的平衡理解为一种"高度守恒"。

1. 核心逻辑:高度的"抵消"

在 AVL 树中,祖先节点的 BF 是否需要更新,唯一取决于parent 为根的这棵子树的总高度是否发生了变化

  1. 插入前 :假设这棵最小失衡子树在插入前的高度是 H H H。
  2. 插入后(旋转前) :新节点使这棵子树的高度暂时变成 H + 1 H+1 H+1。此时如果不旋转,这 1 层高度增长就会继续影响祖先的 BF。
  3. 旋转后 :无论是单旋还是双旋,这棵局部子树都会恢复平衡,并将高度重新恢复为 H H H

结论:

对于祖先节点来说,它看到的下级子树高度在插入前是 H H H,旋转后依然是 H H H。既然高度没变,尚未更新到的更高层祖先就不需要再因这次插入修改 BF,也不会因为这次局部高度增长而新增失衡。

2. 为什么 Insert 里的 break 如此果断?

请看你代码中逻辑的三个出口,这是理解"祖先是否平衡"的关键:

情况 父节点 BF 更新结果 树的高度变化 处理动作 结果
出口 1 parent->_bf == 0 高度不变 break 插入补齐了矮的一边,祖先不受影响。
出口 2 parent->_bf == 1/-1 高度 +1 continue 必须向上更新,因为这 1 层高度差会传导给祖先。
出口 3 parent->_bf == 2/-2 旋转前 +1,旋转后恢复 旋转并 break 旋转"消化"了多出的高度,祖先完全察觉不到。
3. 一个极端的例子

想象一棵 100 层高的 AVL 树。

  • 你在第 100 层插入了一个新节点。
  • 第 99 层、98 层、97 层的 BF 以前都是 0,现在都变成了 1。这意味着高度在向上传递。
  • 到了第 96 层,BF 变成了 2(失衡)。
  • 此时你执行旋转。
  • 奇迹发生了:第 96 层这棵子树的高度,通过旋转变回了插入前的高度。
  • 对于第 95 层到第 1 层的祖先来说:它们看到的第 96 层子树高度没变,所以它们的 BF 依然维持原样。

这就是为什么 AVL 树的一次插入,最多只需要对第一个失衡祖先做 1 次平衡调整:单旋包含 1 次基础旋转,双旋包含 2 次基础旋转。

小结

Insert 中,旋转不是为了"修补"祖先,而是为了"截断"高度的传递。旋转就像是一个高度灭火器,火(高度增加)烧到这一层被扑灭了,上面的建筑(祖先)自然就是安全的。

7. 完整版代码

在模板类内部,可以省略模板参数;但在模板类外部,必须加上模板参数。如果你习惯把函数的声明和定义分离(虽然模板通常写在同一个 .h 里),在类外部实现时,必须显式指定模板参数,即

template

Box<T>::Box() { // 必须写 Box<T>

// 构造函数定义

}

AVL 树全逻辑详细注释版

cpp 复制代码
#include <iostream>
#include <cassert>   // 提供 assert 宏
#include <algorithm> // 提供 swap 和 max
#include <vector>    // 用于压力测试
#include <ctime>     // 用于随机种子
#include <utility>   // pair 类型
using namespace std;

// ======================================================================
// 1. AVL 树节点结构
// ======================================================================
template<class K, class V>
struct AVLTreeNode
{
    pair<K, V> _kv;                     // 键值对
    AVLTreeNode<K, V>* _left;           // 左孩子
    AVLTreeNode<K, V>* _right;          // 右孩子
    AVLTreeNode<K, V>* _parent;         // 父节点(旋转和回溯必须)
    int _bf;                            // 平衡因子 = 右子树高度 - 左子树高度

    AVLTreeNode(const pair<K, V>& kv)
        : _kv(kv)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _bf(0)
    {}
};

// ======================================================================
// 2. AVL 树类定义
// ======================================================================
template<class K, class V>
class AVLTree
{
    typedef AVLTreeNode<K, V> Node;

public:
    // -------------------- 构造 / 析构 / 拷贝 / 赋值 --------------------
    AVLTree() = default;   // 默认构造,_root 被初始化为 nullptr

    // 拷贝构造(深拷贝)
    AVLTree(const AVLTree<K, V>& t)
    {
        _root = Copy(t._root);
    }

    // 赋值运算符重载(现代写法:传值 + swap)
    AVLTree<K, V>& operator=(AVLTree<K, V> t)
    {
        swap(_root, t._root);   // 交换根指针,t 析构时释放旧资源
        return *this;
    }

    // 析构函数
    ~AVLTree()
    {
        Destroy(_root);
        _root = nullptr;
    }

    // -------------------- 核心插入操作 --------------------
    bool Insert(const pair<K, V>& kv)
    {
        // 1. 空树直接作为根
        if (_root == nullptr)
        {
            _root = new Node(kv);
            return true;
        }

        // 2. 标准 BST 插入(找位置)
        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;   // Key 已存在,不允许重复
            }
        }

        // 3. 链接新节点
        cur = new Node(kv);
        if (parent->_kv.first < kv.first)
            parent->_right = cur;
        else
            parent->_left = cur;
        cur->_parent = parent;

        // ============================================================
        // 4. 更新平衡因子 + 旋转调整(AVL 的精髓)
        //    插入节点只影响其祖先的平衡因子,必须从父节点向上回溯
        // ============================================================
        while (parent)   // 一直向上直到根
        {
            // ---------- 4.1 更新当前父节点的平衡因子 ----------
            // 根据 cur 是左孩子还是右孩子,决定 _bf 的变化方向
            if (cur == parent->_left)
                parent->_bf--;   // 左子树增高,平衡因子减 1
            else
                parent->_bf++;   // 右子树增高,平衡因子加 1

            // ---------- 4.2 根据更新后的 _bf 值分类处理 ----------
            if (parent->_bf == 0)
            {
                // 情况 A:_bf 从 ±1 变为 0
                // 说明原本一边高,现在被新节点填平了,子树高度不变
                // 因此不需要继续向上更新,直接跳出循环
                break;
            }
            else if (parent->_bf == 1 || parent->_bf == -1)
            {
                // 情况 B:_bf 从 0 变为 ±1
                // 说明原本平衡,现在变成一边高,子树高度增加了 1
                // 需要继续向上通知祖先节点
                cur = parent;
                parent = parent->_parent;
            }
            else if (parent->_bf == 2 || parent->_bf == -2)
            {
                // 情况 C:_bf 变为 ±2,严重失衡,必须旋转
                // 此时 parent 就是**第一个失衡节点(底部失衡点)**
                // 根据其孩子 cur 的 _bf 判断旋转类型

                // ---- C1: 右右型(RR)----
                // parent 右高 2,且 cur(右孩子)也右高 1
                if (parent->_bf == 2 && cur->_bf == 1)
                {
                    RotateL(parent);   // 左单旋
                }
                // ---- C2: 左左型(LL)----
                else if (parent->_bf == -2 && cur->_bf == -1)
                {
                    RotateR(parent);   // 右单旋
                }
                // ---- C3: 右左型(RL)----
                // parent 右高 2,但 cur(右孩子)左高 1(折线)
                else if (parent->_bf == 2 && cur->_bf == -1)
                {
                    RotateRL(parent);  // 右左双旋
                }
                // ---- C4: 左右型(LR)----
                // parent 左高 2,cur(左孩子)右高 1(折线)
                else   // parent->_bf == -2 && cur->_bf == 1
                {
                    RotateLR(parent);  // 左右双旋
                }

                // 旋转后,以 parent 为根的子树高度恢复到插入前的水平
                // 因此祖先的平衡因子无需再更新,直接退出循环
                break;
            }
            else
            {
                // 正常情况下 _bf 不会超出 [-2, 2],若触发则说明程序有 Bug
                assert(false);
            }
        }

        return true;
    }

    // -------------------- 查找 --------------------
    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;
    }

    // -------------------- 遍历与测试接口 --------------------
    void InOrder()
    {
        _InOrder(_root);
        cout << endl;
    }

    int Height()
    {
        return _Height(_root);
    }

    bool IsBalance()
    {
        return _IsBalance(_root);
    }

private:
    // ---------- 内部辅助函数 ----------
    void _InOrder(Node* root)
    {
        if (!root) return;
        _InOrder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << " ";
        _InOrder(root->_right);
    }

    int _Height(Node* root)
    {
        if (!root) return 0;
        return max(_Height(root->_left), _Height(root->_right)) + 1;
    }

    // 递归检查平衡性(同时校验 _bf 是否与真实高度差一致)
    bool _IsBalance(Node* root)
    {
        if (!root) return true;

        int leftH = _Height(root->_left);
        int rightH = _Height(root->_right);
        int diff = rightH - leftH;

        if (abs(diff) >= 2)
        {
            cout << "失衡节点: " << root->_kv.first << " 高度差=" << diff << endl;
            return false;
        }
        if (diff != root->_bf)
        {
            cout << "BF 记录错误: " << root->_kv.first
                 << " 实际=" << diff << " 记录=" << root->_bf << endl;
            return false;
        }
        return _IsBalance(root->_left) && _IsBalance(root->_right);
    }

    // ---------- 旋转实现(带祖孙三代角色说明) ----------

    // ==================================================================
    // 左单旋(RR 型)
    // 场景:parent(z)失衡,右孩子 subR(y)也偏高
    //       parent->_bf == 2 且 subR->_bf == 1
    // 操作:使 subR(y)上升为新根,parent(z)降为其左孩子
    // ==================================================================
    void RotateL(Node* parent)
    {
        // 祖孙三代:
        //   parent  ------ 失衡节点(z,爷爷)
        //   subR    ------ 右孩子(y,爸爸)
        //   subRL   ------ subR 的左孩子(属于孙辈节点 x 的左子树)
        Node* subR = parent->_right;
        Node* subRL = subR->_left;

        // 1. 将 subRL 过继给 parent 作为右子树
        //    因为 subRL 的所有值都大于 parent,小于 subR
        parent->_right = subRL;
        if (subRL)
            subRL->_parent = parent;

        // 2. 让 parent 成为 subR 的左孩子
        Node* parentParent = parent->_parent;   // 保存更高层的祖先
        subR->_left = parent;
        parent->_parent = subR;

        // 3. 将 subR 与原祖先连接(subR 成为新的子树根)
        if (parentParent == nullptr)
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (parent == parentParent->_left)
                parentParent->_left = subR;
            else
                parentParent->_right = subR;
            subR->_parent = parentParent;
        }

        // 4. 更新平衡因子(旋转后 parent 和 subR 高度一致,均为 0)
        parent->_bf = subR->_bf = 0;
    }

    // ==================================================================
    // 右单旋(LL 型)
    // 场景:parent 左高,左孩子也左高(对称镜像)
    // ==================================================================
    void RotateR(Node* parent)
    {
        // 祖孙:parent(z),subL(y),subLR(孙辈 x 的右子树)
        Node* subL = parent->_left;
        Node* subLR = subL->_right;

        // 1. subLR 过继给 parent 作为左子树
        parent->_left = subLR;
        if (subLR)
            subLR->_parent = parent;

        // 2. parent 成为 subL 的右孩子
        Node* parentParent = parent->_parent;
        subL->_right = parent;
        parent->_parent = subL;

        // 3. subL 上位为新根
        if (parentParent == nullptr)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (parent == parentParent->_left)
                parentParent->_left = subL;
            else
                parentParent->_right = subL;
            subL->_parent = parentParent;
        }

        // 4. 更新平衡因子
        parent->_bf = subL->_bf = 0;
    }

    // ==================================================================
    // 右左双旋(RL 型)
    // 场景:parent 右高 2,但右孩子 subR 左高 1(折线)
    //       先对 subR 进行右旋(使其变成 RR 型),再对 parent 左旋
    // ==================================================================
    void RotateRL(Node* parent)
    {
        // 关键节点:
        //   parent   ------ 失衡点(z)
        //   subR     ------ parent 的右孩子(y)
        //   subRL    ------ subR 的左孩子(孙辈 x),它的平衡因子决定最终校正值
        Node* subR = parent->_right;
        Node* subRL = subR->_left;
        int bf = subRL->_bf;   // 记录旋转前 subRL 的 BF

        // 1. 对 subR 右旋(将折线拉直)
        RotateR(parent->_right);
        // 2. 对 parent 左旋(最终平衡)
        RotateL(parent);

        // 3. 修正平衡因子(根据 subRL 原先的情况分类)
        //    因为双旋后,parent、subR、subRL 的 BF 都需要重新设置
        if (bf == 0)
        {
            // 如果 subRL 本身是新增节点(或高度为 0),则三节点都平衡
            subR->_bf = 0;
            subRL->_bf = 0;
            parent->_bf = 0;
        }
        else if (bf == 1)
        {
            // 新节点插在 subRL 的右边(右子树高)
            // 此时 parent 分到了较矮的左子树,故 BF = -1
            subR->_bf = 0;
            subRL->_bf = 0;
            parent->_bf = -1;
        }
        else if (bf == -1)
        {
            // 新节点插在 subRL 的左边(左子树高)
            // 此时 subR 分到了较矮的右子树,故 BF = 1
            subR->_bf = 1;
            subRL->_bf = 0;
            parent->_bf = 0;
        }
        else
        {
            assert(false);
        }
    }

    // ==================================================================
    // 左右双旋(LR 型)
    // 场景:parent 左高 2,左孩子 subL 右高 1(镜像)
    //       先对 subL 左旋,再对 parent 右旋
    // ==================================================================
    void RotateLR(Node* parent)
    {
        Node* subL = parent->_left;
        Node* subLR = subL->_right;
        int bf = subLR->_bf;   // 记录旋转前 subLR 的 BF

        // 1. 对 subL 左旋
        RotateL(parent->_left);
        // 2. 对 parent 右旋
        RotateR(parent);

        // 3. 修正平衡因子(与 RL 对称)
        if (bf == 0)
        {
            subL->_bf = 0;
            subLR->_bf = 0;
            parent->_bf = 0;
        }
        else if (bf == -1)   // 新节点插在 subLR 的左边
        {
            subL->_bf = 0;
            subLR->_bf = 0;
            parent->_bf = 1;   // parent 分到了较矮的右子树
        }
        else if (bf == 1)    // 新节点插在 subLR 的右边
        {
            subL->_bf = -1;   // subL 分到了较矮的左子树
            subLR->_bf = 0;
            parent->_bf = 0;
        }
        else
        {
            assert(false);
        }
    }

    // ---------- 销毁与拷贝 ----------
    void Destroy(Node* root)
    {
        if (!root) return;
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }

    // 深拷贝:递归复制节点,并正确重建父指针
    Node* Copy(Node* root)
    {
        if (!root) return nullptr;

        Node* newRoot = new Node(root->_kv);
        newRoot->_bf = root->_bf;

        newRoot->_left = Copy(root->_left);
        newRoot->_right = Copy(root->_right);

        // 重建父指针(关键!)
        if (newRoot->_left)
            newRoot->_left->_parent = newRoot;
        if (newRoot->_right)
            newRoot->_right->_parent = newRoot;

        return newRoot;
    }

private:
    Node* _root = nullptr;
};

// ======================================================================
// 3. 测试代码
// ======================================================================
void TestAVLTree()
{
    AVLTree<int, int> t;
    int a[] = {16, 3, 7, 11, 9, 26, 18, 14, 15};
    for (auto e : a)
    {
        t.Insert({e, e});
    }
    t.InOrder();   // 输出升序
    cout << "高度: " << t.Height() << endl;
    cout << "是否平衡: " << (t.IsBalance() ? "是" : "否") << endl;
}

void TestAVLTree_Advanced()
{
    srand((unsigned)time(0));
    const int N = 10000;
    vector<int> v;
    v.reserve(N);
    for (int i = 0; i < N; ++i)
        v.push_back(rand() + i);

    AVLTree<int, int> t;
    for (auto e : v)
        t.Insert({e, e});

    cout << "\n--- 高压测试 ---\n";
    cout << "插入 " << N << " 个随机数\n";
    cout << "树高: " << t.Height() << endl;
    cout << "平衡性: " << (t.IsBalance() ? "通过" : "失败") << endl;
}

int main()
{
    TestAVLTree();
    // TestAVLTree_Advanced();   // 默认注释,可取消以进行压力测试
    return 0;
}

💡 核心逻辑画龙点睛

  1. 三叉链的"爱恨情仇" :在单旋逻辑中(如 RotateL),我详细注释了三个核心步骤。最容易错的是第三步------处理与上层建筑的连接 。如果 parent 原本是整棵树的根,旋转后你必须更新 _root;如果它只是个子树,你必须更新它爷爷的孩子指针。
  2. 双旋的"谍战博弈" :双旋后谁成了新根?是原本那个处于转折点位置的节点。在 RotateRL 中,subRL 最终被提拔到了最高位,它原本携带的孩子分别分给了原来的爸爸和原来的爷爷,所以需要根据 bf 的值(即它的孩子原本在左还是在右)来手动校正。
  3. assert(false) 的意义:作为底层大师,这是你的防线。如果在回溯更新中 BF 超出了 -2, 2 的范围,说明逻辑在之前的步骤就出错了,必须立刻定位问题。

8. 总结:AVL 树的工程意义

  • 时间复杂度 :由于 AVL 树高度为 O ( log ⁡ N ) O(\log N) O(logN),查找、插入、删除的最坏时间复杂度均为 O ( log ⁡ N ) O(\log N) O(logN)。
  • 代价 :AVL 对高度平衡控制更严格。插入 最多只需要对第一个失衡点做一次平衡调整(单旋或双旋);删除 则可能因为子树高度持续降低而向上级联调整,最坏可发生 O ( log ⁡ N ) O(\log N) O(logN) 次旋转。
  • 现状 :在许多通用有序关联容器实现中,更常见的是平衡条件稍宽松、更新时旋转通常更少的红黑树 。例如 SGI STL 的 map/set 复用红黑树;但 C++ 标准本身只规定容器语义和复杂度,并不强制 std::map/std::set 必须使用红黑树。AVL 依然是学习平衡搜索树的最佳练兵场。

第二章:红黑树------数据结构的珠穆朗玛峰

在上一篇中,我们手撕了二叉搜索树(BST)。BST 的查找复杂度本质上取决于树高 h h h,即 O ( h ) O(h) O(h):在较平衡时可达到 O ( log ⁡ N ) O(\log N) O(logN),但在有序或接近有序的数据插入下可能退化成单链表,此时树高变为 N N N,查找最坏退化至 O ( N ) O(N) O(N)。

为了解决这个问题,Adelson-Velsky 和 Landis 提出了 AVL 树 (高度平衡二叉搜索树)。它对左右子树高度差控制得非常严格(绝对值不超过 1):插入时最多在第一个失衡点做一次平衡调整,但删除时可能出现向上的级联调整,因此相较红黑树通常需要维护更严格的平衡信息。

于是,红黑树 (Red-Black Tree) 应运而生。它是一种通过颜色约束实现近似平衡的二叉搜索树许多 C++ 标准库实现中的 std::mapstd::set 采用红黑树作为底层结构; 不过C++ 标准并未强制规定必须使用红黑树。

2.1 红黑树的底层逻辑

红黑树不追求"绝对高度平衡",而是追求 "大致平衡" 。它通过一套颜色规则,保证了以下核心定律:

核心法则: 通过颜色约束,确保从根到各个 NIL/NULL 叶子位置 的路径中,最长路径不会超过最短路径的 2 倍。

由于从同一节点到各个 NIL 的路径具有相同的黑高,并且红节点不能连续出现,所以在极端情况下,较短路径可以近似看作"全黑",较长路径可以近似看作"红黑交替"。因此,红节点数量不会超过同一路径上的黑节点数量,最长路径至多约为最短路径的 2 倍。

这意味着红黑树的高度依然保持在 O ( log ⁡ N ) O(\log N) O(logN) 量级,因此查找、插入、删除的最坏时间复杂度都可以保持在 O ( log ⁡ N ) O(\log N) O(logN)。与 AVL 相比,红黑树对平衡的要求更宽松,通常能够减少更新过程中的旋转次数。

从黑高角度可以进一步理解:若某条最短根到 NIL 路径包含约 bh 个黑节点,那么因为不能有连续红节点,最长路径至多约为 2 * bh;而一棵包含 N N N 个内部节点的红黑树高度仍被约束在对数量级。

2.2 红黑树的"五条军规" (Properties)

实现红黑树之前,必须理解这 5 条性质。后续所有的代码逻辑(变色、旋转),全是为了维护这些规则不被破坏。

  1. 节点颜色 :每个节点不是 红色 就是 黑色
  2. 根节点 :根节点必须是 黑色
  3. 叶子黑 :所有的 NIL 节点(即空指针 nullptr)被视为 黑色。(用于计算路径)
  4. 不红红 :如果一个节点是 红色,则它的两个子节点必须是 黑色 。(这意味着路径上不能出现连续的红色节点)。
  5. 黑高同 :从任一节点到其每个叶子的所有路径,都包含相同数目黑色 节点。

2.3 数据结构定义

为了方便后续实现 mapset 的迭代器,我们的节点结构采用三叉链(包含指向父节点的指针)。

cpp 复制代码
enum Colour {
    RED,
    BLACK
};

template<class T>
struct RBTreeNode {
    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent; // 指向父节点,方便回溯
    
    T _data;
    Colour _col;

    RBTreeNode(const T& data)
        : _left(nullptr), _right(nullptr), _parent(nullptr)
        , _data(data)
        , _col(RED) // 重点:新插入的节点默认是红色
    {}
};

💡 深度思考:为什么新节点默认是红色?

  • 如果在非空树中把普通新节点插成黑色 :会使所有经过该新节点的路径黑高 +1,而其他路径不变,从而直接破坏性质 5(黑高同)。空树插入第一个根节点是特例,根最终必须为黑色。
  • 如果插入红色 :可能会破坏性质 4(不红红)。但这只在"父节点也是红色"时才会发生。如果父节点是黑的,插入红色完全没问题。调整连续红节点的代价通常比调整全局黑高要小。

2.4 插入操作 (Insert) ------ 核心实战

红黑树的插入过程分为两步:

  1. BST 插入:按照二叉搜索树的规则,找到位置并插入新节点(红色)。
  2. 自平衡调整 :如果插入后,父节点也是红色,违反了"不红红"规则,需要通过变色旋转来修复。

(来源见水印,以下图片均来源于水印处)

变量约定

为了方便描述,我们约定:

  • cur:当前节点(新插入的,或向上调整时的当前节点)。
  • p (Parent):父节点。
  • g (Grandfather):祖父节点。
  • u (Uncle):叔叔节点(父节点的兄弟)。

调整的核心也就是看叔叔 u 的脸色。 所有的讨论基于 p 是红色(需要调整)这一前提。

在这场博弈中,祖父节点 (g) 一定是黑色的 (否则插入前就已非法),父节点 (p) 一定是红色的 (否则无需调整)。

因此,决定战术的关键变量,在于叔叔节点 (u) 的状态

我们分两种核心场景讨论:

场景 1:叔叔存在且为红 (Uncle is Red) ------ 仅变色

情况描述

cur 为红,parent 为红,uncle 存在且也为红色

操作逻辑

  1. parentuncle 变为 黑色
  2. grandfather 变为 红色

原理解析

这样变色后,经过 parent 一侧和经过 uncle 一侧的路径都各自增加 1 个黑节点 ;同时把原本为黑的 grandfather 变红,又减少 1 个黑节点,因此从 grandfather 上方看,这棵子树的黑高整体保持不变。

潜在问题(连锁反应)

虽然局部平衡解决了,但 grandfather 变红了。

  • 如果 grandfather 是根节点,直接把它再染黑即可。
  • 如果 grandfather 的父节点也是红色,那么变色后会再次出现连续的红色节点

应对策略

我们需要继续向上调整 。将 grandfather 当作新的 cur,重新找到新的 parentuncle,继续循环判断是变色还是旋转,直到遇到根节点或者不再出现连续红节点为止。

总结 :在当前 parent 已经为红、需要修复连续红的前提下,只要 uncle 也为红,策略就是 "变色 + 向上继续检查" 。局部不需要旋转,但 grandfather 变红后可能与更高层形成新的连续红。

场景 2:旋转 + 变色 (Uncle 不存在 或 Uncle 为黑)

uncle 不是红色时,单纯的变色已经无法解决问题,必须引入旋转 来改变树的拓扑结构。在分类讨论之前,我们需要先厘清一个极具深度的逻辑:cur 节点到底是谁?

💡 核心推导:在本套教学推导中,Uncle 状态与 Cur 来源的关系

在这套插入实现的循环中,一个节点被标记为 cur,有两种来源:要么它是刚 new 出来的新增节点 ,要么它是场景 1 变色后将 grandfather 上移得到的继续调整节点 。下面关于 unclecur 来源的推导,是针对该实现和图示中的黑高层次关系而言。

  1. 当 Uncle 不存在时 Cur 一定是新增节点

    • 原因 :如果 cur 不是新增节点,而是由下方变色上来的,那意味着 cur 下面的路径上至少包含了一个黑色节点(黑高至少为1)。而 uncle 不存在,意味着从 grandfatheruncle 走是直接到 NULL,黑高为 0。这导致左右黑高不一致,说明在插入前这棵树就已经不是红黑树了。所以,uncle 不存在时,cur 只能是刚插入的那个新节点。
  2. 当 Uncle 为黑色时 Cur 一定是向上调整来的节点

    • 原因uncle 是黑色,说明从 grandfatheruncle 的路径上至少有 1 个黑节点。如果 cur 是新增节点(原本路径没黑节点),那么插入位置的路径黑高比 uncle 这边少,这违反了性质 5。因此,cur 所在的子树原本一定包含黑色节点,只是因为下层的"场景1"变色,导致 cur 变成了红色。

理清了这个逻辑,我们将场景 2 细分为两种情况讨论:

情况 A:Uncle 不存在

由推导可知,此时 cur新增节点

为什么不能仅变色?

如果直接把 parent 变黑、grandfather 变红,那么 parent 这边的黑高变成了 1(有一个黑节点),而 uncle (NULL) 那边的黑高依然是 0。黑高失衡,违反性质 5。

解决方案:旋转 + 变色

根据三个节点(grandfather, parent, cur)的相对位置,分单旋和双旋:

  1. 单旋 + 变色(直线型)
  • 形态curparent 的左孩子,parentgrandfather 的左孩子(LL型);或者对称的 RR 型。
  • 操作 :以 grandfather 为轴进行旋转(LL做右旋)。然后Parent 变黑,Grandfather 变红
  1. 双旋 + 变色(折线型)
  • 形态curparent 的右孩子,parentgrandfather 的左孩子(LR型);或者对称的 RL 型。
  • 操作 :先以 parent 为轴旋转(左旋),变成直线型;再以 grandfather 为轴旋转(右旋)。最后Cur 变黑,Grandfather 变红

结果 :旋转变色后,该子树的根变成了黑色,且黑高恢复正常。无需继续向上调整,插入结束。

情况 B:Uncle 存在且为黑

注意:在插入修复的分支选择 上,"Uncle 不存在(nullptr,按黑色 NIL 处理)"和"Uncle 存在且为黑色"都进入 旋转 + 变色 这一大类,因此主体处理逻辑可以复用;但二者在上面的 cur 来源推导中仍有不同背景。

由推导可知,此时 cur向上调整来的节点(原本是黑的,被下层变色染红了)。

虽然 cur 的来源不同,但处理逻辑与"Uncle 不存在"的情况完全一致

  1. 直线型 (LL/RR)单旋 。操作完成后,Parent 变黑,Grandfather 变红
  2. 折线型 (LR/RL)双旋 。操作完成后,Cur 变黑,Grandfather 变红

结果 :该子树的新根节点变为黑色,阻断了红色的向上传播,无需继续向上调整,插入结束。

2.4.3 最终总结

红黑树的插入调整逻辑可以用以下流程图概括:

1. 看 Uncle 的颜色
2. Uncle 为红 变色 (p, u 变黑, g 变红) 向上调整 (Continue)。
3. Uncle 为黑 / 不存在 旋转 + 变色 (单旋或双旋) 停止调整 (Break)。
4. 注意:无论经过何种调整,最后一定要将整棵树的根节点强制设为黑色。

2.5 代码实现

以下是红黑树插入操作的精简核心代码。

cpp 复制代码
// ----------------------------------------------------------------------
// 红黑树核心插入函数
// 性质回顾(辅助理解代码):
// 1. 每个节点要么红,要么黑
// 2. 根节点必须是黑
// 3. 所有 NIL 叶子节点视为黑
// 4. 如果一个节点是红的,它的两个孩子必须是黑的(不能有连续红节点)
// 5. 从任一节点到其每个叶子的所有路径,包含相同数量的黑节点(黑高一致)
// ----------------------------------------------------------------------
bool Insert(const T& data) {
    // --- 1. 处理空树情况 ---
    if (_root == nullptr) {
        _root = new Node(data);
        _root->_col = BLACK; // 性质2:根节点必须是黑色
        return true;
    }

    // --- 2. 标准 BST 搜索插入位置 ---
    Node* parent = nullptr; // 记录 cur 的父节点
    Node* cur = _root;
    while (cur) {
        parent = cur;
        if (data < cur->_data) 
            cur = cur->_left;  // 比当前小,往左走
        else if (data > cur->_data) 
            cur = cur->_right; // 比当前大,往右走
        else 
            return false;      // 键值已存在,插入失败(红黑树/AVL树通常不允许冗余)
    }

    // --- 3. 链接新节点 ---
    cur = new Node(data);
    cur->_col = RED; // 【关键策略】新节点默认染红色
                      // 原因:染红色不会违反性质5(黑高不变),只会可能违反性质4(连续红)
                      // 如果染黑色,会导致所有经过该节点的路径黑高+1,性质5直接崩溃,修复难度极大
    
    if (data < parent->_data) 
        parent->_left = cur;
    else 
        parent->_right = cur;
    cur->_parent = parent; // 三叉链:别忘了维护父指针

    // --- 4. 红黑树性质修复(核心逻辑) ---
    // 循环条件:只要父节点存在且是红色,就违反了性质4(红父不能有红子),必须继续调整
    while (parent && parent->_col == RED) {
        Node* grandfather = parent->_parent; // 祖父节点一定存在(因为父是红,性质4要求父的父必须是黑)

        // =============================================================
        // 【左半区】父节点是祖父的左孩子
        // =============================================================
        if (parent == grandfather->_left) {
            Node* uncle = grandfather->_right; // 叔叔节点是祖父的右孩子

            // ---------------------------------------------------------
            // Case 1:叔叔存在且为红色
            // 策略:变色即可,无需旋转
            // 原理:
            // 1. 父变黑、叔变黑:解决 cur 和 parent 的连续红问题
            // 2. 祖父变红:为了保持黑高不变(父+叔都变黑了,黑高会+2,祖父变红抵消+1)
            // 3. 向上迭代:祖父变红后,可能和它的父节点形成新的连续红,所以把 cur 移到祖父继续检查
            // ---------------------------------------------------------
            if (uncle && uncle->_col == RED) {
                parent->_col = BLACK;      // 父变黑
                uncle->_col = BLACK;       // 叔变黑
                grandfather->_col = RED;    // 祖父变红(牺牲自己,维持黑高)

                // 向上回溯,把祖父当作新的 cur,继续检查上层
                cur = grandfather;
                parent = cur->_parent;
            } 
            // ---------------------------------------------------------
            // Case 2 & 3:叔叔不存在 (NIL) 或叔叔为黑色
            // 策略:必须通过旋转调整结构
            // 原因:叔叔是黑的,如果只变色,会导致祖父这边的黑高变化,性质5崩溃
            // ---------------------------------------------------------
            else {
                // -----------------------------------------------------
                // Case 3:LR 型(折线形:cur 是 parent 的右孩子)
                // 策略:先对 parent 左旋,把折线"拉直"成 LL 型
                // 然后 swap(parent, cur):交换指针,让后续可以统一用 Case 2 的 LL 逻辑处理
                // -----------------------------------------------------
                if (cur == parent->_right) {
                    RotateL(parent);        // 左旋 parent,cur 变成 parent 的父节点
                    std::swap(parent, cur); // 交换后,parent 指向原来的 cur,cur 指向原来的 parent
                                            // 目的:统一后续代码逻辑,不需要再写一套 LR 的旋转变色
                }
                
                // -----------------------------------------------------
                // Case 2:LL 型(直线形:cur 是 parent 的左孩子)
                // 策略:右旋祖父 + 变色
                // 原理:
                // 1. 右旋祖父:让 parent 成为子树的新根,把连续红节点"拆开"
                // 2. parent 变黑:作为新根,修复连续红问题
                // 3. 祖父变红:作为 parent 的右孩子,维持黑高不变
                // 4. break:旋转后 parent 变黑了,子树根是黑的,不会影响上层祖先,直接结束
                // -----------------------------------------------------
                RotateR(grandfather);       // 右旋祖父
                parent->_col = BLACK;        // 新根变黑
                grandfather->_col = RED;     // 旧根变红
                break;                       // 修复完成,无需向上回溯
            }
        }
        // =============================================================
        // 【右半区】父节点是祖父的右孩子(与左半区完全镜像对称)
        // =============================================================
        else {
            Node* uncle = grandfather->_left; // 叔叔节点是祖父的左孩子
            
            // ---------------------------------------------------------
            // Case 1:叔叔存在且为红(镜像逻辑)
            // ---------------------------------------------------------
            if (uncle && uncle->_col == RED) {
                parent->_col = BLACK;
                uncle->_col = BLACK;
                grandfather->_col = RED;
                cur = grandfather;
                parent = cur->_parent;
            } 
            // ---------------------------------------------------------
            // Case 2 & 3:叔叔不存在或为黑(镜像逻辑)
            // ---------------------------------------------------------
            else {
                // -----------------------------------------------------
                // Case 3:RL 型(折线形:cur 是 parent 的左孩子)
                // 策略:先右旋 parent,拉直成 RR 型,再 swap
                // -----------------------------------------------------
                if (cur == parent->_left) {
                    RotateR(parent);
                    std::swap(parent, cur);
                }
                // -----------------------------------------------------
                // Case 2:RR 型(直线形:cur 是 parent 的右孩子)
                // 策略:左旋祖父 + 变色
                // -----------------------------------------------------
                RotateL(grandfather);
                parent->_col = BLACK;
                grandfather->_col = RED;
                break;
            }
        }
    }

    // --- 5. 最终保障:根节点强制染黑 ---
    // 原因:如果在 Case 1 中把根节点染红了(cur 迭代到了根),这里必须强制改回黑
    _root->_col = BLACK;
    return true;
}

(注:红黑树中的 RotateL / RotateR 与 AVL 树的指针重连骨架 相同,都会维护左、右、父指针以及与上层的连接;但 AVL 旋转函数还需要维护 _bf,红黑树则维护颜色,因此两者不能把包含 BF 更新的完整函数体机械照搬。另:上面模拟代码中的 Case 1 注释"父+叔都变黑,黑高会 +2"应理解为左右两条对应路径各自增加 1 个黑节点,并不是同一条路径同时增加 2)

2.6 如何验证你的红黑树是对的?

写完红黑树,不要只看能不能跑通,必须写验证函数检查红黑性质。下面的 Check 主要验证:根为黑、无连续红节点、各路径黑高一致;"节点颜色只能红/黑"由枚举类型天然保证,而二叉搜索树的有序性依赖插入逻辑。若要做更完整的独立验证,还应额外检查中序序列是否有序。

cpp 复制代码
bool IsValidRBTree() {
    if (_root == nullptr) return true;
    
    // 检查性质2:根是黑
    if (_root->_col == RED) return false;

    // 获取任意一条路径的黑节点数作为基准
    int benchmarkBlackCount = 0;
    Node* cur = _root;
    while (cur) {
        if (cur->_col == BLACK) benchmarkBlackCount++;
        cur = cur->_left;
    }

    return _Check(_root, 0, benchmarkBlackCount);
}

bool _Check(Node* root, int blackCount, int benchmark) {
    if (root == nullptr) {
        // 检查性质5:路径黑节点数是否一致
        return blackCount == benchmark;
    }

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

    // 检查性质4:是否有连续红节点
    if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
        cout << "错误:出现连续红节点!" << endl;
        return false;
    }

    return _Check(root->_left, blackCount, benchmark) &&
           _Check(root->_right, blackCount, benchmark);
}

补充:红黑树的查找

红黑树同样保留二叉搜索树的有序性质,所以 Find 本身仍按 BST 规则向左或向右搜索;红黑规则的作用是把树高限制在 O ( log ⁡ N ) O(\log N) O(logN),从而使查找最坏复杂度保持为 O ( log ⁡ N ) O(\log N) O(logN)。也就是说,红黑树复杂的是插入/删除后的平衡维护,而不是查找路径本身

2.7 完整代码展示

以下是完整的、可编译运行的代码:

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <ctime>      // 随机数测试
using namespace std;

// -------------------- 颜色枚举 --------------------
enum Colour { RED, BLACK };

// -------------------- 红黑树节点 --------------------
template<class K, class V>
struct RBTreeNode {
    pair<K, V> _kv;
    RBTreeNode* _left;
    RBTreeNode* _right;
    RBTreeNode* _parent;
    Colour _col;

    RBTreeNode(const pair<K, V>& kv)
        : _kv(kv), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED) {
        /* 新节点默认红色:
           插入黑节点会破坏"黑高一致"(性质5),难以全局修复;
           插入红节点仅可能破坏"不红红"(性质4),局部修正即可。 */
    }
};

// -------------------- 红黑树类 --------------------
template<class K, class V>
class RBTree {
    typedef RBTreeNode<K, V> Node;

public:
    RBTree() = default;
    ~RBTree() { Destroy(_root); }

    // ---------- 插入核心 ----------
    bool Insert(const pair<K, V>& kv) {
        // 1. 空树:建黑根
        if (_root == nullptr) {
            _root = new Node(kv);
            _root->_col = BLACK;
            return true;
        }

        // 2. BST 插入
        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;   // key 已存在
        }
        cur = new Node(kv);   // 红色新节点
        if (parent->_kv.first < kv.first) parent->_right = cur;
        else parent->_left = cur;
        cur->_parent = parent;

        // 3. 红黑树修复(父节点为红才需调整)
        while (parent && parent->_col == RED) {
            Node* grandpa = parent->_parent;   // 祖父一定存在(父为红非根)
            // ------ 父为祖父左孩子 ------
            if (parent == grandpa->_left) {
                Node* uncle = grandpa->_right;
                // 情况1:叔叔为红 ------ 变色 + 向上继续
                if (uncle && uncle->_col == RED) {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandpa->_col = RED;
                    cur = grandpa;
                    parent = cur->_parent;
                }
                // 情况2 & 3:叔叔为黑(或不存在)
                else {
                    // 情况3:cur 是 parent 的右孩子(折线) → 左旋变直线
                    if (cur == parent->_right) {
                        RotateL(parent);
                        swap(parent, cur);   // 交换后 parent 成为新子树的根(黑色)
                    }
                    // 情况2:直线(LL) → 右旋 + 变色
                    RotateR(grandpa);
                    parent->_col = BLACK;      // 新根变黑
                    grandpa->_col = RED;       // 原祖父变红
                    break;                     // 调整结束
                }
            }
            // ------ 父为祖父右孩子(对称) ------
            else {
                Node* uncle = grandpa->_left;
                if (uncle && uncle->_col == RED) {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandpa->_col = RED;
                    cur = grandpa;
                    parent = cur->_parent;
                }
                else {
                    if (cur == parent->_left) {   // RL 折线
                        RotateR(parent);
                        swap(parent, cur);
                    }
                    RotateL(grandpa);            // RR 直线
                    parent->_col = BLACK;
                    grandpa->_col = RED;
                    break;
                }
            }
        }
        _root->_col = BLACK;   // 确保根黑
        return true;
    }

    // ---------- 旋转(同AVL,仅改变指针,不处理颜色) ----------
    void RotateL(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 RotateR(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;
        }
    }

    // ---------- 验证红黑树 ----------
    bool IsValid() {
        if (_root == nullptr) return true;
        if (_root->_col == RED) { cout << "根为红\n"; return false; }
        // 取最左路径的黑节点数作为基准
        int blackCnt = 0;
        Node* cur = _root;
        while (cur) {
            if (cur->_col == BLACK) ++blackCnt;
            cur = cur->_left;
        }
        return Check(_root, 0, blackCnt);
    }

    void InOrder() { InOrder(_root); cout << endl; }
    int Height() { return Height(_root); }
    int Size() { return Size(_root); }

private:
    // ---------- 递归辅助 ----------
    bool Check(Node* root, int curBlack, int bench) {
        if (root == nullptr) {
            if (curBlack != bench) { cout << "黑高不一致\n"; return false; }
            return true;
        }
        if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
            cout << "连续红节点\n"; return false;
        }
        if (root->_col == BLACK) ++curBlack;
        return Check(root->_left, curBlack, bench) &&
               Check(root->_right, curBlack, bench);
    }
    void InOrder(Node* root) {
        if (!root) return;
        InOrder(root->_left);
        cout << root->_kv.first << ":" << root->_kv.second << " ";
        InOrder(root->_right);
    }
    int Height(Node* root) {
        return root ? 1 + max(Height(root->_left), Height(root->_right)) : 0;
    }
    int Size(Node* root) {
        return root ? 1 + Size(root->_left) + Size(root->_right) : 0;
    }
    void Destroy(Node* root) {
        if (!root) return;
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }

    Node* _root = nullptr;
};

// -------------------- 测试 --------------------
int main() {
    // 基础测试
    int a[] = {4, 2, 6, 1, 3, 5, 15, 7, 16, 14};
    RBTree<int, int> t1;
    for (auto e : a) t1.Insert({e, e});
    cout << "中序: "; t1.InOrder();
    cout << "合法: " << (t1.IsValid() ? "是" : "否") << "\n\n";

    // 压力测试
    cout << "随机插入10000个数字...\n";
    const int N = 10000;
    vector<int> v(N);
    srand((unsigned)time(0));
    for (int i = 0; i < N; ++i) v[i] = rand();
    RBTree<int, int> t2;
    for (auto e : v) t2.Insert({e, e});
    cout << "树高: " << t2.Height() << "\n节点数: " << t2.Size() << "\n";
    cout << "合法: " << (t2.IsValid() ? "是" : "否") << "\n";
    return 0;
}

2.8 总结

红黑树之所以难,是因为它将颜色约束旋转逻辑 耦合在了一起。但只要你抓住 "叔叔的颜色决定了是变色还是旋转" 这一核心,就能在脑海中构建出清晰的图谱。

第三章:红黑树的封装------泛型编程的艺术

这是一个非常关键的章节。如果说上一章的"红黑树实现"是造出了强劲的发动机 ,那么这一章就是通过巧妙的封装,造出跑车(Map)和越野车(Set)

这一章的核心思想是:泛型编程(Generic Programming)与"通过策略/仿函数完成适配"的设计思想 。这里的 KeyOfT 更准确地说是一个"取 Key 的策略仿函数",不必把它严格等同于 GoF 的 Adapter 设计模式。我们需要改造红黑树,让它能够同时适配 key 场景(Set)和 key-value 场景(Map)。

setmap 都复用同一个 rb_tree 模板;GNU libstdc++ 的常见实现中对应结构名为 _Rb_tree。需要注意:这是具体标准库实现方式,C++ 标准并不规定 std::map/std::set 的底层必须是红黑树

这怎么可能?

  • set 存的是 K(键值即数据)。
  • map 的节点值类型是 pair<const K, V>(键不可通过迭代器修改,映射值 V 可以修改)。
  • 红黑树插入时需要比较大小:set 直接比 K,但 map 需要比 pair.first

如果我们写两份红黑树代码,那就是代码冗余的灾难。今天,我们要学习如何通过模板参数控制仿函数(Functor),用同一棵树支撑起两个容器。

结合 SGI STL 源码理解三个核心模板角色:

  • Key:用于 find/erase 等按关键码操作的参数类型;
  • Value:红黑树节点中真正存储的数据类型setValueKeymapValuepair<const Key, T>
  • KeyOfValue:从 Value 中提取比较用 Key 的策略。set 相当于使用 identity 直接返回自身,map 相当于使用 select1stpair.first

因此,即使 setKeyValue 看起来相同,这两个模板参数的职责 仍然不同;而在 map 中这种区别尤其明显:插入的是 pair,查找/删除通常按 Key 进行。

3.1 改造节点结构:从 <K, V><T>

在上一章,我们的节点是写死的 pair<K, V>。现在为了通用,我们需要把节点里存的数据定义为 T

  • 对于 SetT 就是 K
  • 对于 MapT 就是 pair<const K, V>
cpp 复制代码
// 原来的写法(只适合 Map)
// template<class K, class V> 
// struct RBTreeNode { pair<K, V> _kv; ... };

// 改造后的写法(通用)
template<class T>
struct RBTreeNode
{
    T _data; // 如果是 set,这里存 key;如果是 map,这里存 pair
    RBTreeNode<T>* _left;
    RBTreeNode<T>* _right;
    RBTreeNode<T>* _parent;
    Colour _col;

    RBTreeNode(const T& data)
        : _data(data), _left(nullptr), _right(nullptr), _parent(nullptr), _col(RED)
    {}
};

3.2 核心难点:如何比较大小?(KeyOfT)

红黑树的 InsertFind 需要比较节点数据的大小。

  • SetKeyOfT 直接返回 T 自身(即 key),然后比较 key。
  • MapKeyOfTT(即 pair)中提取 first,始终只比较 key,而不是让 pairsecond 参与排序。

底层的 RBTree 不知道上层是 Map 还是 Set,它只管存 T谁知道怎么取 Key?上层使用者知道!

因此,我们需要在 RBTree 的模版参数中增加一个仿函数(Functor) ,我们称之为 KeyOfT。它的作用是从 T 中提取出用于比较的 Key

RBTree 类的改造

cpp 复制代码
// K: 关键码类型(用于查找)
// T: 节点中存储的数据类型
// KeyOfT: 仿函数,用于从 T 中提取 K
template<class K, class T, class KeyOfT>
class RBTree
{
    typedef RBTreeNode<T> Node;
public:
    bool Insert(const T& data)
    {
        KeyOfT kot; // 实例化仿函数
        
        if (_root == nullptr) {
            _root = new Node(data);
            _root->_col = BLACK;
            return true;
        }

        Node* parent = nullptr;
        Node* cur = _root;
        while (cur)
        {
            // --- 核心变化点 ---
            // 以前:if (cur->_kv.first < kv.first)
            // 现在:利用 kot 对象从 _data 中提取 key 进行比较
            if (kot(cur->_data) < kot(data))
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (kot(cur->_data) > kot(data))
            {
                parent = cur;
                cur = cur->_left;
            }
            else return false;
        }
        // ... 后续插入与旋转逻辑不变 ...
    }
    // ...
};

3.3 迭代器 (Iterator) 的实现

STL 的精髓在于迭代器。mapset 的迭代器本质上就是红黑树的迭代器。红黑树的迭代器走的是中序遍历(有序)。

迭代器结构

我们需要实现 operator++operator--

  • ++ 逻辑:

    1. 右子树存在 :下一个节点是右子树的最左节点(该右子树中的最小节点)。
    2. 右子树不存在 :沿父指针向上找,只要当前节点是父节点的右孩子,就说明父节点已经访问过;直到找到"当前节点是其父节点左孩子"的祖先,该父节点就是下一个节点。若一直找到根之上,则到达 end()
  • -- 逻辑与之镜像:

    1. 左子树存在 :上一个节点是左子树的最右节点(最大节点)。
    2. 左子树不存在:向上找,只要当前节点是父节点的左孩子就继续上移,直到找到"当前节点是其父节点右孩子"的祖先。
    3. --end() 是特殊情况 :标准双向迭代器要求从 end() 后退能到达整棵树的最右节点。模拟实现让迭代器额外保存 _root 来处理这一点;SGI STL 则使用 header 哨兵节点,header 还会记录根、最左和最右节点。
cpp 复制代码
template<class T, class Ref, class Ptr>
struct __RBTreeIterator
{
    typedef RBTreeNode<T> Node;
    typedef __RBTreeIterator<T, Ref, Ptr> Self;
    
    Node* _node; // 迭代器当前指向的节点指针

    __RBTreeIterator(Node* node) : _node(node) {}

    // --- 常用操作重载 ---

    // 解引用操作:返回节点数据的引用 (Ref)
    // 如果是 iterator,Ref 是 T&;如果是 const_iterator,Ref 是 const T&
    Ref operator*() 
    { 
        return _node->_data; 
    }

    // 箭头操作:返回节点数据的指针 (Ptr)
    // 使得迭代器可以像指针一样使用 -> 访问成员
 //   node:这是迭代器内部持有的红黑树节点指针。
//_node->_data:这是取出了节点里存的真实数据(例如 pair<const K, V>)。
//&:这是取地址。我们取出了 _data 这个数据的内存地址。也就是返回了一个指向数据的原生指针(T*)。
    Ptr operator->() 
    { 
        return &_node->_data; 
    }

    // 判断两个迭代器是否不等
    bool operator!=(const Self& s) 
    { 
        return _node != s._node; 
    }

    // 判断两个迭代器是否相等
    bool operator==(const Self& s)
    {
        return _node == s._node;
    }

    // --- 核心逻辑:前置++ ---
    // 作用:让迭代器指向中序遍历的下一个节点
    // 中序遍历规则:左子树 -> 根节点 -> 右子树
  //  当你对某个节点 curr 调用 ++ 时,说明 curr(根)已经被访问过了。接下来该去找谁?
    Self& operator++()
    {
        if (_node->_right)
        {
            // 【情况 1】:右子树存在
            // 根据中序遍历规则,访问完当前节点(根)后,接下来要访问右子树。
            // 右子树中的第一个节点,一定是右子树中"最左边"的那个节点(即最小值)。
            
            Node* leftMost = _node->_right; // 先走到右孩子
            while (leftMost->_left)         // 然后一直往左走到底
            {
                leftMost = leftMost->_left;
            }
            
            _node = leftMost; // 更新迭代器指向
        }
        else
        {
            // 【情况 2】:右子树不存在
            // 说明当前节点(以及它的左子树)都已经访问过了。
            // 我们需要向上回溯,寻找一个"还未被访问"的祖先节点。

            Node* cur = _node;
            Node* parent = cur->_parent;

            // 循环判断:
            // 如果 cur 是 parent 的【右孩子】:
            // 说明 parent 已经在 cur 之前被访问过了(因为是中序:左->根->右),
            // 所以 parent 不是我们要找的下一个节点,必须继续向上找。
            while (parent && cur == parent->_right)
            {
                cur = parent;
                parent = parent->_parent;
            }

            // 循环结束有两种可能:
 // 1. parent 为空:说明 最开始的cur 是整棵树最右下的节点(根也是其左子树的一部分),遍历结束,_node 置为 nullptr。
 // 2. cur == parent->_left:说明 cur 是 parent 的【左孩子】。
            //    根据中序遍历(左->根->右),访问完左子树(cur所在的子树)后,下一个就是根节点(parent)。
            _node = parent;
        }

        return *this;
    }

    // --- 前置-- (逻辑与 ++ 完全对称) ---
    // 作用:让迭代器指向中序遍历的上一个节点
    Self& operator--()
    {
        if (_node->_left)
        {
            // 【情况 1】:左子树存在
            // 上一个节点是左子树中"最右边"的节点(最大值)
            Node* rightMost = _node->_left;
            while (rightMost->_right)
            {
                rightMost = rightMost->_right;
            }
            _node = rightMost;
        }
        else
        {
            // 【情况 2】:左子树不存在
            // 向上回溯,只要 cur 是 parent 的左孩子,说明 parent 已经在后面了(因为是左->根),继续往上。
            // 直到找到一个节点是其 parent 的"右孩子",该 parent 就是上一个节点。
            Node* cur = _node;
            Node* parent = cur->_parent;
            while (parent && cur == parent->_left)
            {
                cur = parent;
                parent = parent->_parent;
            }
            _node = parent;
        }
        return *this;
    }
};

将迭代器集成到 RBTree 中:

cpp 复制代码
template<class K, class T, class KeyOfT>
class RBTree
{
    // ...
    typedef __RBTreeIterator<T, T&, T*> iterator;
    typedef __RBTreeIterator<T, const T&, const T*> const_iterator;

    iterator begin()
    {
        Node* left = _root;
        while (left && left->_left) left = left->_left; // 找最左节点
        return iterator(left);
    }

    iterator end() { return iterator(nullptr); }
    // ...
};

注意: 上面这段"迭代器结构"是简化演示版。它把 end() 表示为 nullptr,但 operator--() 一开始会直接访问 _node->_left,因此这段简化代码不能直接执行 --end() 。PDF 的完整版通过在迭代器中同时保存 _root,并在 _node == nullptr 时跳到整棵树最右节点来补齐该语义;SGI STL 则借助 header 哨兵实现。

3.4 RBTree.h (核心底层)

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <assert.h>
using namespace std;

// ============================================================================
// 1. 颜色枚举
// ============================================================================
enum Colour { RED, BLACK };

// ============================================================================
// 2. 红黑树节点结构
// ============================================================================
template<class T>
struct RBTreeNode
{
    T _data;                      // 存储的数据(对于 map 是 pair,对于 set 是 key)
    RBTreeNode<T>* _left;         // 左孩子指针
    RBTreeNode<T>* _right;        // 右孩子指针
    RBTreeNode<T>* _parent;       // 父节点指针(红黑树调整时必须)
    Colour _col;                  // 节点颜色

    // 构造函数:用给定数据构造节点
    // 重要:新节点默认颜色为 RED。
    // 理由:插入黑色节点会直接破坏"所有路径黑节点数相同"(性质5),修复成本极高;
    //       插入红色节点仅可能破坏"不能连续红节点"(性质4),可通过局部变色/旋转修复。
    RBTreeNode(const T& data)
        : _data(data)
        , _left(nullptr)
        , _right(nullptr)
        , _parent(nullptr)
        , _col(RED)
    {}
};

// ============================================================================
// 3. 红黑树迭代器(支持中序遍历顺序的 ++ 和 --)
// ============================================================================
template<class T, class Ref, class Ptr>
struct RBTreeIterator
{
    typedef RBTreeNode<T> Node;
    typedef RBTreeIterator<T, Ref, Ptr> Self;

    Node* _node;   // 迭代器当前所指向的节点指针

    // 构造函数:用一个节点指针构造迭代器
    // 这是迭代器唯一的构造方式,通常由红黑树的 Begin() / End() 或 Find() 返回。
    RBTreeIterator(Node* node) : _node(node) {}

    // 解引用运算符:返回节点数据的引用(可读可写,取决于 Ref 是 T& 还是 const T&)
    Ref operator*() { return _node->_data; }

    // 箭头运算符:返回节点数据的指针,便于用 it->first 访问 pair 的成员
    Ptr operator->() { return &_node->_data; }

    // 比较运算符:比较两个迭代器是否指向同一个节点
    bool operator!=(const Self& s) const { return _node != s._node; }
    bool operator==(const Self& s) const { return _node == s._node; }

    // ---------- 前置++(中序遍历的后继) ----------
    // 返回中序遍历中当前节点的下一个节点
    Self& operator++()
    {
        if (_node->_right)
        {
            // 若右子树存在,则下一个节点是右子树中的最左节点(最小节点)
            Node* leftMost = _node->_right;
            while (leftMost->_left)
                leftMost = leftMost->_left;
            _node = leftMost;
        }
        else
        {
            // 右子树为空,向上回溯:
            // 只要当前节点是父节点的右孩子,说明父节点已经访问过,继续上溯;
            // 直到当前节点是父节点的左孩子,则父节点就是下一个节点。
            Node* cur = _node;
            Node* parent = cur->_parent;
            while (parent && cur == parent->_right)
            {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;   // 可能为 nullptr(到达 end)
        }
        return *this;
    }

    // ---------- 前置--(中序遍历的前驱) ----------
    // 返回中序遍历中当前节点的上一个节点
    Self& operator--()
    {
        if (_node->_left)
        {
            // 若左子树存在,则上一个节点是左子树中的最右节点(最大节点)
            Node* rightMost = _node->_left;
            while (rightMost->_right)
                rightMost = rightMost->_right;
            _node = rightMost;
        }
        else
        {
            // 左子树为空,向上回溯:
            // 只要当前节点是父节点的左孩子,说明父节点在当前节点之后,继续上溯;
            // 直到当前节点是父节点的右孩子,则父节点就是上一个节点。
            Node* cur = _node;
            Node* parent = cur->_parent;
            while (parent && cur == parent->_left)
            {
                cur = parent;
                parent = cur->_parent;
            }
            _node = parent;   // 可能为 nullptr(到达 begin 之前)
        }
        return *this;
    }
};

// ============================================================================
// 4. 红黑树主体类
// ============================================================================
template<class K, class T, class KeyOfT>
class RBTree
{
    typedef RBTreeNode<T> Node;

public:
    // ---------- 迭代器类型别名 ----------
    typedef RBTreeIterator<T, T&, T*> Iterator;
    typedef RBTreeIterator<T, const T&, const T*> ConstIterator;

    // ========================================================================
    // 构造 / 析构 / 拷贝 / 赋值  (这些函数是对象生命周期管理的关键)
    // ========================================================================

    // 1. 默认构造函数
    // 功能:构造一棵空树。
    // 过程:编译器自动生成,但这里显式写出 = default,表示使用编译器合成的版本。
    //       合成版本会调用成员变量 _root 的默认构造函数(对于指针,即初始化为 nullptr)。
    // 注:由于 _root 是内置类型,默认初始化为随机值,但类内初始化(= nullptr)保证其为空。
    RBTree() = default;

    // 2. 拷贝构造函数
    // 功能:用另一棵红黑树 t 构造当前树(深拷贝)。
    // 过程:
    //   (1) 调用私有成员函数 Copy(t._root),递归拷贝所有节点。
    //   (2) Copy 函数会复制每个节点的数据、颜色,并重建父子关系。
    //   (3) 将新树的根赋给 _root。
    // 注意:必须深拷贝,因为两个对象不能共享节点(否则析构会 double free)。
    RBTree(const RBTree& t)
    {
        _root = Copy(t._root);
    }

    // 3. 赋值运算符重载(现代写法:Copy and Swap)
    // 功能:将当前对象赋值为另一棵树 t 的副本。
    // 过程:
    //   (1) 参数 t 是传值,这本身会调用拷贝构造(或移动构造)生成一个临时副本。
    //   (2) 交换当前对象的 _root 与临时对象 t 的 _root。
    //   (3) 函数返回后,临时对象 t 被销毁,其析构函数会释放原来当前对象的旧资源。
    //   (4) 当前对象现在拥有新树的节点。
    // 优点:异常安全,代码简洁,自动处理自我赋值。
    RBTree& operator=(RBTree t)
    {
        swap(_root, t._root);   // 交换指针,t 现在持有旧资源
        return *this;
    }

    // 4. 析构函数
    // 功能:释放树中所有节点占用的内存。
    // 过程:
    //   (1) 调用私有辅助函数 Destroy(_root),进行后序遍历删除每个节点。
    //   (2) 然后将 _root 置为 nullptr,防止野指针。
    // 注意:Destroy 是递归的,对于深度很大的树可能递归过深,但红黑树高度为 O(logN),安全。
    ~RBTree()
    {
        Destroy(_root);
        _root = nullptr;
    }

    // ========================================================================
    // 迭代器接口
    // ========================================================================

    // 返回指向树中第一个节点(按中序遍历)的迭代器,即最左节点
    Iterator Begin()
    {
        Node* leftMost = _root;
        while (leftMost && leftMost->_left)
            leftMost = leftMost->_left;
        return Iterator(leftMost);
    }

    // 返回结束迭代器(nullptr)
    Iterator End() { return Iterator(nullptr); }

    // const 版本的 Begin / End(用于 const 对象)
    ConstIterator Begin() const
    {
        Node* leftMost = _root;
        while (leftMost && leftMost->_left)
            leftMost = leftMost->_left;
        return ConstIterator(leftMost);
    }
    ConstIterator End() const { return ConstIterator(nullptr); }

    // ========================================================================
    // 核心插入操作(返回插入位置迭代器 + 是否成功)
    // ========================================================================
    pair<Iterator, bool> Insert(const T& data)
    {
        // ---------- 空树情况 ----------
        if (_root == nullptr)
        {
            _root = new Node(data);
            _root->_col = BLACK;    // 根节点必须为黑色(性质2)
            return make_pair(Iterator(_root), true);
        }

        KeyOfT kot;   // 仿函数对象,用于从 data 中提取键值 key
        Node* parent = nullptr;
        Node* cur = _root;

        // ---------- 1. 标准 BST 插入,寻找位置 ----------
        while (cur)
        {
            if (kot(cur->_data) < kot(data))
            {
                parent = cur;
                cur = cur->_right;
            }
            else if (kot(cur->_data) > kot(data))
            {
                parent = cur;
                cur = cur->_left;
            }
            else
            {
                // key 已存在,插入失败,返回已有节点迭代器和 false
                return make_pair(Iterator(cur), false);
            }
        }

        // ---------- 2. 创建新节点并链接到树中 ----------
        cur = new Node(data);      // 默认红色
        Node* newnode = cur;       // 备份新节点地址,用于返回值

        if (kot(parent->_data) < kot(data))
            parent->_right = cur;
        else
            parent->_left = cur;
        cur->_parent = parent;

        // ---------- 3. 红黑树平衡调整(修复红红冲突) ----------
        // 当父节点存在且为红色时,需要修复
        while (parent && parent->_col == RED)
        {
            Node* grandfather = parent->_parent;

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

                // Case 1:叔叔存在且为红色 → 变色,向上继续
                if (uncle && uncle->_col == RED)
                {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = cur->_parent;   // 继续向上检查
                }
                else
                {
                    // Case 2 和 3:叔叔不存在或为黑色 → 旋转
                    // Case 3:当前节点是父节点的右孩子(LR 折线)→ 先左旋变 LL 直线
                    if (cur == parent->_right)
                    {
                        RotateL(parent);
                        swap(parent, cur);    // 旋转后 parent 和 cur 角色互换,统一为 LL 处理
                    }
                    // Case 2:当前节点是父节点的左孩子(LL 直线)→ 右单旋 + 变色
                    RotateR(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    break;    // 调整完毕,子树根变黑,不会影响上层
                }
            }
            // ------- 情况 B:父节点是祖父的右孩子(对称) -------
            else // parent == grandfather->_right
            {
                Node* uncle = grandfather->_left;

                if (uncle && uncle->_col == RED)
                {
                    parent->_col = BLACK;
                    uncle->_col = BLACK;
                    grandfather->_col = RED;
                    cur = grandfather;
                    parent = cur->_parent;
                }
                else
                {
                    // Case 3:当前节点是父节点的左孩子(RL 折线)→ 先右旋变 RR 直线
                    if (cur == parent->_left)
                    {
                        RotateR(parent);
                        swap(parent, cur);
                    }
                    // Case 2:当前节点是父节点的右孩子(RR 直线)→ 左单旋 + 变色
                    RotateL(grandfather);
                    parent->_col = BLACK;
                    grandfather->_col = RED;
                    break;
                }
            }
        }

        // ---------- 4. 确保根节点为黑色(性质2) ----------
        _root->_col = BLACK;

        // 返回新插入节点的迭代器和成功标志
        return make_pair(Iterator(newnode), true);
    }

    // ========================================================================
    // 查找操作
    // ========================================================================
    Iterator Find(const K& key)
    {
        KeyOfT kot;
        Node* cur = _root;
        while (cur)
        {
            if (kot(cur->_data) < key)
                cur = cur->_right;
            else if (kot(cur->_data) > key)
                cur = cur->_left;
            else
                return Iterator(cur);
        }
        return End();
    }

    // ========================================================================
    // 调试 & 验证接口
    // ========================================================================
    int Size() { return _Size(_root); }
    int Height() { return _Height(_root); }

    // 验证当前树是否满足红黑树的所有性质
    bool IsBalance()
    {
        if (_root == nullptr) return true;

        // 性质2:根节点必须为黑色
        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:
    // ========================================================================
    // 旋转操作(与 AVL 相同,但红黑树旋转不修改平衡因子,仅改变指针)
    // ========================================================================

    // 左单旋:用于 RR 型失衡
    // 以 parent 为轴,将其右孩子 subR 提升为子树根,parent 变为 subR 的左孩子
    void RotateL(Node* parent)
    {
        Node* subR = parent->_right;
        Node* subRL = subR->_left;

        // 1. subRL 过继给 parent 作为右孩子
        parent->_right = subRL;
        if (subRL) subRL->_parent = parent;

        // 2. parent 变成 subR 的左孩子
        Node* parentParent = parent->_parent;
        subR->_left = parent;
        parent->_parent = subR;

        // 3. subR 接替原 parent 的位置
        if (parentParent == nullptr)
        {
            _root = subR;
            subR->_parent = nullptr;
        }
        else
        {
            if (parent == parentParent->_left)
                parentParent->_left = subR;
            else
                parentParent->_right = subR;
            subR->_parent = parentParent;
        }
    }

    // 右单旋:用于 LL 型失衡(对称于左旋)
    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 (parentParent == nullptr)
        {
            _root = subL;
            subL->_parent = nullptr;
        }
        else
        {
            if (parent == parentParent->_left)
                parentParent->_left = subL;
            else
                parentParent->_right = subL;
            subL->_parent = parentParent;
        }
    }

    // ========================================================================
    // 递归检查红黑树性质(供 IsBalance 调用)
    // ========================================================================
    bool Check(Node* root, int blackNum, const int refNum)
    {
        if (root == nullptr)
        {
            // 到达叶子节点,检查当前路径的黑高是否与基准一致
            if (refNum != blackNum)
            {
                cout << "错误:路径黑色节点数量不一致" << endl;
                return false;
            }
            return true;
        }

        // 检查性质4:不能有连续红色节点
        if (root->_col == RED && root->_parent && root->_parent->_col == RED)
        {
            cout << "错误:存在连续的红色节点" << endl;
            return false;
        }

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

        // 递归检查左右子树
        return Check(root->_left, blackNum, refNum) &&
               Check(root->_right, blackNum, refNum);
    }

    // ========================================================================
    // 内部辅助函数(递归实现)
    // ========================================================================

    // 计算节点个数
    int _Size(Node* root)
    {
        return root == nullptr ? 0 : _Size(root->_left) + _Size(root->_right) + 1;
    }

    // 计算树的高度(以根为第1层)
    int _Height(Node* root)
    {
        if (root == nullptr) return 0;
        int leftH = _Height(root->_left);
        int rightH = _Height(root->_right);
        return leftH > rightH ? leftH + 1 : rightH + 1;
    }

    // 后序遍历删除所有节点(析构时调用)
    void Destroy(Node* root)
    {
        if (!root) return;
        Destroy(root->_left);
        Destroy(root->_right);
        delete root;
    }

    // 深拷贝辅助函数(前序遍历递归复制)
    // 注意:必须正确维护新节点的父指针,否则树结构损坏
    Node* Copy(Node* root)
    {
        if (root == nullptr) return nullptr;

        // 拷贝当前节点(数据 + 颜色)
        Node* newRoot = new Node(root->_data);
        newRoot->_col = root->_col;

        // 递归拷贝左子树
        newRoot->_left = Copy(root->_left);
        if (newRoot->_left) newRoot->_left->_parent = newRoot;

        // 递归拷贝右子树
        newRoot->_right = Copy(root->_right);
        if (newRoot->_right) newRoot->_right->_parent = newRoot;

        return newRoot;
    }

    // ========================================================================
    // 成员变量
    // ========================================================================
    Node* _root = nullptr;   // 根节点指针,类内初始化确保空树时指向 nullptr
};

3.5 封装 MySet

现在底层准备好了,MySet 的封装就变得非常简单,简直是"填空题"。PDF 的做法是让底层树直接实例化为 RBTree<K, const K, SetKeyOfT>,从存储类型层面保证 set 的 key 不能被修改;下面保留的模拟代码采取"底层存 K、对外只暴露 ConstIterator"的思路,目标同样是禁止通过 set 迭代器修改 key。

cpp 复制代码
#pragma once
#include "RBTree.h"

namespace m
{
	template<class K>
	class set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key) { return key; }
		};

	public:
		typedef RBTree<K, K, SetKeyOfT> Tree;
        
		// Set 的迭代器不可被修改,所以 iterator 和 const_iterator 都是 const 的
		typedef typename Tree::ConstIterator iterator;
		typedef typename Tree::ConstIterator const_iterator;

		pair<iterator, bool> insert(const K& k)
		{
            // 底层 insert 返回的是 pair<普通iterator, bool>
            // 这里可能涉及普通迭代器转 const 迭代器,实际编译器通常支持隐式转换
            // 或者需要手动构造 pair
            auto ret = _t.Insert(k);
			return pair<iterator, bool>(ret.first, ret.second);
		}

		iterator begin() const { return _t.Begin(); }
		iterator end() const { return _t.End(); }
        
        iterator find(const K& k) const { return _t.Find(k); }

	private:
		Tree _t;
	};
}

3.6 封装 MyMap

MyMap 稍微复杂一点点,主要体现在 operator[] 的支持上。为了实现 operator[],底层 Insert 不能只返回成功/失败,而要返回 pair<iterator, bool>iterator 指向"新插入节点或已存在节点",bool 表示是否真的发生了插入。这样 operator[] 可以统一执行 insert(make_pair(key, V())),再返回 ret.first->second:key 不存在时插入一个默认构造的 V(),key 已存在时直接取得原节点的 second

cpp 复制代码
#pragma once
#include "RBTree.h"

namespace m
{
	template<class K, class V>
	class map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& kv) { return kv.first; }
		};

	public:
		// Map 的 Key 是 const 的,Value 是可修改的
		typedef RBTree<K, pair<const K, V>, MapKeyOfT> Tree;
		
		typedef typename Tree::Iterator iterator;
		typedef typename Tree::ConstIterator const_iterator;

		pair<iterator, bool> insert(const pair<K, V>& kv)
		{
			return _t.Insert(kv);
		}

		// 方括号重载:不存在则插入默认值,存在则返回 Value 引用
		V& operator[](const K& key)
		{
			pair<iterator, bool> ret = insert(make_pair(key, V()));
			return ret.first->second;
		}

		iterator begin() { return _t.Begin(); }
		iterator end() { return _t.End(); }
        
        const_iterator begin() const { return _t.Begin(); }
        const_iterator end() const { return _t.End(); }

        iterator find(const K& k) { return _t.Find(k); }

	private:
		Tree _t;
	};
}

3.7 总结与思考

通过这一章的封装,你完成了从"数据结构"到"容器库"的飞跃。

一定要理解的逻辑闭环:

  1. 用户 调用 MyMap
  2. MyMap 定义了 MapKeyOfT(如何从 pair 取 key)。
  3. MyMap 实例化底层的 RBTree<K, pair<const K, V>, MapKeyOfT>
  4. RBTree 在插入时,不知道数据是 pair,但它调用 kot(data),不管是 int 还是 pair,它最终都拿到了 key 进行比较。

这就是 C++ STL 极致复用的智慧。

相关推荐
hold?fish:palm1 小时前
20 旋转图像
c++·算法·leetcode
此生决int2 小时前
深入理解C++系列(10)——lsit
开发语言·c++
NoteStream2 小时前
【C语言基础】分支和循环(上)
c语言·开发语言·c++·经验分享·笔记·算法·c#
2401_827499992 小时前
C++(黑马)05-提高编程
java·开发语言·c++
hold?fish:palm3 小时前
链表的基本原理和实现(C++版本)
数据结构·c++·链表
jufeng13073 小时前
【系列:MiniKV 原理剖析 · 第 5 篇】
linux·网络·c++·软件工程
豆沙沙包?4 小时前
C++-程序的内存模型(P84-P88)
java·jvm·c++
jufeng13074 小时前
【系列:MiniKV 原理剖析 · 第 8 篇(完结篇)】
linux·c++·log4j·软件工程·makefile
VL——MOESR4 小时前
【LuoguP1967】货车运输【生成树】【倍增】
c++·算法·题解·倍增·生成树