#CS225
t
,其平衡因子定义为:
$$
b(t) = height(t \rightarrow right) - height(t \rightarrow left)
$$
其中 height(NULL)
(空节点的高度) 通常定义为 -1。t
,其平衡因子 $b(t)$ 都在集合 ${-1, 0, 1}$ 中,那么这棵树就是一棵 AVL 树。换句话说,任何节点的左子树和右子树的高度差最多为 1。t
的平衡因子满足 $b(t) \ge 2$ 或 $b(t) \le -2$,那么 AVL 属性就被破坏了,这棵树需要在该节点处进行再平衡 (rebalancing)
1
2
3
4
5
6
7
struct TreeNode {
T key; // 键值
unsigned height; // 存储以此节点为根的子树的高度
TreeNode *left; // 左子节点指针
TreeNode *right; // 右子节点指针
};
目的 (Purpose): 当在某个节点 (我们称之为 z ) 检测到不平衡时,我们使用旋转操作来恢复 AVL 属性 ($ |
b | \le 1$),同时关键地要保持 BST 的搜索属性 (BST search property)。旋转是局部的指针修改操作,花费 $O(1)$ 时间。 |
[[ Data Structure-2#核心旋转操作 ]]
z
) 识别了四种情况。
z
不平衡,$b(z) = 2$,并且不平衡是由于插入到 z
的右子节点 (y
) 的右子树中引起的。(路径:右-右,Right-Right)。balance(z) == 2
并且 balance(z->right) == 1
。(注意:删除时 balance(z->right)
也可能为 0)。z
执行一次左旋 (Left Rotation)。y
成为这个子树的新根。z
不平衡,$b(z) = -2$,并且不平衡是由于插入到 z
的左子节点 (y
) 的左子树中引起的。(路径:左-左,Left-Left)。balance(z) == -2
并且 balance(z->left) == -1
。(注意:删除时 balance(z->left)
也可能为 0)。z
执行一次右旋 (Right Rotation)。y
成为新根。z
不平衡,$b(z) = -2$,但!不平衡是由于插入到 z
的左子节点 (y
) 的右子树中引起的。(路径:左-右,Left-Right,一个 “之” 字形)。balance(z) == -2
并且 balance(z->left) == 1
。y
(z->left
) 执行一次左旋 (Left Rotation)。z
执行一次右旋 (Right Rotation)。
“之” 字路径上的孙子节点 (x
) 成为新根。z
不平衡,$b(z) = 2$,但!不平衡是由于插入到 z
的右子节点 (y
) 的左子树中引起的。(路径:右-左,Right-Left,一个 “之” 字形)。balance(z) == 2
并且 balance(z->right) == -1
。y
(z->right
) 执行一次右旋 (Right Rotation)。z
执行一次左旋 (Left Rotation)。
“之” 字路径上的孙子节点 (x
) 成为新根。t
处应执行哪种旋转,你需要检查它的平衡因子 b(t)
以及不平衡方向上的子节点的平衡因子 (如果 b(t) == -2
则检查 t->left
,如果 b(t) == 2
则检查 t->right
)。这个子节点的平衡因子会告诉你这是直线型的 “stick” (LL/RR) 还是 “之” 字形的 “zig-zag” (LR/RL)。t
和键 x
:
t
是 NULL,创建一个高度为 0 的新节点。如果 x < t->key
,向左递归;如果 x > t->key
,向右递归。b(t) = height(t->right) - height(t->left)
。如有必要,执行旋转 (Rotate, if necessary): 如果 $ | b(t) |
|
t
的高度: t->height = 1 + max(height(t->left), height(t->right))
。Implementation
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
#include <algorithm> // for std::max
#include <iostream> // for potential debugging output
// --- 1. 节点结构 (Node Structure) ---
template <typename T>
struct TreeNode {
T key; // 节点存储的键值 (Key)
int height; // 以此节点为根的子树的高度 (Height)
TreeNode *left; // 指向左子节点的指针 (Left child pointer)
TreeNode *right; // 指向右子节点的指针 (Right child pointer)
// 构造函数 (Constructor)
TreeNode(T k, TreeNode* l = nullptr, TreeNode* r = nullptr)
: key(k), height(0), left(l), right(r) {} // 新节点高度初始为 0
};
// --- 2. 辅助函数 (Helper Functions) ---
// 获取节点高度 (安全地处理空指针)
// Get the height of a node (handles NULL safely)
template <typename T>
int height(TreeNode<T>* node) {
if (node == nullptr) {
return -1; // 空树的高度定义为 -1 (Height of NULL node is -1)
}
return node->height;
}
// 更新节点高度
// Update the height of a node based on its children's heights
template <typename T>
void updateHeight(TreeNode<T>* node) {
if (node != nullptr) {
node->height = 1 + std::max(height(node->left), height(node->right));
}
}
// 获取平衡因子
// Get the balance factor of a node
template <typename T>
int getBalance(TreeNode<T>* node) {
if (node == nullptr) {
return 0; // 空节点的平衡因子为 0 (Balance factor of NULL node is 0)
}
// Balance factor = height(right subtree) - height(left subtree)
return height(node->right) - height(node->left);
}
// --- 3. 旋转操作 (Rotation Operations) ---
// 右旋 (LL Case) -围绕 z 进行
// Right Rotation (for LL Imbalance) - Pivot is z
template <typename T>
TreeNode<T>* rightRotate(TreeNode<T>* z) {
TreeNode<T>* y = z->left; // y 是 z 的左孩子
TreeNode<T>* T3 = y->right; // T3 是 y 的右子树
// 执行旋转
y->right = z;
z->left = T3;
// 更新高度 (必须先更新 z, 再更新 y)
// Update heights (must update z first, then y)
updateHeight(z);
updateHeight(y);
// 返回旋转后子树的新根 (y)
// Return the new root of the subtree (y)
return y;
}
// 左旋 (RR Case) - 围绕 z 进行
// Left Rotation (for RR Imbalance) - Pivot is z
template <typename T>
TreeNode<T>* leftRotate(TreeNode<T>* z) {
TreeNode<T>* y = z->right; // y 是 z 的右孩子
TreeNode<T>* T2 = y->left; // T2 是 y 的左子树
// 执行旋转
y->left = z;
z->right = T2;
// 更新高度 (必须先更新 z, 再更新 y)
// Update heights (must update z first, then y)
updateHeight(z);
updateHeight(y);
// 返回旋转后子树的新根 (y)
// Return the new root of the subtree (y)
return y;
}
// 左右旋 (LR Case) - 先对 y 左旋,再对 z 右旋
// Left-Right Rotation (for LR Imbalance) - Left rotate y, then right rotate z
template <typename T>
TreeNode<T>* leftRightRotate(TreeNode<T>* z) {
// 对 z 的左孩子 y 进行左旋
// Left rotate the left child y
z->left = leftRotate(z->left);
// 对 z 进行右旋,并返回新根
// Right rotate z and return the new root
return rightRotate(z);
}
// 右左旋 (RL Case) - 先对 y 右旋,再对 z 左旋
// Right-Left Rotation (for RL Imbalance) - Right rotate y, then left rotate z
template <typename T>
TreeNode<T>* rightLeftRotate(TreeNode<T>* z) {
// 对 z 的右孩子 y 进行右旋
// Right rotate the right child y
z->right = rightRotate(z->right);
// 对 z 进行左旋,并返回新根
// Left rotate z and return the new root
return leftRotate(z);
}
// --- 4. 插入操作 (Insertion Operation) ---
// (基于幻灯片中的 insert_ 递归辅助函数模板)
// (Based on the recursive helper function template `insert_` from the slides)
template <class T>
class AVLTree { // 假设这些函数在一个 AVLTree 类中
public:
TreeNode<T>* root = nullptr;
void insert(const T & x) {
root = insert_(x, root);
}
private:
TreeNode<T>* insert_(const T & x, TreeNode<T>* t) {
// 1. 执行标准 BST 插入 (Perform standard BST insert)
if (t == nullptr) {
return new TreeNode<T>(x); // 创建新节点,高度为 0
}
if (x < t->key) {
t->left = insert_(x, t->left);
} else if (x > t->key) {
t->right = insert_(x, t->right);
} else {
// 不允许重复键 (Duplicate keys not allowed or handle as needed)
return t;
}
// 2. 更新当前节点的高度 (Update height of the current node)
updateHeight(t);
// 3. 获取平衡因子并检查是否失衡 (Get balance factor and check for imbalance)
int balance = getBalance(t);
// 4. 如果失衡,执行相应的旋转 (If unbalanced, perform rotations)
// LL Case (左子树过高,且插入发生在左子树的左侧)
if (balance < -1 && x < t->left->key) { // 或者检查 getBalance(t->left) <= -1 (标准通常用-1)
return rightRotate(t);
}
// RR Case (右子树过高,且插入发生在右子树的右侧)
if (balance > 1 && x > t->right->key) { // 或者检查 getBalance(t->right) >= 1 (标准通常用 1)
return leftRotate(t);
}
// LR Case (左子树过高,但插入发生在左子树的右侧)
if (balance < -1 && x > t->left->key) { // 或者检查 getBalance(t->left) == 1
return leftRightRotate(t);
}
// RL Case (右子树过高,但插入发生在右子树的左侧)
if (balance > 1 && x < t->right->key) { // 或者检查 getBalance(t->right) == -1
return rightLeftRotate(t);
}
// 5. 返回 (可能已旋转或未旋转的) 子树根节点
// Return the (possibly rotated) subtree root
return t;
}
// --- 5. 删除操作 (Deletion Operation - 伪代码/概念) ---
/*
TreeNode<T>* remove_(const T& x, TreeNode<T>* t) {
// 1. 执行标准 BST 删除 (Perform standard BST delete)
if (t == nullptr) { return t; } // 未找到 (Not found)
if (x < t->key) {
t->left = remove_(x, t->left);
} else if (x > t->key) {
t->right = remove_(x, t->right);
} else {
// 找到节点 (Node found)
if (t->left == nullptr || t->right == nullptr) {
// 0 或 1 个子节点 (0 or 1 child)
TreeNode<T>* temp = t->left ? t->left : t->right;
if (temp == nullptr) { // 无子节点 (No child)
temp = t;
t = nullptr;
} else { // 一个子节点 (One child)
*t = *temp; // 拷贝内容 (Copy contents)
}
delete temp; // 删除旧节点或临时节点 (Delete old node or temp)
// 注意: 如果使用拷贝内容的方式,需要确保正确处理指针和内存
} else {
// 2 个子节点 (2 children)
// 找到中序后继 (或前驱) (Find inorder successor (or predecessor))
TreeNode<T>* temp = findMin(t->right); // 假设有 findMin 函数
// 拷贝后继的值到当前节点 (Copy successor's key)
t->key = temp->key;
// 从右子树中删除该后继 (Delete the successor from right subtree)
t->right = remove_(temp->key, t->right);
}
}
// 如果树在删除后变为空 (If tree became empty after deletion)
if (t == nullptr) {
return t;
}
// (关键步骤) 从删除点向上回溯,更新高度并检查平衡
// (Key Step) Backtrack from deletion point, update height & check balance
// 2. 更新高度 (Update height)
updateHeight(t);
// 3. 获取平衡因子 (Get balance factor)
int balance = getBalance(t);
// 4. 检查并执行旋转 (Check and perform rotations)
// (需要检查左右子节点的平衡因子来确定是单旋还是双旋)
// LL Case (可能由右侧删除引起左侧过高)
if (balance < -1 && getBalance(t->left) <= 0) { // 可能为 -1 或 0
return rightRotate(t);
}
// LR Case
if (balance < -1 && getBalance(t->left) > 0) { // 必须为 1
return leftRightRotate(t);
}
// RR Case (可能由左侧删除引起右侧过高)
if (balance > 1 && getBalance(t->right) >= 0) { // 可能为 1 或 0
return leftRotate(t);
}
// RL Case
if (balance > 1 && getBalance(t->right) < 0) { // 必须为 -1
return rightLeftRotate(t);
}
// 5. 返回节点 (Return node)
return t;
}
// (需要 findMin 辅助函数)
TreeNode<T>* findMin(TreeNode<T>* node) {
TreeNode<T>* current = node;
while (current->left != nullptr) {
current = current->left;
}
return current;
}
*/
}; // end of AVLTree class definition
注意AVL的删除可能存在多次旋转,在每一次向下调用结束以后都要注意Retain Balance
总结要点:
TreeNode
结构中包含 height
成员。height()
用于安全获取高度,updateHeight()
用于更新高度,getBalance()
用于计算平衡因子。rightRotate
, leftRotate
, leftRightRotate
, rightLeftRotate
) 是核心,它们在 $O(1)$ 时间内完成指针调整并更新受影响节点的高度。特性 (Feature) | 红黑树 (Red-Black Tree) | AVL 树 (AVL Tree) |
---|---|---|
最大高度 (Max Height) | $2 \cdot lg(n)$ | $1.44 \cdot lg(n)$ |
插入旋转 (Insert Rotations) | 常数次 (Constant) | 最多 1 次 (单旋或双旋) (Max 1) |
删除旋转 (Remove Rotations) | 常数次 (Constant) | $O(log n)$ |
查找旋转 (Find Rotations) | 0 | 0 |
平衡性 (Balance) | 弱平衡 (Weaker) | 强平衡 (Stronger) |
适用性 (Applicability) | 更适用于频繁的插入和删除操作 (Frequent Insert/Delete) | 更适用于查找操作较多的场景 (Frequent Search) |
要点:
TreeMap
和 C++ 的 std::map
都使用红黑树实现。操作 (Operation) | 未排序数组 (Unsorted Array) | 排序数组 (Sorted Array) | 未排序链表 (Unsorted List) | 排序链表 (Sorted List) | 一般二叉树 (Binary Tree) | BST (二叉搜索树) | AVL 树 (AVL Tree) |
---|---|---|---|---|---|---|---|
查找 (Find) | $O(n)$ | $O(\log n)$ | $O(n)$ | $O(n)$ | $O(n)$ | $O(h)$ | $O(\log n)$ |
插入 (Insert) | $O(1)$ (摊销 Amortized) | $O(n)$ | $O(1)$ (头插 Head Insert) | $O(n)$ | $O(n)$ | $O(h)$ | $O(\log n)$ |
删除 (Remove) | $O(n)$ (查找+移动/交换) | $O(n)$ (查找+移动) | $O(n)$ (查找+删除) | $O(n)$ (查找+删除) | $O(n)$ | $O(h)$ | $O(\log n)$ |
遍历 (Traverse) | $O(n)$ | $O(n)$ | $O(n)$ | $O(n)$ | $O(n)$ | $O(n)$ | $O(n)$ |
复杂度解释:
find + O(1)
,意味着找到后与末尾交换。总时间是 $O(n)$。find + O(n)
,即 $O(\log n) + O(n) = O(n)$。find + O(n)
,即 $O(\log n) + O(n) = O(n)$。find + O(1)
。find + O(1)
。find + O(1)
。find + O(1)
实际上意味着 $O(h)$。结论: 对于搜索密集型应用,AVL 树提供了有保证的对数时间 ($O(\log n)$) 查找、插入和删除性能,相比普通 BST (最坏情况 $O(n)$) 和线性结构具有显著优势。
这部分内容将平衡 BST (如 AVL 树) 的概念应用于解决查找特定范围内所有数据点的问题。
问题: 给定一组一维 (1D) 点 $p = {p_1, p_2, …, p_n}$,找出所有满足 $low \le p_i \le high$ 的点 $p_i$。例如:找出在区间 $[11, 42]$ 内的点。
方法一:排序数组 (Sorted Array)
方法二:平衡 BST (例如 AVL 树)
findInRange(node, low, high, results)
:
node
为空,返回。node.value >= low
:
findInRange(node.left, low, high, results)
low <= node.value <= high
:
将 node.value
添加到 results
。node.value <= high
:
findInRange(node.right, low, high, results)
33 >= 11
,向左走。33 <= 42
,最终会向右走。6 < 11
,只需检查右边。11 >= 11
,检查左边 (空)。11 >= 11
且 11 <= 42
,添加 11。11 <= 42
,检查右边 (叶子 33)。33 >= 11
,检查左边 (空)。33 >= 11
且 33 <= 42
,添加 33。33 <= 42
,检查右边 (空)。33 >= 11
且 33 <= 42
,添加 33 (取决于实现细节,通常在遍历中添加叶子节点或值)。33 <= 42
,向右走。44 >= 11
,向左走。44 > 42
,所以 不 向右走。41 >= 11
,检查左边 (空)。41 >= 11
且 41 <= 42
,添加 41。41 <= 42
,检查右边 (叶子 44)。44 >= 11
,检查左边 (空)。44 > 42
,不添加。44 > 42
,不检查右边。技术比喻: 在 BST 中进行一维范围搜索,就像在图书馆的特定区域找书。你使用目录 (树结构) 快速定位到大致的起点和终点 ($O(\log n)$),然后在书架的那个特定区域走动,收集所有符合条件的书 ($O(k)$)。
这部分将思想扩展到二维 (2D) 数据。
问题:
挑战: 标准的 BST 只能按一个维度排序。我们如何有效地在二维 (或更高维度) 中搜索?
解决方案思路:kD 树 (k-dimensional Tree)
技术比喻: 构建 kD 树就像递归地切蛋糕。首先,你沿着中间垂直切一刀 (x 轴分割)。然后,你把得到的两半分别水平切开 (y 轴分割)。接着,你把得到的四个象限再分别垂直切开,以此类推,将空间分割成越来越小的矩形区域。
核心要点: kD 树是一种将二叉搜索树概念应用于处理多维数据 (multi-dimensional data) 的方法,它通过在不同树层级交替使用不同维度来进行空间分割。这使得在多维空间中进行高效的范围搜索和最近邻搜索成为可能 (尽管实际的搜索算法比一维情况更复杂,可能在后续课程中讲解)。