目录
[1. 基本定义](#1. 基本定义)
[2. 核心特性](#2. 核心特性)
[3. 红黑树的节点定义](#3. 红黑树的节点定义)
[4. 自平衡机制(变色与旋转)](#4. 自平衡机制(变色与旋转))
[5. 红黑树的插入](#5. 红黑树的插入)
[6. 红黑树的查找](#6. 红黑树的查找)
[7. 获取树的高度](#7. 获取树的高度)
[8. 红黑树的验证](#8. 红黑树的验证)
[8.1 BST性质验证](#8.1 BST性质验证)
[8.2 红黑树规则检查](#8.2 红黑树规则检查)
[9. 总结](#9. 总结)
1. 基本定义
红黑树是一种自平衡的二叉搜索树,它通过颜色规则和有限的旋转操作维持 "黑高度平衡",在插入 / 删除时的性能优于 AVL 树,是工程中应用最广泛的平衡树之一(如 C++ STL 的map/set、Java TreeMap的等底层实现)。
2. 核心特性
红黑树的每个节点除了存储键值、左右子节点和父节点外,还增加了颜色属性 (红色或黑色),并通过以下五条严格规则保证树的 "近似平衡":
- 每个节点要么是红色,要么是黑色;
- 根节点必须是黑色;
- 所有NIL 叶子节点(空节点)视为黑色;
- 红色节点的左右孩子必须是黑色的(即不存在两个连续的红色节点)。
- 从任一节点到NIL的所有路径黑色节点的数量相同。
可简记为"根叶黑、不红红、黑路同"。
红黑树不追求 AVL 树那样的 "严格高度平衡"(左右子树高度差≤1),而是通过 "黑路同" 保证最长路径不超过最短路径的 2 倍,间接控制树的高度。这种 "宽松平衡" 使得红黑树在插入 / 删除时需要的旋转操作更少(最多 2 次旋转),效率更高。
红黑树插入节点默认选择红色而不是黑色,核心原因是:插入红色节点会红黑树规则的破坏更小,修复成本更低。如果插入黑色节点,那么会破坏"黑路同"规则 ,所有经过该节点的路径会多一个黑色节点,这种破坏是全局性的 ,想要调整,相当于要重新平衡所有路径的黑节点数量,修复逻辑极其复杂。如果插入红色节点,对规则的破坏是局部且轻微的 ,仅可能破坏"根叶黑""不红红"规则,通过局部的变色或旋转即可修复,不会涉及全局路径的调整,成本远低于修复"黑路同"。
3. 红黑树的节点定义
// 枚举值表示颜色
enum Colour { RED, BLACK };
// 这里我们按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col; //节点颜色
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_col(RED) //新节点默认红色
{}
};
4. 自平衡机制(变色与旋转)
插入节点的平衡调整
- 若插入节点是根节点,破坏**"根叶黑"**规则,直接将其设为黑色就可以了。
- 插入后,父节点是黑色的,规则未被破坏,无需调整。
- 插入后,父节点是红色的,破坏**"不红红"** 规则,需要根据**"叔父节点的颜色"**分情况处理:
**情况 1:**叔父节点是红色 → 父节点和叔父节点变黑,祖父节点变红(向上递归检查);
**情况 2:**叔父节点是黑色 → 结合节点的位置(左 / 右孩子)执行旋转 + 变色( LL型、RR型、LR 型、RL型)。
情况1和情况2如图示:

示例:插入节点25流程 
示例:插入节点5流程

5. 红黑树的插入
bool Insert(const pair<K, V>& kv)
{
//空树直接插入根节点(黑色)
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
//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; //键已存在,插入失败
}
}
//创建新节点并链接到父亲节点
cur = new Node(kv);
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 (parent == grandfather->_left)//父亲节点是爷爷节点的左孩子
{
// g
// p u
Node* uncle = grandfather->_right;
//情况1:叔父节点为红色-->仅对叔父爷变色,并向上递归检查
if (uncle && uncle->_col == RED)
{
//对叔父爷变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//祖父作为新节点继续检查
cur = grandfather;
parent = cur->_parent;
}
else // 情况2:叔父节点为黑色 旋转+变色
{
if (cur == parent->_left) //cur是父亲节点的左孩子 LL直线型
{
RightRotate(grandfather); //右单旋
//变色
grandfather->_col = RED;
parent->_col = BLACK;
}
else //cur是父亲节点的右孩子 LR折线型
{
LeftRotate(parent);//左单旋
RightRotate(grandfather);//右单旋
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //修复完成,无需继续循环
}
}
else //父亲节点是爷爷节点的右孩子
{
// g
// u p
Node* uncle = grandfather->_left;
//情况1:叔父节点为红色-->仅对叔父爷变色,并向上递归检查
if (uncle && uncle->_col == RED)
{
//变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 祖父作为新节点继续检查
cur = grandfather;
parent = cur->_parent;
}
else //情况2:叔父节点为黑色 旋转+变色
{
if (cur == parent->_right)//cur为父亲节点的右孩子 RR直线型
{
LeftRotate(grandfather);//左单旋
//变色
grandfather->_col = RED;
parent->_col = BLACK;
}
else //cur为父亲节点的左孩子 RL折线型
{
RightRotate(parent);//右单旋
LeftRotate(grandfather);//左单旋
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //修复完成,无需继续循环
}
}
}
_root->_col = BLACK; //确保根节点始终是黑色
return true;
}
删除节点的平衡调整,删除操作更复杂(需维护黑高度一致),本文不作研究。
6. 红黑树的查找
//查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_kv.first)
cur = cur->_right;
else if (key < cur->_kv.first)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
7. 获取树的高度
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
8. 红黑树的验证
8.1 BST性质验证
通过中序遍历检查是否升序
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << " ";
_InOrder(root->_right);
}
8.2 红黑树规则检查
验证红黑树的规则
- **节点颜色只能是红 / 黑:**靠类型约束天然保证,无需检查;
- **根节点为黑色:**直接检查根节点颜色,若根非空且颜色不是黑色则违规;
- **不存在连续红色节点:**递归遍历每个节点,若当前节点为红色,则检查其父节点颜色(父节点必存在,除根节点外),若父节点也为红色,直接判定违规。
- **所有路径黑节点数一致:**先确定 "参考黑节点数":选任意一条从根到空节点的路径(如一直向左),统计黑节点数量作为refNum;递归遍历所有路径:用形参blackNum记录 "从根到当前节点的黑节点数",遇到黑色节点则blackNum++;路径结束验证:走到空节点时,对比当前路径的blackNum和refNum,不一致则违规。
bool IsBalanceTree()
{
if (_root == nullptr)
return true;
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);
}
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
//前序遍历走到空时,一条路径就走完了
if (refNum != blackNum)
{
cout << "存在黑色结点数量不相等的路径" << endl;
return false;
}
return true;
}
// 遇到红色节点,检查它的父亲是否是红色的
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色结点" << endl;
return false;
}
//遇到黑色节点就++
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
9. 总结
红黑树比AVL数更常用的原因:
调整触发频率低(平衡条件宽松):
插入操作:红黑树约 50% 的情况仅需改变颜色,无需旋转;最坏情况仅需 2 次旋转,虽然AVL树的插入也最坏旋转两次,但调整的"成本结构"和"触发频率"差异极大;
删除操作:红黑树处理"黑高缺失"最多需要2次旋转,AVL树删除后可能引发"失衡传播",极端情况下需O(log n)次旋转。
查询性能差距可忽略:实际场景中 "足够快"
红黑树的高度虽然略高,但log₂n 和2log₂n在实际数量下(如n=100万时,log₂n≈20,2log₂n≈40),而现代 CPU 的指令执行速度(每秒数十亿次)下,对查找性能影响可忽略(微秒级甚至纳秒级差异)。
空间开销极小:适配底层系统与海量数据
红黑树:仅需为每个节点存储"颜色"标记------1位二进制即可(如0表示黑色,1表示红色),几乎不占用额外空间。AVL树:每个节点存储 "高度" 或 "平衡因子"------ 通常占用 4 字节(int 类型),若节点数量达千万级,AVL 树额外占用的空间会达到数十 MB,而红黑树可忽略不计。
| 对比维度 | 红黑树 | AVL 树 |
|---|---|---|
| 平衡标准 | 黑高度一致(近似平衡):任意节点到叶子的路径中,黑色节点数相同;最长路径 ≤ 最短路径 ×2 | 严格高度平衡:任意节点的左右子树高度差 ≤ 1 |
| 树高上限 | 2log2(n+1)(n 为节点数) | log2n+1(更矮) |
| 插入旋转次数 | 最多 2 次(仅叔父为黑色时需要) | 最多 2 次(单旋 / 双旋) |
| 删除旋转次数 | 最多 2 次(修复双重黑色节点) | 最多O(logn)次(可能连锁调整) |
| 查找性能 | 略差(树高更高) | 略优(树高更矮) |
| 调整成本 | 低(优先变色,旋转少) | 高(依赖旋转,可能连锁调整) |
| 适用场景 | 频繁插入 / 删除(如容器:C++ map、Java TreeMap) |
频繁查找(如数据库索引、缓存) |
模拟实现红黑树:
// 枚举值表示颜色
enum Colour { RED, BLACK };
// 这里我们按key/value结构实现
template<class K, class V>
struct RBTreeNode
{
pair<K, V> _kv;
RBTreeNode<K, V>* _left;
RBTreeNode<K, V>* _right;
RBTreeNode<K, V>* _parent;
Colour _col; //节点颜色
RBTreeNode(const pair<K, V>& kv)
:_kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
,_col(RED) //新节点默认红色
{}
};
template<class K, class V>
class RBTree
{
typedef RBTreeNode<K, V> Node;
public:
//插入
bool Insert(const pair<K, V>& kv)
{
//空树直接插入根节点(黑色)
if (_root == nullptr)
{
_root = new Node(kv);
_root->_col = BLACK;
return true;
}
//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; //键已存在,插入失败
}
}
//创建新节点并链接到父亲节点
cur = new Node(kv);
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 (parent == grandfather->_left)//父亲节点是爷爷节点的左孩子
{
// g
// p u
Node* uncle = grandfather->_right;
//情况1:叔父节点为红色-->仅对叔父爷变色,并向上递归检查
if (uncle && uncle->_col == RED)
{
//对叔父爷变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
//祖父作为新节点继续检查
cur = grandfather;
parent = cur->_parent;
}
else // 情况2:叔父节点为黑色 旋转+变色
{
if (cur == parent->_left) //cur是父亲节点的左孩子 LL直线型
{
RightRotate(grandfather); //右单旋
//变色
grandfather->_col = RED;
parent->_col = BLACK;
}
else //cur是父亲节点的右孩子 LR折线型
{
LeftRotate(parent);//左单旋
RightRotate(grandfather);//右单旋
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //修复完成,无需继续循环
}
}
else //父亲节点是爷爷节点的右孩子
{
// g
// u p
Node* uncle = grandfather->_left;
//情况1:叔父节点为红色-->仅对叔父爷变色,并向上递归检查
if (uncle && uncle->_col == RED)
{
//变色
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 祖父作为新节点继续检查
cur = grandfather;
parent = cur->_parent;
}
else //情况2:叔父节点为黑色 旋转+变色
{
if (cur == parent->_right)//cur为父亲节点的右孩子 RR直线型
{
LeftRotate(grandfather);//左单旋
//变色
grandfather->_col = RED;
parent->_col = BLACK;
}
else //cur为父亲节点的左孩子 RL折线型
{
RightRotate(parent); //右单旋
LeftRotate(grandfather); //左单旋
//变色
cur->_col = BLACK;
grandfather->_col = RED;
}
break; //修复完成,无需继续循环
}
}
}
_root->_col = BLACK; //确保根节点始终是黑色
return true;
}
//左单旋
void LeftRotate(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 (parentparent == nullptr)
{
_root = subR;
subR->_parent = nullptr;
}
else
{
if (parentparent->_left == parent)
{
parentparent->_left = subR;
}
else
{
parentparent->_right = subR;
}
subR->_parent = parentparent;
}
}
//右单旋
void RightRotate(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 (parent == parentparent->_left)
{
parentparent->_left = subL;
}
else
{
parentparent->_right = subL;
}
subL->_parent = parentparent;
}
}
//中序遍历
void InOrder()
{
_InOrder(_root);
cout << endl;
}
//求树的高度
int Height()
{
return _Height(_root);
}
size_t Size()
{
return _Size(_root);
}
//查找
Node* Find(const K& key)
{
Node* cur = _root;
while (cur)
{
if (key > cur->_kv.first)
cur = cur->_right;
else if (key < cur->_kv.first)
cur = cur->_left;
else
return cur;
}
return nullptr;
}
//验证红黑树
bool IsBalanceTree()
{
if (_root == nullptr)
return true;
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:
void _InOrder(Node* root)
{
if (root == nullptr)
{
return;
}
_InOrder(root->_left);
cout << root->_kv.first << ":" << root->_kv.second << endl;
_InOrder(root->_right);
}
int _Height(Node* root)
{
if (root == nullptr)
return 0;
int leftHeight = _Height(root->_left);
int rightHeight = _Height(root->_right);
return leftHeight > rightHeight ? leftHeight + 1 : rightHeight + 1;
}
size_t _Size(Node* root)
{
if (root == nullptr)
return 0;
return _Size(root->_left) + _Size(root->_right) + 1;
}
bool Check(Node* root, int blackNum, const int refNum)
{
if (root == nullptr)
{
//前序遍历走到空时,一条路径就走完了
if (refNum != blackNum)
{
cout << "存在黑色结点数量不相等的路径" << endl;
return false;
}
return true;
}
// 遇到红色节点,检查它的父亲是否是红色的
if (root->_col == RED && root->_parent->_col == RED)
{
cout << root->_kv.first << "存在连续的红色结点" << endl;
return false;
}
//遇到黑色节点就++
if (root->_col == BLACK)
{
blackNum++;
}
return Check(root->_left, blackNum, refNum)
&& Check(root->_right, blackNum, refNum);
}
private:
Node* _root = nullptr;
};