RB-Tree(上)
红黑树(RB-Tree)是一种自平衡二叉搜索树,通过节点颜色与旋转操作保证树的平衡性,常用于高效实现关联容器(如 STL 中的 map、set)。
RB-Tree(上)
RB-tree(红黑树)是加了平衡条件的二叉搜索树,需满足以下规则:
- 每个节点非红即黑。
- 根节点是黑色。
- 红节点不能有红色子节点(即不能连续红)。
从任一节点到其所有 NULL(叶端)路径上的黑节点数相同。
- 这些路径是指:从某个节点出发,往下走到“空指针”结束(即 NULL 节点)。
- 这些“NULL 节点”并不是真正存在于树中的实际节点,但在理论分析中,会将每个叶子节点的左右子指针(即指向 NULL)视为“虚拟叶子节点”。
- 这些“NULL”被视作黑色节点,这点在红黑树的定义中是默认的。
每个叶子节点(即 NULL 节点)都是黑色的。
在 红黑树的语境下:
NULL 被视作叶子节点,并且是黑色的。
一般来说红黑树中的“叶子节点”就是指的 NULL 节点(并不是没有孩子的真实节点)。
但有的时候是指实际节点,具体看情况。
新增节点设为红色,若违反规则需通过“变色”和“旋转”调整恢复平衡。
后续图示不再画出 NULL 节点。
AVL vs 红黑树的对比
特性 | AVL 树 | 红黑树 |
---|---|---|
平衡条件 | 任一节点左右子树高度差 ≤ 1 | 任一路径黑节点数相同,不允许连续红 |
平衡“严格度” | 更严格(几乎是最平衡的) | 较宽松(可容忍部分不平衡) |
插入调整 | 可能需要多次旋转 | 最多一次旋转 + 多次变色 |
删除调整 | 较复杂,旋转多 | 简洁,最多三次旋转 |
查找效率 | 更快(因树更矮) | 略慢(树更高) |
应用场景 | 注重查找效率,如数据库索引 | 注重插入/删除效率,如操作系统、STL map/set |
插入节点
插入 3、8、35、75 后,都会违反红黑树的规则,因此需要调整结构。这些调整包括:旋转和变色,以恢复红黑树的五大规则(尤其是红红相连和黑高不一致的问题)。
为便于讨论,我们为特定节点设定代称:
- 新插入节点为 X
- 父节点为 P
- 爷爷节点为 G
- 叔叔节点(P 的兄弟)为 S
- 曾爷爷节点为 GG
推论
- 由于按二叉搜索树规则,X 一定是叶节点;
- 根据红黑树规则 4,X 必须为红色;
- 若其父 P 也是红色,就违反了红黑树规则 3,必须进行调整(如果父节点是黑色,直接插入红色新节点,不需要任何旋转或染色操作;如果插入的就是根节点,那就把插入到红色节点变成黑色)。这时,G 必为黑色(因为原树满足红黑树规则)。
接下来根据插入节点 X 的位置 和叔叔节点 S、曾爷爷节点GG 的颜色,有三类典型处理情形。
情形一:插入的节点是根节点
处理方法:直接变黑。
情形二:插入节点的叔叔是黑色
叔叔节点 S 是黑色,且 X 是“外侧插入”
处理方法:单旋转加变色。
- 对 P 和 G 进行 一次单旋转
- 然后交换转轴 P 和旋转点 G 的颜色
这样就能重新满足红黑树的规则,尤其是规则 3(红色不能相邻)。这是最简单、最直接的一种调整方式。
虽然此时可能出现子树高度差超过 1 的不平衡(如 A、B 为 null 而 D 或 E 非 null),但这无妨,因为红黑树的平衡要求本就比 AVL 树宽松。
经验表明,红黑树的平均搜索效率和 AVL 树几乎相同,实际效果很好。
叔叔节点 S 是黑色,且 X 是“内侧插入”
处理方法:双旋转加变色。
- 对 P 和 X 进行 一次单旋转,并交换 G 和 X 的颜色
- 再对 G 进行 一次单旋转
情形三:插入节点的叔叔是红色
叔叔节点 S 是红色,且 X 是“外侧插入”
红黑树插入调整中,“叔叔节点 S 是红色”时,不论 X 是内侧插入还是外侧插入,这两种情况的处理方式是一样的——都是“重新染色并递归向上调整”。换句话说,叔叔是红色时,不旋转,只变色,并往上继续递归修正。
处理方法:
- 父节点和叔叔节点同时变为黑色
- 爷爷节点染成红色
- 将“当前节点指针”上移到爷爷节点,循环判断,直到满足红黑树性质(如根节点为黑,或没有连续红节点)
如果曾祖父 GG 是黑色,调整完成;若 GG 是红色,则把 G 当成新的插入节点,继续向上调整,直到没有连续红色父子节点为止。
小结
插入信息 | 处理办法 |
---|---|
插入的节点是根节点 | 直接变黑。 |
插入节点的叔叔是黑色 | (LL,RR,LR,RL)旋转,然后变色。 |
插入节点的叔叔是红色 | 不旋转,只变色,把父亲节点,叔叔节点,爷爷节点都变色,然后当前节点指针上移到爷爷节点,循环判断。 |
Top-Down 插入优化策略
普通插入(Bottom-Up)
- 先一路找到插入位置,插入节点(红色)。
- 如果“父子皆红”才开始从插入点往上回溯修正。
- 回溯过程中可能会遇到多个节点需要旋转或变色。
- 问题:最坏情况下会一路递归到根,导致多次检查与栈操作。
Top-Down 插入
为了避免在插入过程中出现“父子节点同为红色”的情况,特别是当叔叔节点为红色时可能引发的向上颜色调整,可以采用一种 自顶向下(Top-Down) 的处理策略:
- 在向下走的过程中,一旦遇到某个节点 X 两个孩子都是红色,立刻:
- X 变红
- 两个孩子变黑
- 这样在到达插入位置之前,就已经打破了可能形成的“父子红冲突”。
- 新节点插入后,冲突范围被局限在很小的局部,通常只需要一次旋转(或者甚至不用)。
对比效果
逻辑本质
- 是同一个修正过程,只是把回溯改成了下行时的“预修正”。
- 所以它不会改变红黑树最终结构,只是改变调整的时机。
性能差别
- Bottom-Up:可能需要递归回溯 N 层(N 为树高度)。
- Top-Down:始终是 O(1) 栈空间(因为一路向下),没有显式回溯。
- 在需要频繁插入、且数据分布不平衡的情况下,Top-Down 的常数开销会小一些,尤其适合无递归的实现(例如在内核或嵌入式里)。
缺点
- 写法更复杂,要考虑沿途变色可能引起的旋转(尤其当父节点是红色时)。
- 有些情况下会多做一次颜色翻转(虽然不影响最终树形)。
这种变换能预先消除将来可能出现的红冲突,从而减少后续旋转和颜色调整的复杂度,也能避免向上递归调整,提升插入效率。它并不是“额外优化红黑树的平衡性质”,只是把调整从事后搬到事前,节省了回溯的栈操作,让插入过程可以一次从根到叶的线性遍历完成,适合要求无递归的实现。
即使使用 Top-Down 插入法,在插入节点 A 的路径上先做了变色处理,但若插入 A 后,A 的父节点 P 是红色,仍会产生“父子皆红”的违规情况。此时:
- 若是“外侧插入”(A 和 P 同侧)→ 执行一次单旋转,并交换颜色。
- 若是“内侧插入”(A 和 P 异侧)→ 执行一次双旋转,并调整颜色。
完成这些操作后,红黑树性质恢复。接着,后续节点(如节点 35)插入就会很简单:
只需普通插入,或插入后配合一次简单的旋转即可。
RB-tree 的节点设计
RB-tree 是具有红黑节点和左右子树的二叉搜索树。SGI STL 实现中采用双层节点结构,并设有 parent
指针,便于向上回溯。极值查找如 minimum()
、maximum()
操作也非常简单高效。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 定义颜色类型为 bool
typedef bool __rb_tree_color_type;
// 红色为 false (0),黑色为 true (1)
const __rb_tree_color_type __rb_tree_red = false;
const __rb_tree_color_type __rb_tree_black = true;
// 基础节点结构体,包含颜色、父子节点指针
struct __rb_tree_node_base {
typedef __rb_tree_color_type color_type; // 节点颜色类型(红或黑)
typedef __rb_tree_node_base* base_ptr; // 指向自身类型的指针别名
color_type color; // 节点颜色,非红即黑
base_ptr parent; // 指向父节点的指针(红黑树的操作常需要回溯父节点)
base_ptr left; // 指向左子节点的指针
base_ptr right; // 指向右子节点的指针
// 查找以 x 为根的子树中的最小值节点(一直往左走)
static base_ptr minimum(base_ptr x) {
while (x->left != 0) x = x->left;
// 一直向左走就能找到最小值
return x;
}
// 查找以 x 为根的子树中的最大值节点(一直往右走)
static base_ptr maximum(base_ptr x) {
while (x->right != 0) x = x->right;
// 一直向右走就能找到最大值
return x;
}
};
// 继承基础节点结构体,并添加 value 域(即模板值类型)
template <class Value>
struct __rb_tree_node : public __rb_tree_node_base {
typedef __rb_tree_node<Value>* link_type; // 指向自身类型的指针别名
Value value_field; // 存储的数据内容(节点的值)
};
- 红黑树节点由
__rb_tree_node_base
提供通用结构,包括颜色和指针。 __rb_tree_node<Value>
是模板化的节点,额外持有值字段value_field
。minimum()
和maximum()
利用了二叉搜索树的特性,分别查找最小和最大值节点。
RB-tree 的迭代器
为了将 RB-Tree 实现为泛型容器,迭代器设计是关键,需支持以下操作:
- 类型分类(category)
- 前进(++)、后退(–)
- 提领(*)、成员访问(->)
SGI STL 采用了 双层结构 的设计:
- 节点:
__rb_tree_node
继承自__rb_tree_node_base
- 迭代器:
__rb_tree_iterator
继承自__rb_tree_base_iterator
这种设计类似 slist
,利于统一操作与灵活转换,也方便我们深入分析 RB-Tree 的行为和状态。
header 节点
header
的位置和作用
在 SGI STL 的 rb_tree
里,header
节点是一棵红黑树之外的特殊节点,它的结构是这样的:
header
是基类__rb_tree_node_base
类型的节点,它只包含最基础的指针和颜色信息,没有存储具体的值。- 普通的红黑树节点是
__rb_tree_node
,它继承自__rb_tree_node_base
,并额外存储了数据字段(Value)。
主要作用:
- 保存根节点指针(
header->parent
):header->parent
指向树的真正根节点。 - 保存最小节点指针(
header->left
):header->left
指向树中的最小节点,方便begin()
常数时间返回。 - 保存最大节点指针(
header->right
):header->right
指向树中的最大节点,方便end()
前一个迭代器的查找。 - 充当
end()
迭代器的 node:迭代器到达最后一个节点的下一个位置时,node
会指向header
。
RB-Tree 迭代器特性与前后移动操作
- 属于双向迭代器,不支持随机访问。
- 提领(
*
)与成员访问(->
) 与list
类似。 - 前进操作
operator++()
会调用基类的increment()
- 后退操作
operator--()
会调用基类的decrement()
前后移动都遵循 二叉搜索树的中序遍历规则,实现上对根节点处理有些特殊技巧。
RB-tree 基础迭代器结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
struct __rb_tree_base_iterator {
typedef __rb_tree_node_base::base_ptr base_ptr;
typedef bidirectional_iterator_tag iterator_category;
typedef ptrdiff_t difference_type;
base_ptr node; // 当前迭代器指向的节点指针
// 中序遍历的后继节点(++ 操作)
void increment() {
if (node->right != 0) {
// 情况 1:当前节点有右子树
// 后继是右子树中最左节点
node = node->right;
while (node->left != 0)
node = node->left;
} else {
// 情况 2:当前节点无右子树
// 向上回溯,寻找第一个祖先节点,使当前节点是该祖先的左子节点
base_ptr y = node->parent;
while (node == y->right) {
node = y;
y = y->parent;
}
// 退出循环时:
// 情况 2.1:y 是第一个使 node 成为其左子节点的祖先,
// 此时循环前的 node 的直接后继就是 y
// 情况 2.2:y 是 header(哨兵),循环后的 node 是根节点,
// 循环前的 node 实际是最大值节点,
// 经过下面的判断后,node 被设为 header,表示迭代器到达 end()
if (node->right != y)
node = y;
}
}
// 中序遍历的前驱节点(-- 操作)
void decrement() {
// 情况 1:当前迭代器指向 header(end())
// header 颜色为红色,且其父的父节点是 header 本身
if (node->color == _rb_tree_red && node->parent->parent == node) {
// 前驱是树中最大节点,位于 header->right
node = node->right;
} else if (node->left != 0) {
// 情况 2:当前节点有左子树
// 前驱是左子树中最右节点
base_ptr y = node->left;
while (y->right != 0)
y = y->right;
node = y;
} else {
// 情况 3:当前节点无左子树
// 向上回溯,找到第一个祖先节点,使当前节点是该祖先的右子节点
base_ptr y = node->parent;
while (node == y->left) {
node = y;
y = y->parent;
}
// y 是中序遍历的前驱节点
node = y;
}
}
};
RB-tree 正规迭代器结构(继承基层迭代器):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
template <class Value, class Ref, class Ptr>
struct __rb_tree_iterator : public __rb_tree_base_iterator {
// 迭代器中元素的类型
typedef Value value_type;
// 解引用时返回的引用类型,可能是 Value& 或 const Value&
typedef Ref reference;
// 迭代器指针类型,可能是 Value* 或 const Value*
typedef Ptr pointer;
// iterator 和 const_iterator 类型定义,方便使用
typedef __rb_tree_iterator<Value, Value&, Value*> iterator;
typedef __rb_tree_iterator<Value, const Value&, const Value*> const_iterator;
typedef __rb_tree_iterator<Value, Ref, Ptr> self; // 当前迭代器类型自身别名
typedef __rb_tree_node<Value>* link_type; // 节点指针类型
// 构造函数
__rb_tree_iterator() {} // 默认构造,节点为空
__rb_tree_iterator(link_type x) { // 通过节点指针构造迭代器
node = x;
}
__rb_tree_iterator(const iterator& it) { // 通过非 const 迭代器构造 const 迭代器
node = it.node;
}
// 解引用操作符:通过迭代器访问节点的值
reference operator*() const {
return link_type(node)->value_field; // 节点的值成员变量,一般叫 value 或 value_field
}
#ifndef __SGI_STL_NO_ARROW_OPERATOR
// 箭头操作符,返回指向节点值的指针,方便通过迭代器访问成员
pointer operator->() const {
return &(operator*());
}
#endif
// 前置 ++ 运算符:移动到中序遍历的下一个节点
self& operator++() {
increment(); // 调用基类或成员函数完成节点跳转
return *this;
}
// 后置 ++ 运算符:移动到下一个节点,返回递增前的迭代器副本
self operator++(int) {
self tmp = *this; // 复制当前迭代器
increment();
return tmp; // 返回递增前的迭代器
}
// 前置 -- 运算符:移动到中序遍历的前一个节点
self& operator--() {
decrement(); // 调用基类或成员函数完成节点跳转
return *this;
}
// 后置 -- 运算符:移动到前一个节点,返回递减前的迭代器副本
self operator--(int) {
self tmp = *this; // 复制当前迭代器
decrement();
return tmp; // 返回递减前的迭代器
}
};
increment()
的状况 2.2: 当迭代器指向的是整棵树的最大节点,执行 ++
时会指向 header
,也就是 end()
。
decrement()
的状况 1: 当迭代器是 end()
(即指向 header
)时,执行 --
,实际返回的是最大节点(最右节点)。
RB-tree 的数据结构
下面是红黑树(RB-tree)的定义简述:
- 使用专属的空间配置器,每次分配一个节点大小的内存。
- 定义了多种类型,用于管理整棵红黑树的数据结构。
- 包含一个仿函数(functor),用于比较节点的大小,保证树的有序性。
- 提供了若干成员函数,用于插入、删除、查找等操作。
这些设计保证了红黑树的灵活性和高效性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
template <class Key, class Value, class KeyOfValue, class Compare, class Alloc = allocator>
class rb_tree {
protected:
typedef void* void_pointer;
typedef __rb_tree_node_base* base_ptr; // 基础节点指针类型,指向红黑树节点基类
typedef __rb_tree_node<Value> rb_tree_node; // 红黑树节点具体类型,存储Value数据
typedef simple_alloc<rb_tree_node, Alloc> rb_tree_node_allocator; // 节点内存配置器,用于节点的分配和释放
typedef __rb_tree_color_type color_type; // 节点颜色类型,红或黑
public:
// 公开类型定义,方便外部访问
typedef Key key_type; // 键的类型
typedef Value value_type; // 节点存储值的类型
typedef value_type* pointer; // 指向值的指针类型
typedef const value_type* const_pointer; // 指向常量值的指针类型
typedef value_type& reference; // 值的引用类型
typedef const value_type& const_reference; // 常量值引用类型
typedef rb_tree_node* link_type; // 节点指针类型
typedef size_t size_type; // 容器大小类型
typedef ptrdiff_t difference_type; // 差值类型
protected:
// 内存管理相关函数
// 分配一个新的节点内存
link_type get_node() {
return rb_tree_node_allocator::allocate();
}
// 释放一个节点内存
void put_node(link_type p) {
rb_tree_node_allocator::deallocate(p);
}
// 创建节点(分配+构造)
link_type create_node(const value_type& x) {
link_type tmp = get_node(); // 先分配内存
__STLTRY {
construct(&tmp->value_field, x); // 构造节点数据部分
} __STL_UNWIND(put_node(tmp)); // 构造失败时释放内存
return tmp;
}
// 复制节点,复制值和颜色,但左右子节点置空
link_type clone_node(link_type x) {
link_type tmp = create_node(x->value_field); // 复制节点值
tmp->color = x->color; // 复制颜色
tmp->left = 0;
tmp->right = 0;
return tmp;
}
// 销毁节点(析构并释放内存)
void destroy_node(link_type p) {
destroy(&p->value_field);
put_node(p);
}
protected:
// 树的核心数据成员
size_type node_count; // 当前树中节点总数
link_type header; // 特殊节点,作为树的“头”,方便操作树结构
Compare key_compare; // 用于比较键值的仿函数
// 通过header节点访问红黑树中的重要节点:
// root节点、最左(最小)节点、最右(最大)节点
link_type& root() const { return (link_type&) header->parent; }
link_type& leftmost() const { return (link_type&) header->left; }
link_type& rightmost() const { return (link_type&) header->right; }
// 静态辅助函数,方便访问节点的成员指针和值
static link_type& left(link_type x) { return (link_type&)(x->left); }
static link_type& right(link_type x) { return (link_type&)(x->right); }
static link_type& parent(link_type x) { return (link_type&)(x->parent); }
static reference value(link_type x) { return x->value_field; }
static const Key& key(link_type x) { return KeyOfValue()(value(x)); }
static color_type& color(link_type x) { return (color_type&)(x->color); }
// 以上函数对 base_ptr 同样支持,方便类型转换和操作
// 查找子树的最小和最大节点
static link_type minimum(link_type x) { return (link_type)__rb_tree_node_base::minimum(x); }
static link_type maximum(link_type x) { return (link_type)__rb_tree_node_base::maximum(x); }
public:
// 迭代器类型定义,方便外部使用
typedef __rb_tree_iterator<value_type, reference, pointer> iterator;
private:
// 内部辅助函数声明
iterator __insert(base_ptr x, base_ptr y, const value_type& v);
link_type __copy(link_type x, link_type p);
void __erase(link_type x);
// 初始化树结构
void init() {
header = get_node(); // 分配header节点
color(header) = _rb_tree_red; // header颜色设为红,便于区分
root() = 0; // 空树时root为空
leftmost() = header; // header的left指向自己,表示空树
rightmost() = header; // header的right指向自己,表示空树
}
public:
// 构造函数,初始化空树,传入比较函数对象
rb_tree(const Compare& comp = Compare()) : node_count(0), key_compare(comp) {
init();
}
// 析构函数,清空树并释放header节点
~rb_tree() {
clear(); // 清空所有节点
put_node(header); // 释放header
}
// 访问器
// 返回比较函数对象
Compare key_comp() const { return key_compare; }
// 返回指向最小元素的迭代器
iterator begin() { return leftmost(); }
// 返回表示end()的迭代器(header节点)
iterator end() { return iterator(header); }
// 判断是否为空树
bool empty() const { return node_count == 0; }
// 返回节点数量
size_type size() const { return node_count; }
// 返回最大容量(通常是size_t最大值)
size_type max_size() const { return size_type(-1); }
// 插入接口(详细实现略)
// 保持节点值独一无二
pair<iterator, bool> insert_unique(const value_type& x);
// 允许节点值重复
iterator insert_equal(const value_type& x);
// ...
};
header
节点是红黑树中一个特殊哨兵节点,用来简化树操作,比如空树时的根节点为空的情况等。KeyOfValue
是一个仿函数,用来从Value
中提取对应的Key
,方便做键比较。- 内存管理使用
simple_alloc
封装,负责节点内存分配与释放。 insert_unique
和insert_equal
分别对应不允许重复键和允许重复键的插入操作。