1. 红黑树概述
红黑树(Red-Black Tree)是一种自平衡的二叉搜索树,它在计算机科学中有着广泛的应用,特别是在C++ STL的map、set等关联容器中作为底层数据结构。与AVL树相比,红黑树对平衡的要求相对宽松,这使得它在插入和删除操作中需要更少的旋转次数,从而在实际应用中具有更好的性能表现。
1.1 什么是红黑树?
红黑树是一种特殊的二叉搜索树,它在每个节点上增加了一个存储位来表示节点的颜色(红色或黑色)。通过对从根到叶子的任何一条简单路径上的节点颜色进行约束,红黑树确保没有一条路径会比其他路径长出两倍,因此是近似平衡的。
红黑树的五大特性(规则):
- 颜色特性:每个节点要么是红色,要么是黑色。
- 根节点特性:根节点必须是黑色。
- 红色节点特性:红色节点的两个子节点必须是黑色(即不能有连续的红色节点)。
- 黑色高度特性:从任一节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。
- 叶子节点特性:每个叶子节点(NIL 节点,空节点)都是黑色的(这一特性在实现中通常隐式处理)。
2. 红黑树的核心原理
2.1 为什么红黑树能保持近似平衡?
红黑树通过颜色约束来间接控制树的平衡。让我们分析一下这些规则如何保证树的高度不会过大:
假设从根节点到任意空节点(NIL)的路径上黑色节点的数量为bh(black height),根据规则4,所有路径的bh值都相同。
- 最短路径:全由黑色节点组成,长度为bh
- 最长路径:由于规则3禁止连续红色节点,最长路径为黑红相间,长度为2×bh
因此,任意路径的长度h满足:bh ≤ h ≤ 2×bh
这意味着最长路径不会超过最短路径的两倍,从而保证了树的近似平衡。
2.2 红黑树与AVL树的对比
| 特性 | 红黑树 | AVL树 |
|---|---|---|
| 平衡标准 | 颜色约束,近似平衡 | 高度差严格为-1,0,1 |
| 旋转次数 | 相对较少 | 相对较多 |
| 查找效率 | O(log n) | O(log n) |
| 插入/删除 | 最多3次旋转 | 最多O(log n)次旋转 |
| 适用场景 | 频繁插入删除 | 频繁查找 |
3. 红黑树的数据结构设计
3.1 节点结构
在 C++ 中,我们使用枚举(enum)来定义节点的颜色。枚举是一种用户定义的数据类型,它允许我们为一组整数值赋予有意义的名称。在这里,我们定义了一个名为 Colour 的枚举,它有两个可能的值:RED 和 BLACK。这样在代码中就可以使用 Colour::RED 和 Colour::BLACK 来代替数字 0 和 1,使代码更易读和维护。
cpp
// 颜色枚举
enum Colour {
RED,
BLACK
};
// 红黑树节点模板类
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 std::pair<K, V>& kv)
: _kv(kv)
, _left(nullptr)
, _right(nullptr)
, _parent(nullptr)
, _col(RED) // 新节点默认为红色
{}
};
3.2 树结构
cpp
template<class K, class V>
class RBTree {
typedef RBTreeNode<K, V> Node;
public:
// 构造函数
RBTree() : _root(nullptr) {}
// 插入操作
bool Insert(const pair<K, V>& kv);
// 查找操作
Node* Find(const K& key);
// 验证红黑树性质
bool IsBalance();
private:
// 旋转操作
void RotateL(Node* parent); // 左单旋
void RotateR(Node* parent); // 右单旋
// 验证辅助函数
bool _Check(Node* root, int blackNum, const int refNum);
private:
Node* _root = nullptr; // 根节点
};
4. 红黑树的插入操作详解
红黑树的插入操作是理解红黑树的关键。插入过程分为两个阶段:
- 二叉搜索树插入:按照 BST 规则插入新节点
- 颜色调整与旋转:修复可能违反的红黑树性质
4.1 插入的基本规则
- 空树插入:新节点作为根节点,颜色设为黑色
- 非空树插入:新节点颜色设为红色(如果设为黑色会立即违反规则4)
- 父节点为黑色:直接插入,不违反任何规则
- 父节点为红色:需要根据叔叔节点的颜色进行不同处理
4.2 插入情况分析
为了便于讨论,我们定义以下符号:
- c (cur):当前节点(新插入的节点或向上调整的节点)
- p (parent):c的父节点
- g (grandfather):p的父节点,c的祖父节点
- u (uncle):p的兄弟节点
情况1:叔叔节点u存在且为红色
g(B) g(R)
/ \ / \
p(R) u(R) => p(B) u(B)
/ /
c(R) c(R)
处理方式:
- 将p和u变为黑色
- 将g变为红色
- 将g作为新的c,继续向上调整
原理:通过变色保持黑色高度不变,同时解决连续红色节点问题。
情况 2:叔叔节点 u 不存在或为黑色,且 c 是 p 的左孩子(LL 型)
g(B) p(B)
/ \ / \
p(R) u(B) => c(R) g(R)
/ \
c(R) u(B)
处理方式:
- 以 g 为轴进行右单旋
- 将 p 变为黑色,g 变为红色
情况3:叔叔节点u不存在或为黑色,且c是p的右孩子(LR型)
g(B) c(B)
/ \ / \
p(R) u(B) => p(R) g(R)
\ / \
c(R) (子树) u(B)
处理方式:
- 以p为轴进行左单旋
- 以g为轴进行右单旋
- 将c变为黑色,g变为红色
情况 4 和情况 5:对称情况(RR 型和 RL 型)
处理方式与情况2和情况3对称,只是旋转方向相反。
5. 插入操作的 C++ 实现
cpp
template<class K, class V>
bool RBTree<K, V>::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; // 键值已存在
}
}
// 3. 创建新节点并插入
cur = new Node(kv);
cur->_col = RED; // 新节点初始为红色
if (parent->_kv.first < kv.first) {
parent->_right = cur;
} else {
parent->_left = cur;
}
cur->_parent = parent;
// 4. 颜色调整(当父节点为红色时需要调整)
while (parent && parent->_col == RED) {
Node* grandfather = parent->_parent;
// 情况1:父节点是祖父节点的左孩子
if (parent == grandfather->_left) {
Node* uncle = grandfather->_right;
// 情况1.1:叔叔存在且为红色(变色处理)
if (uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
// 继续向上调整
cur = grandfather;
parent = cur->_parent;
}
// 情况1.2:叔叔不存在或为黑色(旋转+变色)
else {
// LL型:cur是parent的左孩子
if (cur == parent->_left) {
RotateR(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
// LR型:cur是parent的右孩子
else {
RotateL(parent);
RotateR(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break; // 旋转后调整完成
}
}
// 情况2:父节点是祖父节点的右孩子(对称处理)
else {
Node* uncle = grandfather->_left;
// 情况2.1:叔叔存在且为红色
if (uncle && uncle->_col == RED) {
parent->_col = uncle->_col = BLACK;
grandfather->_col = RED;
cur = grandfather;
parent = cur->_parent;
}
// 情况2.2:叔叔不存在或为黑色
else {
// RR型:cur是parent的右孩子
if (cur == parent->_right) {
RotateL(grandfather);
parent->_col = BLACK;
grandfather->_col = RED;
}
// RL型:cur是parent的左孩子
else {
RotateR(parent);
RotateL(grandfather);
cur->_col = BLACK;
grandfather->_col = RED;
}
break;
}
}
}
// 5. 确保根节点为黑色
_root->_col = BLACK;
return true;
}
6. 旋转操作实现
6.1 左单旋(Left Rotation)
cpp
template<class K, class V>
void RBTree<K, V>::RotateL(Node* parent) {
Node* subR = parent->_right;
Node* subRL = subR->_left;
// 1. 处理subRL
parent->_right = subRL;
if (subRL) {
subRL->_parent = parent;
}
// 2. 处理parent的父节点
subR->_parent = parent->_parent;
if (parent == _root) {
_root = subR;
} else {
if (parent == parent->_parent->_left) {
parent->_parent->_left = subR;
} else {
parent->_parent->_right = subR;
}
}
// 3. 连接parent和subR
subR->_left = parent;
parent->_parent = subR;
}
6.2 右单旋(Right Rotation)
cpp
template<class K, class V>
void RBTree<K, V>::RotateR(Node* parent) {
Node* subL = parent->_left;
Node* subLR = subL->_right;
// 1. 处理subLR
parent->_left = subLR;
if (subLR) {
subLR->_parent = parent;
}
// 2. 处理parent的父节点
subL->_parent = parent->_parent;
if (parent == _root) {
_root = subL;
} else {
if (parent == parent->_parent->_left) {
parent->_parent->_left = subL;
} else {
parent->_parent->_right = subL;
}
}
// 3. 连接parent和subL
subL->_right = parent;
parent->_parent = subL;
}
7. 红黑树的验证
验证红黑树需要检查所有五个性质。以下是完整的验证实现:
cpp
template<class K, class V>
bool RBTree<K, V>::IsBalance() {
// 1. 空树是平衡的
if (_root == nullptr) {
return true;
}
// 2. 检查根节点是否为黑色(规则2)
if (_root->_col == RED) {
std::cout << "根节点必须为黑色!" << std::endl;
return false;
}
// 3. 获取参考黑色节点数量(最左路径)
int refBlackNum = 0;
Node* cur = _root;
while (cur) {
if (cur->_col == BLACK) {
++refBlackNum;
}
cur = cur->_left;
}
// 4. 递归检查所有性质
return _Check(_root, 0, refBlackNum);
}
template<class K, class V>
bool RBTree<K, V>::_Check(Node* root, int blackNum, const int refBlackNum) {
if (root == nullptr) {
// 到达叶子节点,检查黑色节点数量
if (blackNum != refBlackNum) {
std::cout << "存在黑色节点数量不等的路径!" << std::endl;
return false;
}
return true;
}
// 检查规则3:不能有连续的红色节点
if (root->_col == RED && root->_parent && root->_parent->_col == RED) {
std::cout << "发现连续的红色节点: " << root->_kv.first << std::endl;
return false;
}
// 统计黑色节点数量
if (root->_col == BLACK) {
++blackNum;
}
// 递归检查左右子树
return _Check(root->_left, blackNum, refBlackNum)
&& _Check(root->_right, blackNum, refBlackNum);
}
8. 查找操作实现
cpp
template<class K, class V>
typename RBTree<K, V>::Node* RBTree<K, V>::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; // 未找到
}
9. 红黑树的性能分析
9.1 时间复杂度
- 查找:O(log n) - 由于红黑树是近似平衡的,树的高度最多为 2log(n+1)
- 插入:O(log n) - 最多需要3次旋转
- 删除:O(log n) - 最多需要3次旋转
9.2 空间复杂度
- 空间开销:O(n) --- 每个节点需要存储颜色信息和父指针
- 额外开销:相比普通BST,每个节点多1位颜色信息和1个父指针
9.3 实际应用优势
- 内存效率:相比 AVL 树,红黑树需要更少的旋转操作,减少了内存访问次数
- 缓存友好:相对平衡的结构使得数据访问模式更加可预测
- 实现简单:虽然概念复杂,但实现相对 AVL 树更简单
10. 红黑树的应用场景
10.1 C++ STL 中的应用
红黑树是 C++ 标准模板库中以下容器的底层实现:
map:有序键值对容器set:有序集合容器multimap:允许重复键的有序映射multiset:允许重复值的有序集合
11. 红黑树操作可视化示例
由于无法直接生成动图,我将用 Mermaid 流程图展示红黑树的插入过程:
#mermaid-svg-tUBdtareJWxCtBvF{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tUBdtareJWxCtBvF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tUBdtareJWxCtBvF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tUBdtareJWxCtBvF .error-icon{fill:#552222;}#mermaid-svg-tUBdtareJWxCtBvF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tUBdtareJWxCtBvF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tUBdtareJWxCtBvF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tUBdtareJWxCtBvF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tUBdtareJWxCtBvF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tUBdtareJWxCtBvF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tUBdtareJWxCtBvF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tUBdtareJWxCtBvF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tUBdtareJWxCtBvF .marker.cross{stroke:#333333;}#mermaid-svg-tUBdtareJWxCtBvF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tUBdtareJWxCtBvF p{margin:0;}#mermaid-svg-tUBdtareJWxCtBvF .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tUBdtareJWxCtBvF .cluster-label text{fill:#333;}#mermaid-svg-tUBdtareJWxCtBvF .cluster-label span{color:#333;}#mermaid-svg-tUBdtareJWxCtBvF .cluster-label span p{background-color:transparent;}#mermaid-svg-tUBdtareJWxCtBvF .label text,#mermaid-svg-tUBdtareJWxCtBvF span{fill:#333;color:#333;}#mermaid-svg-tUBdtareJWxCtBvF .node rect,#mermaid-svg-tUBdtareJWxCtBvF .node circle,#mermaid-svg-tUBdtareJWxCtBvF .node ellipse,#mermaid-svg-tUBdtareJWxCtBvF .node polygon,#mermaid-svg-tUBdtareJWxCtBvF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tUBdtareJWxCtBvF .rough-node .label text,#mermaid-svg-tUBdtareJWxCtBvF .node .label text,#mermaid-svg-tUBdtareJWxCtBvF .image-shape .label,#mermaid-svg-tUBdtareJWxCtBvF .icon-shape .label{text-anchor:middle;}#mermaid-svg-tUBdtareJWxCtBvF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tUBdtareJWxCtBvF .rough-node .label,#mermaid-svg-tUBdtareJWxCtBvF .node .label,#mermaid-svg-tUBdtareJWxCtBvF .image-shape .label,#mermaid-svg-tUBdtareJWxCtBvF .icon-shape .label{text-align:center;}#mermaid-svg-tUBdtareJWxCtBvF .node.clickable{cursor:pointer;}#mermaid-svg-tUBdtareJWxCtBvF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tUBdtareJWxCtBvF .arrowheadPath{fill:#333333;}#mermaid-svg-tUBdtareJWxCtBvF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tUBdtareJWxCtBvF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tUBdtareJWxCtBvF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tUBdtareJWxCtBvF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tUBdtareJWxCtBvF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tUBdtareJWxCtBvF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tUBdtareJWxCtBvF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tUBdtareJWxCtBvF .cluster text{fill:#333;}#mermaid-svg-tUBdtareJWxCtBvF .cluster span{color:#333;}#mermaid-svg-tUBdtareJWxCtBvF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tUBdtareJWxCtBvF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tUBdtareJWxCtBvF rect.text{fill:none;stroke-width:0;}#mermaid-svg-tUBdtareJWxCtBvF .icon-shape,#mermaid-svg-tUBdtareJWxCtBvF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tUBdtareJWxCtBvF .icon-shape p,#mermaid-svg-tUBdtareJWxCtBvF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tUBdtareJWxCtBvF .icon-shape .label rect,#mermaid-svg-tUBdtareJWxCtBvF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tUBdtareJWxCtBvF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tUBdtareJWxCtBvF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tUBdtareJWxCtBvF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-tUBdtareJWxCtBvF .start>*{fill:#e1f5fe!important;stroke:#01579b!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .start span{fill:#e1f5fe!important;stroke:#01579b!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .decision>*{fill:#fff3e0!important;stroke:#f57c00!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .decision span{fill:#fff3e0!important;stroke:#f57c00!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .operation>*{fill:#e8f5e8!important;stroke:#2e7d32!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .operation span{fill:#e8f5e8!important;stroke:#2e7d32!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .colorChange>*{fill:#fce4ec!important;stroke:#c2185b!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .colorChange span{fill:#fce4ec!important;stroke:#c2185b!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .rotate>*{fill:#f3e5f5!important;stroke:#7b1fa2!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .rotate span{fill:#f3e5f5!important;stroke:#7b1fa2!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .done>*{fill:#c8e6c9!important;stroke:#388e3c!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .done span{fill:#c8e6c9!important;stroke:#388e3c!important;stroke-width:2px!important;}#mermaid-svg-tUBdtareJWxCtBvF .noteStyle>*{fill:#fffde7!important;stroke:#f9a825!important;stroke-dasharray:5 5!important;}#mermaid-svg-tUBdtareJWxCtBvF .noteStyle span{fill:#fffde7!important;stroke:#f9a825!important;stroke-dasharray:5 5!important;} 黑色
红色
红色
黑色/不存在
LL/RR型
LR/RL型
插入新节点(红色)
父节点颜色?
插入完成
叔叔节点颜色?
变色处理
父节点变黑
叔叔节点变黑
祖父节点变红
祖父作为新节点
继续向上调整
插入位置类型?
单旋+变色
执行单旋
(LL右旋 / RR左旋)
父节点变黑
祖父节点变红
双旋+变色
先执行一次旋转
调整结构
再执行一次旋转
完成平衡
当前节点变黑
祖父节点变红
💡 关键操作说明:
• 变色:改变节点颜色,保持黑高不变
• 单旋:一次旋转调整结构
• 双旋:两次旋转处理复杂情况