This commit is contained in:
krahets
2023-02-26 19:53:26 +08:00
parent cf26cd551a
commit 9cd475d8c2
29 changed files with 180 additions and 0 deletions
+16
View File
@@ -10,10 +10,14 @@ comments: true
![AVL 树在删除结点后发生退化](avl_tree.assets/avltree_degradation_from_removing_node.png)
<p align="center"> Fig. AVL 树在删除结点后发生退化 </p>
再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。
![AVL 树在插入结点后发生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png)
<p align="center"> Fig. AVL 树在插入结点后发生退化 </p>
G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。
换言之,在频繁增删查改的使用场景中,AVL 树可始终保持很高的数据增删查改效率,具有很好的应用价值。
@@ -470,6 +474,8 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
![有 grandChild 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png)
<p align="center"> Fig. 有 grandChild 的右旋操作 </p>
“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。
=== "Java"
@@ -646,10 +652,14 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
![左旋操作](avl_tree.assets/avltree_left_rotate.png)
<p align="center"> Fig. 左旋操作 </p>
同理,若结点 `child` 本身有左子结点(记为 `grandChild` ),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。
![有 grandChild 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png)
<p align="center"> Fig. 有 grandChild 的左旋操作 </p>
观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` ,即可得到「左旋」代码。
=== "Java"
@@ -826,18 +836,24 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
![先左旋后右旋](avl_tree.assets/avltree_left_right_rotate.png)
<p align="center"> Fig. 先左旋后右旋 </p>
### Case 4 - 先右后左
同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 `child` 执行「右旋」,然后对 `node` 执行「左旋」。
![先右旋后左旋](avl_tree.assets/avltree_right_left_rotate.png)
<p align="center"> Fig. 先右旋后左旋 </p>
### 旋转的选择
下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 **右旋、左旋、先右后左、先左后右** 的旋转操作。
![AVL 树的四种旋转情况](avl_tree.assets/avltree_rotation_cases.png)
<p align="center"> Fig. AVL 树的四种旋转情况 </p>
具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。
<div class="center-table" markdown>
+12
View File
@@ -11,6 +11,8 @@ comments: true
![二叉搜索树](binary_search_tree.assets/binary_search_tree.png)
<p align="center"> Fig. 二叉搜索树 </p>
## 7.3.1. &nbsp; 二叉搜索树的操作
### 查找结点
@@ -249,6 +251,8 @@ comments: true
![在二叉搜索树中插入结点](binary_search_tree.assets/bst_insert.png)
<p align="center"> Fig. 在二叉搜索树中插入结点 </p>
=== "Java"
```java title="binary_search_tree.java"
@@ -551,10 +555,14 @@ comments: true
![在二叉搜索树中删除结点(度为 0](binary_search_tree.assets/bst_remove_case1.png)
<p align="center"> Fig. 在二叉搜索树中删除结点(度为 0) </p>
**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。
![在二叉搜索树中删除结点(度为 1](binary_search_tree.assets/bst_remove_case2.png)
<p align="center"> Fig. 在二叉搜索树中删除结点(度为 1) </p>
**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步:
1. 找到待删除结点在 **中序遍历序列** 中的下一个结点,记为 `nex`
@@ -1137,6 +1145,8 @@ comments: true
![二叉搜索树的中序遍历序列](binary_search_tree.assets/bst_inorder_traversal.png)
<p align="center"> Fig. 二叉搜索树的中序遍历序列 </p>
## 7.3.2. &nbsp; 二叉搜索树的效率
假设给定 $n$ 个数字,最常用的存储方式是「数组」,那么对于这串乱序的数字,常见操作的效率为:
@@ -1178,6 +1188,8 @@ comments: true
![二叉搜索树的平衡与退化](binary_search_tree.assets/bst_degradation.png)
<p align="center"> Fig. 二叉搜索树的平衡与退化 </p>
## 7.3.4. &nbsp; 二叉搜索树常见应用
- 系统中的多级索引,高效查找、插入、删除操作。
+24
View File
@@ -133,6 +133,8 @@ comments: true
![父结点、子结点、子树](binary_tree.assets/binary_tree_definition.png)
<p align="center"> Fig. 父结点、子结点、子树 </p>
## 7.1.1. &nbsp; 二叉树常见术语
二叉树的术语较多,建议尽量理解并记住。后续可能遗忘,可以在需要使用时回来查看确认。
@@ -148,6 +150,8 @@ comments: true
![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png)
<p align="center"> Fig. 二叉树的常用术语 </p>
!!! tip "高度与深度的定义"
值得注意,我们通常将「高度」和「深度」定义为“走过边的数量”,而有些题目或教材会将其定义为“走过结点的数量”,此时高度或深度都需要 + 1 。
@@ -306,6 +310,8 @@ comments: true
![在二叉树中插入与删除结点](binary_tree.assets/binary_tree_add_remove.png)
<p align="center"> Fig. 在二叉树中插入与删除结点 </p>
=== "Java"
```java title="binary_tree.java"
@@ -428,6 +434,8 @@ comments: true
![完美二叉树](binary_tree.assets/perfect_binary_tree.png)
<p align="center"> Fig. 完美二叉树 </p>
### 完全二叉树
「完全二叉树 Complete Binary Tree」只有最底层的结点未被填满,且最底层结点尽量靠左填充。
@@ -436,18 +444,24 @@ comments: true
![完全二叉树](binary_tree.assets/complete_binary_tree.png)
<p align="center"> Fig. 完全二叉树 </p>
### 完满二叉树
「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。
![完满二叉树](binary_tree.assets/full_binary_tree.png)
<p align="center"> Fig. 完满二叉树 </p>
### 平衡二叉树
「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。
![平衡二叉树](binary_tree.assets/balanced_binary_tree.png)
<p align="center"> Fig. 平衡二叉树 </p>
## 7.1.4. &nbsp; 二叉树的退化
当二叉树的每层的结点都被填满时,达到「完美二叉树」;而当所有结点都偏向一边时,二叉树退化为「链表」。
@@ -457,6 +471,8 @@ comments: true
![二叉树的最佳与最二叉树的最佳和最差结构差情况](binary_tree.assets/binary_tree_corner_cases.png)
<p align="center"> Fig. 二叉树的最佳与最二叉树的最佳和最差结构差情况 </p>
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
<div class="center-table" markdown>
@@ -480,10 +496,14 @@ comments: true
![完美二叉树的数组表示](binary_tree.assets/array_representation_mapping.png)
<p align="center"> Fig. 完美二叉树的数组表示 </p>
然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 `null` ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。
![给定数组对应多种二叉树可能性](binary_tree.assets/array_representation_without_empty.png)
<p align="center"> Fig. 给定数组对应多种二叉树可能性 </p>
为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,**即在序列中使用特殊符号来显式地表示“空位”**。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。
=== "Java"
@@ -563,8 +583,12 @@ comments: true
![任意类型二叉树的数组表示](binary_tree.assets/array_representation_with_empty.png)
<p align="center"> Fig. 任意类型二叉树的数组表示 </p>
回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。
![完全二叉树的数组表示](binary_tree.assets/array_representation_complete_binary_tree.png)
<p align="center"> Fig. 完全二叉树的数组表示 </p>
数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。
+4
View File
@@ -16,6 +16,8 @@ comments: true
![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png)
<p align="center"> Fig. 二叉树的层序遍历 </p>
### 算法实现
广度优先遍历一般借助「队列」来实现。队列的规则是“先进先出”,广度优先遍历的规则是 ”一层层平推“ ,两者背后的思想是一致的。
@@ -256,6 +258,8 @@ comments: true
![二叉搜索树的前、中、后序遍历](binary_tree_traversal.assets/binary_tree_dfs.png)
<p align="center"> Fig. 二叉搜索树的前、中、后序遍历 </p>
<div class="center-table" markdown>
| 位置 | 含义 | 此处访问结点时对应 |