C/C++ 数据结构(十二)平衡二叉树、红黑树

本篇 核心知识点 :AVL 平衡二叉树(平衡因子、四大失衡旋转)、红黑树五大规则、STL map/set 底层原理、容器选型对比

一、AVL 平衡二叉树

1. 概念

AVL 树是严格平衡二叉搜索树,任意节点左右子树高度差绝对值 ≤ 1 ;高度差值定义为平衡因子 ,差值超出[-1,1]范围则树失衡,必须通过左旋 / 右旋修复结构。

2. 平衡因子计算公式

平衡因子 bf = 左子树高度 - 右子树高度

合法取值:-1、0、1;bf>1 左偏重,bf<-1 右偏重。

3. 节点结构(带平衡因子、父指针)

复制代码
template<typename T>
struct AVLNode
{
    T data;
    int bf; // 平衡因子
    AVLNode<T>* left;
    AVLNode<T>* right;
    AVLNode<T>* parent;
    AVLNode(const T& val):data(val),bf(0),left(nullptr),right(nullptr),parent(nullptr){}
};
template<typename T>
class AVLTree
{
private:
    AVLNode<T>* root;
    // 私有旋转、更新高度、查找最小失衡节点函数
    void updateHeight(AVLNode<T>* p);
    void RotateRight(AVLNode<T>*& root); // 右旋LL
    void RotateLeft(AVLNode<T>*& root);  // 左旋RR
public:
    AVLTree():root(nullptr){}
    void insert(const T& val); // 插入自动平衡
};

4. 插入完整流程

  1. 按二叉搜索树规则递归插入新节点;

  2. 从新节点向上回溯父链,逐层更新平衡因子;

  3. 找到距离插入点最近的最小失衡根

  4. 判断失衡类型,执行对应旋转;

  5. 旋转完成后重新刷新路径所有节点平衡因子。

5. 四大失衡类型与旋转方案

(1)LL 型 左左失衡
概念

最小失衡根 A 的左孩子 B,B 的左子树新增节点,左侧过重。

调整方案:单次右旋
核心旋转逻辑
  1. 临时保存 A 的左孩子 B;

  2. 将 B 原有右子树挂载为 A 左子树;

  3. A 作为 B 的右孩子;

  4. 更新父节点指向,刷新 bf 值。

    template
    void AVLTree::RotateRight(AVLNode& A)
    {
    AVLNode
    B = A->left;
    AVLNode* br = B->right;
    // 1. B右子树交给A左
    A->left = br;
    if(br != nullptr) br->parent = A;
    // 2. A成为B右子树
    B->right = A;
    A->parent = B;
    // 3. 更新上层父节点指向
    B->parent = A->parent;
    A->parent = nullptr;
    A = B;
    // 简化更新平衡因子(完整实现需递归计算高度)
    A->bf = 0;
    A->right->bf = 0;
    }

(2)RR 型 右右失衡
概念

最小失衡根 A 的右孩子 C,C 的右子树新增节点,右侧过重。

调整方案

单次左旋,逻辑与右旋完全对称。

(3)LR 型 左右失衡
概念

最小失衡根 A 左孩子 B,B 的右子树新增节点。

调整步骤

先左旋 B → 再右旋 A,转换为 LL 型处理。

(4)RL 型 右左失衡
概念

最小失衡根 A 右孩子 C,C 的左子树新增节点。

调整步骤

先右旋 C → 再左旋 A,转换为 RR 型处理。

6. AVL 树特性 & 拓展

  1. 优点:平衡约束严格,查询稳定(O(logn)),查找效率极高;

  2. 缺点:插入删除频繁触发旋转,增删开销大;

  3. 适用场景:查询远多于增删(游戏静态资源索引);

  4. 限制:仅调整失衡局部子树,上层树天然保持平衡,无需全局重算高度。

二、红黑树(弱平衡二叉搜索树)

1. 概念

红黑树是弱平衡 BST,不强制左右高度差≤1,依靠节点红 / 黑双色规则 维持近似平衡;STLmap/set/multimap/multiset底层统一采用红黑树。

2. 五大强制规则

  1. 根节点必须为黑色;

  2. 所有空叶子 (NIL) 统一为黑色;

  3. 红色节点的左右子节点只能是黑色(无连续红父子);

  4. 任意节点到全部叶子,黑色节点数量相等(黑高一致);

  5. 允许单侧高度略大,但路径长度差距不会翻倍。

