|
|
|
@ -177,6 +177,12 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title=""
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
「节点高度」是指从该节点到最远叶节点的距离,即所经过的“边”的数量。需要特别注意的是,叶节点的高度为 0 ,而空节点的高度为 -1 。我们将创建两个工具函数,分别用于获取和更新节点的高度。
|
|
|
|
|
|
|
|
|
|
=== "Java"
|
|
|
|
@ -360,6 +366,21 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 获取节点高度 */
|
|
|
|
|
int height(TreeNode? node) {
|
|
|
|
|
return node == null ? -1 : node.height;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 更新节点高度 */
|
|
|
|
|
void updateHeight(TreeNode? node) {
|
|
|
|
|
// 节点高度等于最高子树高度 + 1
|
|
|
|
|
node!.height = max(height(node.left), height(node.right)) + 1;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 节点平衡因子
|
|
|
|
|
|
|
|
|
|
节点的「平衡因子 Balance Factor」定义为节点左子树的高度减去右子树的高度,同时规定空节点的平衡因子为 0 。我们同样将获取节点平衡因子的功能封装成函数,方便后续使用。
|
|
|
|
@ -490,6 +511,18 @@ G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorit
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 获取平衡因子 */
|
|
|
|
|
int balanceFactor(TreeNode? node) {
|
|
|
|
|
// 空节点平衡因子为 0
|
|
|
|
|
if (node == null) return 0;
|
|
|
|
|
// 节点平衡因子 = 左子树高度 - 右子树高度
|
|
|
|
|
return height(node.left) - height(node.right);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
!!! note
|
|
|
|
|
|
|
|
|
|
设平衡因子为 $f$ ,则一棵 AVL 树的任意节点的平衡因子皆满足 $-1 \le f \le 1$ 。
|
|
|
|
@ -704,6 +737,24 @@ AVL 树的特点在于「旋转 Rotation」操作,它能够在不影响二叉
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 右旋操作 */
|
|
|
|
|
TreeNode? rightRotate(TreeNode? node) {
|
|
|
|
|
TreeNode? child = node!.left;
|
|
|
|
|
TreeNode? grandChild = child!.right;
|
|
|
|
|
// 以 child 为原点,将 node 向右旋转
|
|
|
|
|
child.right = node;
|
|
|
|
|
node.left = grandChild;
|
|
|
|
|
// 更新节点高度
|
|
|
|
|
updateHeight(node);
|
|
|
|
|
updateHeight(child);
|
|
|
|
|
// 返回旋转后子树的根节点
|
|
|
|
|
return child;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 左旋
|
|
|
|
|
|
|
|
|
|
相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行「左旋」操作。
|
|
|
|
@ -900,6 +951,24 @@ AVL 树的特点在于「旋转 Rotation」操作,它能够在不影响二叉
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 左旋操作 */
|
|
|
|
|
TreeNode? leftRotate(TreeNode? node) {
|
|
|
|
|
TreeNode? child = node!.right;
|
|
|
|
|
TreeNode? grandChild = child!.left;
|
|
|
|
|
// 以 child 为原点,将 node 向左旋转
|
|
|
|
|
child.left = node;
|
|
|
|
|
node.right = grandChild;
|
|
|
|
|
// 更新节点高度
|
|
|
|
|
updateHeight(node);
|
|
|
|
|
updateHeight(child);
|
|
|
|
|
// 返回旋转后子树的根节点
|
|
|
|
|
return child;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 先左旋后右旋
|
|
|
|
|
|
|
|
|
|
对于下图中的失衡节点 3,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先左旋后右旋,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。
|
|
|
|
@ -1275,6 +1344,40 @@ AVL 树的特点在于「旋转 Rotation」操作,它能够在不影响二叉
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 执行旋转操作,使该子树重新恢复平衡 */
|
|
|
|
|
TreeNode? rotate(TreeNode? node) {
|
|
|
|
|
// 获取节点 node 的平衡因子
|
|
|
|
|
int factor = balanceFactor(node);
|
|
|
|
|
// 左偏树
|
|
|
|
|
if (factor > 1) {
|
|
|
|
|
if (balanceFactor(node!.left) >= 0) {
|
|
|
|
|
// 右旋
|
|
|
|
|
return rightRotate(node);
|
|
|
|
|
} else {
|
|
|
|
|
// 先左旋后右旋
|
|
|
|
|
node.left = leftRotate(node.left);
|
|
|
|
|
return rightRotate(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 右偏树
|
|
|
|
|
if (factor < -1) {
|
|
|
|
|
if (balanceFactor(node!.right) <= 0) {
|
|
|
|
|
// 左旋
|
|
|
|
|
return leftRotate(node);
|
|
|
|
|
} else {
|
|
|
|
|
// 先右旋后左旋
|
|
|
|
|
node.right = rightRotate(node.right);
|
|
|
|
|
return leftRotate(node);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 平衡树,无需旋转,直接返回
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## 7.5.3. AVL 树常用操作
|
|
|
|
|
|
|
|
|
|
### 插入节点
|
|
|
|
@ -1562,6 +1665,32 @@ AVL 树的特点在于「旋转 Rotation」操作,它能够在不影响二叉
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 插入节点 */
|
|
|
|
|
void insert(int val) {
|
|
|
|
|
root = insertHelper(root, val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 递归插入节点(辅助方法) */
|
|
|
|
|
TreeNode? insertHelper(TreeNode? node, int val) {
|
|
|
|
|
if (node == null) return TreeNode(val);
|
|
|
|
|
/* 1. 查找插入位置,并插入节点 */
|
|
|
|
|
if (val < node.val)
|
|
|
|
|
node.left = insertHelper(node.left, val);
|
|
|
|
|
else if (val > node.val)
|
|
|
|
|
node.right = insertHelper(node.right, val);
|
|
|
|
|
else
|
|
|
|
|
return node; // 重复节点不插入,直接返回
|
|
|
|
|
updateHeight(node); // 更新节点高度
|
|
|
|
|
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
|
|
|
|
|
node = rotate(node);
|
|
|
|
|
// 返回子树的根节点
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 删除节点
|
|
|
|
|
|
|
|
|
|
类似地,在二叉搜索树的删除节点方法的基础上,需要从底至顶地执行旋转操作,使所有失衡节点恢复平衡。
|
|
|
|
@ -2021,6 +2150,49 @@ AVL 树的特点在于「旋转 Rotation」操作,它能够在不影响二叉
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
=== "Dart"
|
|
|
|
|
|
|
|
|
|
```dart title="avl_tree.dart"
|
|
|
|
|
/* 删除节点 */
|
|
|
|
|
void remove(int val) {
|
|
|
|
|
root = removeHelper(root, val);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 递归删除节点(辅助方法) */
|
|
|
|
|
TreeNode? removeHelper(TreeNode? node, int val) {
|
|
|
|
|
if (node == null) return null;
|
|
|
|
|
/* 1. 查找节点,并删除之 */
|
|
|
|
|
if (val < node.val)
|
|
|
|
|
node.left = removeHelper(node.left, val);
|
|
|
|
|
else if (val > node.val)
|
|
|
|
|
node.right = removeHelper(node.right, val);
|
|
|
|
|
else {
|
|
|
|
|
if (node.left == null || node.right == null) {
|
|
|
|
|
TreeNode? child = node.left ?? node.right;
|
|
|
|
|
// 子节点数量 = 0 ,直接删除 node 并返回
|
|
|
|
|
if (child == null)
|
|
|
|
|
return null;
|
|
|
|
|
// 子节点数量 = 1 ,直接删除 node
|
|
|
|
|
else
|
|
|
|
|
node = child;
|
|
|
|
|
} else {
|
|
|
|
|
// 子节点数量 = 2 ,则将中序遍历的下个节点删除,并用该节点替换当前节点
|
|
|
|
|
TreeNode? temp = node.right;
|
|
|
|
|
while (temp!.left != null) {
|
|
|
|
|
temp = temp.left;
|
|
|
|
|
}
|
|
|
|
|
node.right = removeHelper(node.right, temp.val);
|
|
|
|
|
node.val = temp.val;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
updateHeight(node); // 更新节点高度
|
|
|
|
|
/* 2. 执行旋转操作,使该子树重新恢复平衡 */
|
|
|
|
|
node = rotate(node);
|
|
|
|
|
// 返回子树的根节点
|
|
|
|
|
return node;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 查找节点
|
|
|
|
|
|
|
|
|
|
AVL 树的节点查找操作与二叉搜索树一致,在此不再赘述。
|
|
|
|
|