本篇 核心知识点 :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. 插入完整流程
-
按二叉搜索树规则递归插入新节点;
-
从新节点向上回溯父链,逐层更新平衡因子;
-
找到距离插入点最近的最小失衡根;
-
判断失衡类型,执行对应旋转;
-
旋转完成后重新刷新路径所有节点平衡因子。
5. 四大失衡类型与旋转方案
(1)LL 型 左左失衡
概念
最小失衡根 A 的左孩子 B,B 的左子树新增节点,左侧过重。
调整方案:单次右旋
核心旋转逻辑
-
临时保存 A 的左孩子 B;
-
将 B 原有右子树挂载为 A 左子树;
-
A 作为 B 的右孩子;
-
更新父节点指向,刷新 bf 值。
template
void AVLTree::RotateRight(AVLNode & A)
{
AVLNodeB = 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 树特性 & 拓展
-
优点:平衡约束严格,查询稳定(O(logn)),查找效率极高;
-
缺点:插入删除频繁触发旋转,增删开销大;
-
适用场景:查询远多于增删(游戏静态资源索引);
-
限制:仅调整失衡局部子树,上层树天然保持平衡,无需全局重算高度。
二、红黑树(弱平衡二叉搜索树)
1. 概念
红黑树是弱平衡 BST,不强制左右高度差≤1,依靠节点红 / 黑双色规则 维持近似平衡;STLmap/set/multimap/multiset底层统一采用红黑树。
2. 五大强制规则
-
根节点必须为黑色;
-
所有空叶子 (NIL) 统一为黑色;
-
红色节点的左右子节点只能是黑色(无连续红父子);
-
任意节点到全部叶子,黑色节点数量相等(黑高一致);
-
允许单侧高度略大,但路径长度差距不会翻倍。
3. 特性
-
平衡约束宽松,插入删除仅少量旋转 + 变色操作,性能优于 AVL;
-
时间复杂度稳定(O(logn));
-
缺点:查询效率略低于 AVL,存在路径长度浮动。
4. AVL vs 红黑树对比表
| 对比维度 | AVL 树 | 红黑树 |
|---|---|---|
| 平衡强度 | 严格平衡(高度差≤1) | 弱平衡,允许小幅高度差 |
| 查询速度 | 更快 | 略慢 |
| 增删开销 | 旋转频繁,开销大 | 仅少量旋转 / 变色,开销小 |
| 工业应用 | 手写底层、极少标准库使用 | STL 关联容器底层标准实现 |
三、STL 关联容器 map /set
3.1 底层共性
全部依托红黑树实现,容器内元素永久自动升序排序,内置二分查找,查找效率(O(logn))。
3.2 set 集合容器
概念
存储单个数值,键值合一;set元素不可重复,multiset允许重复元素。
特性
-
无
push_back/push_front,仅支持insert插入; -
自定义类存入必须重载
<比较运算符; -
遍历依靠迭代器,元素按 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允许重复键。
特性
-
仅根据 Key 排序,Value 不参与排序;
-
Key 只读不可修改,Value 可修改;
-
两种插入方式,行为差异巨大;
两种插入区别
-
mp.insert(pair<K,V>):键重复时插入失败,不会覆盖原有数据; -
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(红黑树)
-
复杂度:增删查(O(logn));
-
特点:全局有序,支持范围遍历、区间查找;
-
适用:资源管理、需要有序输出、少量数据场景(游戏静态图片资源库)。
2. unordered_map 哈希表
-
平均复杂度:增删查(O(1));
-
特点:存储无序,仅单点快速查询;
-
适用:海量装备、道具、数据库缓存,仅根据 key 快速取值。
拓展项目选型(游戏开发)
-
少量图片 / 配置资源、需要有序遍历:
map; -
上万件道具、仅通过 ID 快速查询:哈希表
unordered_map; -
禁止仅为自动排序选择 map,排序是附加功能,核心优势是对数级查找。
五、二叉树通用拓展:四叉树 & 八叉树
1. 四叉树(2D 游戏场景)
概念
正方形地图持续四等分,边长优先选用 2 的幂,位运算加速分区计算。
用途:
摄像机视锥剔除,只渲染镜头内地块,减少渲染面片,提升游戏帧率。
2. 八叉树(3D 游戏)
概念:
三维立方体沿 XYZ 三轴八等分空间;
用途:
3D 场景遮挡剔除、AI 分区寻路、碰撞检测优化。
六、重点汇总
1 AVL树
-
平衡因子定义、计算公式;
-
LL/RR/LR/RL 四种失衡场景,对应旋转操作;
-
插入后查找最小失衡节点流程;
-
AVL 与红黑优缺点、项目选型场景。
2 map/set 问题
-
map 底层数据结构(红黑树);
-
为什么没有 push_back/push_front;
-
insert 与下标
[]插入风险区别; -
自定义类作为 Key 的条件;
-
map 与 unordered_map 区别、业务选型。