3. 特性

  1. 平衡约束宽松,插入删除仅少量旋转 + 变色操作,性能优于 AVL;

  2. 时间复杂度稳定(O(logn));

  3. 缺点:查询效率略低于 AVL,存在路径长度浮动。

4. AVL vs 红黑树对比表

对比维度 AVL 树 红黑树
平衡强度 严格平衡(高度差≤1) 弱平衡,允许小幅高度差
查询速度 更快 略慢
增删开销 旋转频繁,开销大 仅少量旋转 / 变色,开销小
工业应用 手写底层、极少标准库使用 STL 关联容器底层标准实现

三、STL 关联容器 map /set

3.1 底层共性

全部依托红黑树实现,容器内元素永久自动升序排序,内置二分查找,查找效率(O(logn))。

3.2 set 集合容器

概念

存储单个数值,键值合一;set元素不可重复,multiset允许重复元素。

特性
  1. push_back/push_front,仅支持insert插入;

  2. 自定义类存入必须重载<比较运算符;

  3. 遍历依靠迭代器,元素按 key 自动排序。

代码示例
复制代码
#include <set>
#include <iostream>
using namespace std;
int main()
{
    set<int> st;
    st.insert(10);
    st.insert(2);
    st.insert(10); // 重复插入无效
    for(auto it = st.begin(); it != st.end(); ++it)
        cout << *it << " ";
    return 0;
}

3.3 map 映射容器

概念

存储pair<Key, Value>键值对,Key 唯一;multimap允许重复键。

特性
  1. 仅根据 Key 排序,Value 不参与排序;

  2. Key 只读不可修改,Value 可修改;

  3. 两种插入方式,行为差异巨大;

两种插入区别
  1. mp.insert(pair<K,V>):键重复时插入失败,不会覆盖原有数据;

  2. mp[key]下标访问:键不存在自动新建默认值,存在直接覆盖旧值,存在数据丢失风险。

代码示例
复制代码
#include <map>
#include <string>
int main()
{
    map<string, int> mp;
    mp.insert(pair<string, int>("玩家A", 100));
    mp["玩家B"] = 200;
    // 迭代遍历pair,it->first键,it->second值
    for(auto& p : mp)
        cout << p.first << " " << p.second << endl;
    return 0;
}

3.4 map 模板参数与约束

完整模板:map<Key, Val, 比较仿函数, 内存分配器>

约束:Key 类型必须支持<运算符;自定义类需重载<或传入自定义排序仿函数。

3.5 常用核心接口

find(key)二分查找、erase(键/迭代器)删除、size()获取元素数量、empty()判空。

四、容器选型对比:红黑树容器 vs 哈希表

1. map/set(红黑树)

  1. 复杂度:增删查(O(logn));

  2. 特点:全局有序,支持范围遍历、区间查找;

  3. 适用:资源管理、需要有序输出、少量数据场景(游戏静态图片资源库)。

2. unordered_map 哈希表

  1. 平均复杂度:增删查(O(1));

  2. 特点:存储无序,仅单点快速查询;

  3. 适用:海量装备、道具、数据库缓存,仅根据 key 快速取值。

拓展项目选型(游戏开发)

  1. 少量图片 / 配置资源、需要有序遍历:map

  2. 上万件道具、仅通过 ID 快速查询:哈希表unordered_map

  3. 禁止仅为自动排序选择 map,排序是附加功能,核心优势是对数级查找。

五、二叉树通用拓展:四叉树 & 八叉树

1. 四叉树(2D 游戏场景)

概念

正方形地图持续四等分,边长优先选用 2 的幂,位运算加速分区计算。

用途:

摄像机视锥剔除,只渲染镜头内地块,减少渲染面片,提升游戏帧率。

2. 八叉树(3D 游戏)

概念:

三维立方体沿 XYZ 三轴八等分空间;

用途:

3D 场景遮挡剔除、AI 分区寻路、碰撞检测优化。

六、重点汇总

1 AVL树

  1. 平衡因子定义、计算公式;

  2. LL/RR/LR/RL 四种失衡场景,对应旋转操作;

  3. 插入后查找最小失衡节点流程;

  4. AVL 与红黑优缺点、项目选型场景。

2 map/set 问题

  1. map 底层数据结构(红黑树);

  2. 为什么没有 push_back/push_front;

  3. insert 与下标[]插入风险区别;

  4. 自定义类作为 Key 的条件;

  5. map 与 unordered_map 区别、业务选型。