mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-25 01:26:29 +08:00
Update the captions of all the figures.
This commit is contained in:
parent
85d04b30fb
commit
9e99ac06ce
31 changed files with 99 additions and 175 deletions
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
「数组 Array」是一种将 **相同类型元素** 存储在 **连续内存空间** 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。
|
「数组 Array」是一种将 **相同类型元素** 存储在 **连续内存空间** 的数据结构,将元素在数组中的位置称为元素的「索引 Index」。
|
||||||
|
|
||||||
![array_definition](array.assets/array_definition.png)
|
![数组定义与存储方式](array.assets/array_definition.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 数组定义与存储方式 </p>
|
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
|
|
||||||
|
@ -102,9 +100,7 @@
|
||||||
|
|
||||||
**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。
|
**在数组中访问元素非常高效**。这是因为在数组中,计算元素的内存地址非常容易。给定数组首个元素的地址、和一个元素的索引,利用以下公式可以直接计算得到该元素的内存地址,从而直接访问此元素。
|
||||||
|
|
||||||
![array_memory_location_calculation](array.assets/array_memory_location_calculation.png)
|
![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 数组元素的内存地址计算 </p>
|
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
# 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引
|
# 元素内存地址 = 数组内存地址 + 元素长度 * 元素索引
|
||||||
|
@ -241,7 +237,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||||
|
|
||||||
**数组中插入或删除元素效率低下**。如果我们想要在数组中间插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。
|
**数组中插入或删除元素效率低下**。如果我们想要在数组中间插入一个元素,由于数组元素在内存中是“紧挨着的”,它们之间没有空间再放任何数据。因此,我们不得不将此索引之后的所有元素都向后移动一位,然后再把元素赋值给该索引。
|
||||||
|
|
||||||
![array_insert_element](array.assets/array_insert_element.png)
|
![数组插入元素](array.assets/array_insert_element.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -299,7 +295,7 @@ elementAddr = firtstElementAddr + elementLength * elementIndex
|
||||||
|
|
||||||
删除元素也是类似,如果我们想要删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。值得注意的是,删除元素后,原先末尾的元素变得“无意义”了,我们无需特意去修改它。
|
删除元素也是类似,如果我们想要删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。值得注意的是,删除元素后,原先末尾的元素变得“无意义”了,我们无需特意去修改它。
|
||||||
|
|
||||||
![array_remove_element](array.assets/array_remove_element.png)
|
![数组删除元素](array.assets/array_remove_element.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
|
|
@ -8,9 +8,7 @@
|
||||||
|
|
||||||
链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。
|
链表的「结点 Node」包含两项数据,一是结点「值 Value」,二是指向下一结点的「指针 Pointer」(或称「引用 Reference」)。
|
||||||
|
|
||||||
![linkedlist_definition](linked_list.assets/linkedlist_definition.png)
|
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 链表定义与存储方式 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -314,7 +312,7 @@
|
||||||
|
|
||||||
**在链表中,插入与删除结点的操作效率高**。比如,如果我们想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。
|
**在链表中,插入与删除结点的操作效率高**。比如,如果我们想在链表中间的两个结点 `A` , `B` 之间插入一个新结点 `P` ,我们只需要改变两个结点指针即可,时间复杂度为 $O(1)$ ,相比数组的插入操作高效很多。
|
||||||
|
|
||||||
![linkedlist_insert_node](linked_list.assets/linkedlist_insert_node.png)
|
![链表插入结点](linked_list.assets/linkedlist_insert_node.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -378,7 +376,7 @@
|
||||||
|
|
||||||
在链表中删除结点也很方便,只需要改变一个结点指针即可。如下图所示,虽然在完成删除后结点 `P` 仍然指向 `n2` ,但实际上 `P` 已经不属于此链表了,因为遍历此链表是无法访问到 `P` 的。
|
在链表中删除结点也很方便,只需要改变一个结点指针即可。如下图所示,虽然在完成删除后结点 `P` 仍然指向 `n2` ,但实际上 `P` 已经不属于此链表了,因为遍历此链表是无法访问到 `P` 的。
|
||||||
|
|
||||||
![linkedlist_remove_node](linked_list.assets/linkedlist_remove_node.png)
|
![链表删除结点](linked_list.assets/linkedlist_remove_node.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -720,6 +718,6 @@
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
![linkedlist_common_types](linked_list.assets/linkedlist_common_types.png)
|
![常见链表种类](linked_list.assets/linkedlist_common_types.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 常见链表类型 </p>
|
<p align="center"> Fig. 常见链表类型 </p>
|
||||||
|
|
|
@ -20,9 +20,7 @@
|
||||||
- 「栈帧空间」用于保存调用函数的上下文数据。系统每次调用函数都会在栈的顶部创建一个栈帧,函数返回时,栈帧空间会被释放。
|
- 「栈帧空间」用于保存调用函数的上下文数据。系统每次调用函数都会在栈的顶部创建一个栈帧,函数返回时,栈帧空间会被释放。
|
||||||
- 「指令空间」用于保存编译后的程序指令,**在实际统计中一般忽略不计**。
|
- 「指令空间」用于保存编译后的程序指令,**在实际统计中一般忽略不计**。
|
||||||
|
|
||||||
![space_types](space_complexity.assets/space_types.png)
|
![算法使用的相关空间](space_complexity.assets/space_types.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 算法使用的相关空间 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -561,9 +559,7 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![space_complexity_common_types](space_complexity.assets/space_complexity_common_types.png)
|
![空间复杂度的常见类型](space_complexity.assets/space_complexity_common_types.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 空间复杂度的常见类型 </p>
|
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
|
@ -761,9 +757,7 @@ $$
|
||||||
[class]{}-[func]{linearRecur}
|
[class]{}-[func]{linearRecur}
|
||||||
```
|
```
|
||||||
|
|
||||||
![space_complexity_recursive_linear](space_complexity.assets/space_complexity_recursive_linear.png)
|
![递归函数产生的线性阶空间复杂度](space_complexity.assets/space_complexity_recursive_linear.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 递归函数产生的线性阶空间复杂度 </p>
|
|
||||||
|
|
||||||
### 平方阶 $O(n^2)$
|
### 平方阶 $O(n^2)$
|
||||||
|
|
||||||
|
@ -891,9 +885,7 @@ $$
|
||||||
[class]{}-[func]{quadraticRecur}
|
[class]{}-[func]{quadraticRecur}
|
||||||
```
|
```
|
||||||
|
|
||||||
![space_complexity_recursive_quadratic](space_complexity.assets/space_complexity_recursive_quadratic.png)
|
![递归函数产生的平方阶空间复杂度](space_complexity.assets/space_complexity_recursive_quadratic.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 递归函数产生的平方阶空间复杂度 </p>
|
|
||||||
|
|
||||||
### 指数阶 $O(2^n)$
|
### 指数阶 $O(2^n)$
|
||||||
|
|
||||||
|
@ -959,9 +951,7 @@ $$
|
||||||
[class]{}-[func]{buildTree}
|
[class]{}-[func]{buildTree}
|
||||||
```
|
```
|
||||||
|
|
||||||
![space_complexity_exponential](space_complexity.assets/space_complexity_exponential.png)
|
![满二叉树产生的指数阶空间复杂度](space_complexity.assets/space_complexity_exponential.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 满二叉树下的指数阶空间复杂度 </p>
|
|
||||||
|
|
||||||
### 对数阶 $O(\log n)$
|
### 对数阶 $O(\log n)$
|
||||||
|
|
||||||
|
|
|
@ -365,9 +365,7 @@ $$
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
![time_complexity_simple_example](time_complexity.assets/time_complexity_simple_example.png)
|
![算法 A, B, C 的时间增长趋势](time_complexity.assets/time_complexity_simple_example.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 算法 A, B, C 的时间增长趋势 </p>
|
|
||||||
|
|
||||||
相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?
|
相比直接统计算法运行时间,时间复杂度分析的做法有什么好处呢?以及有什么不足?
|
||||||
|
|
||||||
|
@ -534,9 +532,7 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得
|
||||||
T(n) = O(f(n))
|
T(n) = O(f(n))
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![asymptotic_upper_bound](time_complexity.assets/asymptotic_upper_bound.png)
|
![函数的渐近上界](time_complexity.assets/asymptotic_upper_bound.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 函数的渐近上界 </p>
|
|
||||||
|
|
||||||
本质上看,计算渐近上界就是在找一个函数 $f(n)$ ,**使得在 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别(仅相差一个常数项 $c$ 的倍数)**。
|
本质上看,计算渐近上界就是在找一个函数 $f(n)$ ,**使得在 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别(仅相差一个常数项 $c$ 的倍数)**。
|
||||||
|
|
||||||
|
@ -774,9 +770,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![time_complexity_common_types](time_complexity.assets/time_complexity_common_types.png)
|
![时间复杂度的常见类型](time_complexity.assets/time_complexity_common_types.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 时间复杂度的常见类型 </p>
|
|
||||||
|
|
||||||
!!! tip
|
!!! tip
|
||||||
|
|
||||||
|
@ -1042,9 +1036,7 @@ $$
|
||||||
[class]{}-[func]{quadratic}
|
[class]{}-[func]{quadratic}
|
||||||
```
|
```
|
||||||
|
|
||||||
![time_complexity_constant_linear_quadratic](time_complexity.assets/time_complexity_constant_linear_quadratic.png)
|
![常数阶、线性阶、平方阶的时间复杂度](time_complexity.assets/time_complexity_constant_linear_quadratic.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 常数阶、线性阶、平方阶的时间复杂度 </p>
|
|
||||||
|
|
||||||
以「冒泡排序」为例,外层循环 $n - 1$ 次,内层循环 $n-1, n-2, \cdots, 2, 1$ 次,平均为 $\frac{n}{2}$ 次,因此时间复杂度为 $O(n^2)$ 。
|
以「冒泡排序」为例,外层循环 $n - 1$ 次,内层循环 $n-1, n-2, \cdots, 2, 1$ 次,平均为 $\frac{n}{2}$ 次,因此时间复杂度为 $O(n^2)$ 。
|
||||||
|
|
||||||
|
@ -1180,9 +1172,7 @@ $$
|
||||||
[class]{}-[func]{exponential}
|
[class]{}-[func]{exponential}
|
||||||
```
|
```
|
||||||
|
|
||||||
![time_complexity_exponential](time_complexity.assets/time_complexity_exponential.png)
|
![指数阶的时间复杂度](time_complexity.assets/time_complexity_exponential.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 指数阶的时间复杂度 </p>
|
|
||||||
|
|
||||||
在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 $n$ 次后停止。
|
在实际算法中,指数阶常出现于递归函数。例如以下代码,不断地一分为二,分裂 $n$ 次后停止。
|
||||||
|
|
||||||
|
@ -1314,9 +1304,7 @@ $$
|
||||||
[class]{}-[func]{logarithmic}
|
[class]{}-[func]{logarithmic}
|
||||||
```
|
```
|
||||||
|
|
||||||
![time_complexity_logarithmic](time_complexity.assets/time_complexity_logarithmic.png)
|
![对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 对数阶的时间复杂度 </p>
|
|
||||||
|
|
||||||
与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 $\log_2 n$ 的递归树。
|
与指数阶类似,对数阶也常出现于递归函数。以下代码形成了一个高度为 $\log_2 n$ 的递归树。
|
||||||
|
|
||||||
|
@ -1446,9 +1434,7 @@ $$
|
||||||
[class]{}-[func]{linearLogRecur}
|
[class]{}-[func]{linearLogRecur}
|
||||||
```
|
```
|
||||||
|
|
||||||
![time_complexity_logarithmic_linear](time_complexity.assets/time_complexity_logarithmic_linear.png)
|
![线性对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic_linear.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 线性对数阶的时间复杂度 </p>
|
|
||||||
|
|
||||||
### 阶乘阶 $O(n!)$
|
### 阶乘阶 $O(n!)$
|
||||||
|
|
||||||
|
@ -1520,9 +1506,7 @@ $$
|
||||||
[class]{}-[func]{factorialRecur}
|
[class]{}-[func]{factorialRecur}
|
||||||
```
|
```
|
||||||
|
|
||||||
![time_complexity_factorial](time_complexity.assets/time_complexity_factorial.png)
|
![阶乘阶的时间复杂度](time_complexity.assets/time_complexity_factorial.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 阶乘阶的时间复杂度 </p>
|
|
||||||
|
|
||||||
## 最差、最佳、平均时间复杂度
|
## 最差、最佳、平均时间复杂度
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,7 @@
|
||||||
- **线性数据结构**:数组、链表、栈、队列、哈希表;
|
- **线性数据结构**:数组、链表、栈、队列、哈希表;
|
||||||
- **非线性数据结构**:树、图、堆、哈希表;
|
- **非线性数据结构**:树、图、堆、哈希表;
|
||||||
|
|
||||||
![classification_logic_structure](classification_of_data_structure.assets/classification_logic_structure.png)
|
![线性与非线性数据结构](classification_of_data_structure.assets/classification_logic_structure.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 线性与非线性数据结构 </p>
|
|
||||||
|
|
||||||
## 物理结构:连续与离散
|
## 物理结构:连续与离散
|
||||||
|
|
||||||
|
@ -23,9 +21,7 @@
|
||||||
|
|
||||||
**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。
|
**「物理结构」反映了数据在计算机内存中的存储方式**。从本质上看,分别是 **数组的连续空间存储** 和 **链表的离散空间存储**。物理结构从底层上决定了数据的访问、更新、增删等操作方法,在时间效率和空间效率方面呈现出此消彼长的特性。
|
||||||
|
|
||||||
![classification_phisical_structure](classification_of_data_structure.assets/classification_phisical_structure.png)
|
![连续空间存储与离散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 连续空间存储与离散空间存储 </p>
|
|
||||||
|
|
||||||
**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。
|
**所有数据结构都是基于数组、或链表、或两者组合实现的**。例如栈和队列,既可以使用数组实现、也可以使用链表实现,而例如哈希表,其实现同时包含了数组和链表。
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,7 @@ $$
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![ieee_754_float](data_and_memory.assets/ieee_754_float.png)
|
![IEEE 754 标准下的 float 表示方式](data_and_memory.assets/ieee_754_float.png)
|
||||||
|
|
||||||
以上图为例,$\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,易得
|
以上图为例,$\mathrm{S} = 0$ , $\mathrm{E} = 124$ ,$\mathrm{N} = 2^{-2} + 2^{-3} = 0.375$ ,易得
|
||||||
|
|
||||||
|
@ -206,8 +206,6 @@ $$
|
||||||
|
|
||||||
**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。
|
**系统通过「内存地址 Memory Location」来访问目标内存位置的数据**。计算机根据特定规则给表格中每个单元格编号,保证每块内存空间都有独立的内存地址。自此,程序便通过这些地址,访问内存中的数据。
|
||||||
|
|
||||||
![computer_memory_location](data_and_memory.assets/computer_memory_location.png)
|
![内存条、内存空间、内存地址](data_and_memory.assets/computer_memory_location.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 内存条、内存空间、内存地址 </p>
|
|
||||||
|
|
||||||
**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。
|
**内存资源是设计数据结构与算法的重要考虑因素**。内存是所有程序的公共资源,当内存被某程序占用时,不能被其它程序同时使用。我们需要根据剩余内存资源的情况来设计算法。例如,若剩余内存空间有限,则要求算法占用的峰值内存不能超过系统剩余内存;若运行的程序很多、缺少大块连续的内存空间,则要求选取的数据结构必须能够存储在离散的内存空间内。
|
||||||
|
|
|
@ -10,7 +10,7 @@ G & = \{ V, E \} \newline
|
||||||
\end{aligned}
|
\end{aligned}
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![linkedlist_tree_graph](graph.assets/linkedlist_tree_graph.png)
|
![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png)
|
||||||
|
|
||||||
那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。**相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂**。
|
那么,图与其他数据结构的关系是什么?如果我们把「顶点」看作结点,把「边」看作连接各个结点的指针,则可将「图」看成一种从「链表」拓展而来的数据结构。**相比线性关系(链表)和分治关系(树),网络关系(图)的自由度更高,也从而更为复杂**。
|
||||||
|
|
||||||
|
@ -21,18 +21,18 @@ $$
|
||||||
- 在无向图中,边表示两顶点之间“双向”的连接关系,例如微信或 QQ 中的“好友关系”;
|
- 在无向图中,边表示两顶点之间“双向”的连接关系,例如微信或 QQ 中的“好友关系”;
|
||||||
- 在有向图中,边是有方向的,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系;
|
- 在有向图中,边是有方向的,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系;
|
||||||
|
|
||||||
![directed_graph](graph.assets/directed_graph.png)
|
![有向图与无向图](graph.assets/directed_graph.png)
|
||||||
|
|
||||||
根据所有顶点是否连通,分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。
|
根据所有顶点是否连通,分为「连通图 Connected Graph」和「非连通图 Disconnected Graph」。
|
||||||
|
|
||||||
- 对于连通图,从某个顶点出发,可以到达其余任意顶点;
|
- 对于连通图,从某个顶点出发,可以到达其余任意顶点;
|
||||||
- 对于非连通图,从某个顶点出发,至少有一个顶点无法到达;
|
- 对于非连通图,从某个顶点出发,至少有一个顶点无法到达;
|
||||||
|
|
||||||
![connected_graph](graph.assets/connected_graph.png)
|
![连通图与非连通图](graph.assets/connected_graph.png)
|
||||||
|
|
||||||
我们可以给边添加“权重”变量,得到「有权图 Weighted Graph」。例如,在王者荣耀等游戏中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以使用有权图来表示。
|
我们可以给边添加“权重”变量,得到「有权图 Weighted Graph」。例如,在王者荣耀等游戏中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以使用有权图来表示。
|
||||||
|
|
||||||
![weighted_graph](graph.assets/weighted_graph.png)
|
![有权图与无权图](graph.assets/weighted_graph.png)
|
||||||
|
|
||||||
## 图常用术语
|
## 图常用术语
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@ $$
|
||||||
|
|
||||||
如下图所示,记邻接矩阵为 $M$ 、顶点列表为 $V$ ,则矩阵元素 $M[i][j] = 1$ 代表着顶点 $V[i]$ 到顶点 $V[j]$ 之间有边,相反地 $M[i][j] = 0$ 代表两顶点之间无边。
|
如下图所示,记邻接矩阵为 $M$ 、顶点列表为 $V$ ,则矩阵元素 $M[i][j] = 1$ 代表着顶点 $V[i]$ 到顶点 $V[j]$ 之间有边,相反地 $M[i][j] = 0$ 代表两顶点之间无边。
|
||||||
|
|
||||||
![adjacency_matrix](graph.assets/adjacency_matrix.png)
|
![图的邻接矩阵表示](graph.assets/adjacency_matrix.png)
|
||||||
|
|
||||||
邻接矩阵具有以下性质:
|
邻接矩阵具有以下性质:
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ $$
|
||||||
|
|
||||||
「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表结点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了所有与该顶点相连的顶点。
|
「邻接表 Adjacency List」使用 $n$ 个链表来表示图,链表结点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了所有与该顶点相连的顶点。
|
||||||
|
|
||||||
![adjacency_list](graph.assets/adjacency_list.png)
|
![图的邻接表表示](graph.assets/adjacency_list.png)
|
||||||
|
|
||||||
邻接表仅存储存在的边,而边的总数往往远小于 $n^2$ ,因此更加节省空间。但是,因为在邻接表中需要通过遍历链表来查找边,所以其时间效率不如邻接矩阵。
|
邻接表仅存储存在的边,而边的总数往往远小于 $n^2$ ,因此更加节省空间。但是,因为在邻接表中需要通过遍历链表来查找边,所以其时间效率不如邻接矩阵。
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
**广度优先遍历优是一种由近及远的遍历方式,从距离最近的顶点开始访问,并一层层向外扩张**。具体地,从某个顶点出发,先遍历该顶点的所有邻接顶点,随后遍历下个顶点的所有邻接顶点,以此类推……
|
**广度优先遍历优是一种由近及远的遍历方式,从距离最近的顶点开始访问,并一层层向外扩张**。具体地,从某个顶点出发,先遍历该顶点的所有邻接顶点,随后遍历下个顶点的所有邻接顶点,以此类推……
|
||||||
|
|
||||||
![graph_bfs](graph_traversal.assets/graph_bfs.png)
|
![图的广度优先遍历](graph_traversal.assets/graph_bfs.png)
|
||||||
|
|
||||||
### 算法实现
|
### 算法实现
|
||||||
|
|
||||||
|
@ -133,7 +133,7 @@ BFS 常借助「队列」来实现。队列具有“先入先出”的性质,
|
||||||
|
|
||||||
**深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。具体地,从某个顶点出发,不断地访问当前结点的某个邻接顶点,直到走到尽头时回溯,再继续走到底 + 回溯,以此类推……直至所有顶点遍历完成时结束。
|
**深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。具体地,从某个顶点出发,不断地访问当前结点的某个邻接顶点,直到走到尽头时回溯,再继续走到底 + 回溯,以此类推……直至所有顶点遍历完成时结束。
|
||||||
|
|
||||||
![graph_dfs](graph_traversal.assets/graph_dfs.png)
|
![图的深度优先遍历](graph_traversal.assets/graph_dfs.png)
|
||||||
|
|
||||||
### 算法实现
|
### 算法实现
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
|
|
||||||
在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。**考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中**。
|
在原始哈希表中,桶内的每个地址只能存储一个元素(即键值对)。**考虑将单个元素转化成一个链表,将所有冲突元素都存储在一个链表中**。
|
||||||
|
|
||||||
![hash_collision_chaining](hash_collision.assets/hash_collision_chaining.png)
|
![链式地址](hash_collision.assets/hash_collision_chaining.png)
|
||||||
|
|
||||||
链式地址下,哈希表操作方法为:
|
链式地址下,哈希表操作方法为:
|
||||||
|
|
||||||
|
@ -50,7 +50,7 @@
|
||||||
1. 找到对应元素,返回 value 即可;
|
1. 找到对应元素,返回 value 即可;
|
||||||
2. 若遇到空位,则说明查找键值对不在哈希表中;
|
2. 若遇到空位,则说明查找键值对不在哈希表中;
|
||||||
|
|
||||||
![hash_collision_linear_probing](hash_collision.assets/hash_collision_linear_probing.png)
|
![线性探测](hash_collision.assets/hash_collision_linear_probing.png)
|
||||||
|
|
||||||
线性探测存在以下缺陷:
|
线性探测存在以下缺陷:
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
|
|
||||||
例如,给定一个包含 $n$ 个学生的数据库,每个学生有“姓名 `name` ”和“学号 `id` ”两项数据,希望实现一个查询功能:**输入一个学号,返回对应的姓名**,则可以使用哈希表实现。
|
例如,给定一个包含 $n$ 个学生的数据库,每个学生有“姓名 `name` ”和“学号 `id` ”两项数据,希望实现一个查询功能:**输入一个学号,返回对应的姓名**,则可以使用哈希表实现。
|
||||||
|
|
||||||
![hash_map](hash_map.assets/hash_map.png)
|
![哈希表的抽象表示](hash_map.assets/hash_map.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 哈希表抽象表示 </p>
|
|
||||||
|
|
||||||
## 哈希表效率
|
## 哈希表效率
|
||||||
|
|
||||||
|
@ -404,9 +402,7 @@ $$
|
||||||
f(x) = x \% 100
|
f(x) = x \% 100
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![hash_function](hash_map.assets/hash_function.png)
|
![简单哈希函数示例](hash_map.assets/hash_function.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 哈希函数 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -498,9 +494,7 @@ $$
|
||||||
|
|
||||||
两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论。
|
两个学号指向了同一个姓名,这明显是不对的,我们将这种现象称为「哈希冲突 Hash Collision」。如何避免哈希冲突的问题将被留在下章讨论。
|
||||||
|
|
||||||
![hash_collision](hash_map.assets/hash_collision.png)
|
![哈希冲突示例](hash_map.assets/hash_collision.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 哈希冲突 </p>
|
|
||||||
|
|
||||||
综上所述,一个优秀的「哈希函数」应该具备以下特性:
|
综上所述,一个优秀的「哈希函数」应该具备以下特性:
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值;
|
- 「大顶堆 Max Heap」,任意结点的值 $\geq$ 其子结点的值;
|
||||||
- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值;
|
- 「小顶堆 Min Heap」,任意结点的值 $\leq$ 其子结点的值;
|
||||||
|
|
||||||
![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png)
|
![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png)
|
||||||
|
|
||||||
## 堆术语与性质
|
## 堆术语与性质
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@
|
||||||
|
|
||||||
具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。
|
具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。
|
||||||
|
|
||||||
![representation_of_heap](heap.assets/representation_of_heap.png)
|
![堆的表示与存储](heap.assets/representation_of_heap.png)
|
||||||
|
|
||||||
我们将索引映射公式封装成函数,以便后续使用。
|
我们将索引映射公式封装成函数,以便后续使用。
|
||||||
|
|
||||||
|
@ -789,7 +789,7 @@ $$
|
||||||
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
|
||||||
$$
|
$$
|
||||||
|
|
||||||
![heapify_operations_count](heap.assets/heapify_operations_count.png)
|
![完美二叉树的各层结点数量](heap.assets/heapify_operations_count.png)
|
||||||
|
|
||||||
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得
|
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得
|
||||||
|
|
||||||
|
|
|
@ -27,9 +27,7 @@
|
||||||
- 算法是发挥数据结构优势的舞台。数据结构仅存储数据信息,结合算法才可解决特定问题。
|
- 算法是发挥数据结构优势的舞台。数据结构仅存储数据信息,结合算法才可解决特定问题。
|
||||||
- 算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。
|
- 算法有对应最优的数据结构。给定算法,一般可基于不同的数据结构实现,而最终执行效率往往相差很大。
|
||||||
|
|
||||||
![relationship_between_data_structure_and_algorithm](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png)
|
![数据结构与算法的关系](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 数据结构与算法的关系 </p>
|
|
||||||
|
|
||||||
如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。
|
如果将「LEGO 乐高」类比到「数据结构与算法」,那么可以得到下表所示的对应关系。
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
本书主要内容分为复杂度分析、数据结构、算法三个部分。
|
本书主要内容分为复杂度分析、数据结构、算法三个部分。
|
||||||
|
|
||||||
![hello_algo_mindmap](about_the_book.assets/hello_algo_mindmap.png)
|
![Hello 算法内容结构](about_the_book.assets/hello_algo_mindmap.png)
|
||||||
|
|
||||||
### 复杂度分析
|
### 复杂度分析
|
||||||
|
|
||||||
|
@ -75,9 +75,9 @@
|
||||||
- **第二阶段,刷算法题**。可以先从热门题单开刷,推荐[剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode Hot 100](https://leetcode.cn/problem-list/2cktkvj/),先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,在重复 3 轮以上后,往往就能牢记于心了。
|
- **第二阶段,刷算法题**。可以先从热门题单开刷,推荐[剑指 Offer](https://leetcode.cn/problem-list/xb9nqhhg/)、[LeetCode Hot 100](https://leetcode.cn/problem-list/2cktkvj/),先积累至少 100 道题量,熟悉大多数的算法问题。刚开始刷题时,“遗忘”是最大的困扰点,但这是很正常的,请不要担心。学习中有一种概念叫“周期性回顾”,同一道题隔段时间做一次,在重复 3 轮以上后,往往就能牢记于心了。
|
||||||
- **第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,相应刷题计划与心得可以在社区中找到,在此不做赘述。
|
- **第三阶段,搭建知识体系**。在学习方面,可以阅读算法专栏文章、解题框架、算法教材,不断地丰富知识体系。在刷题方面,可以开始采用进阶刷题方案,例如按专题分类、一题多解、一解多题等,相应刷题计划与心得可以在社区中找到,在此不做赘述。
|
||||||
|
|
||||||
作为入门教程,**本书主要对应「第一阶段」的学习内容**,致力于使读者更高质量高效地开展第二、三阶段的学习。
|
根据观察,很多同学都是从“第二阶段”开始学习算法的。而作为入门教程,**本书内容主要对应“第一阶段”**,致力于帮助读者更高效地开展第二、三阶段的学习。
|
||||||
|
|
||||||
![learning_route](suggestions.assets/learning_route.png)
|
![算法学习路线](suggestions.assets/learning_route.png)
|
||||||
|
|
||||||
## 本书特点
|
## 本书特点
|
||||||
|
|
||||||
|
@ -111,6 +111,6 @@
|
||||||
|
|
||||||
本书鼓励“手脑并用”的学习方式,在这点上受到了《动手学深度学习》很大影响,也在此向各位同学强烈推荐这本著作,包括[中文版](https://github.com/d2l-ai/d2l-zh)、[英文版](https://github.com/d2l-ai/d2l-en)、[李沐老师 bilibili 主页](https://space.bilibili.com/1567748478)。
|
本书鼓励“手脑并用”的学习方式,在这点上受到了《动手学深度学习》很大影响,也在此向各位同学强烈推荐这本著作,包括[中文版](https://github.com/d2l-ai/d2l-zh)、[英文版](https://github.com/d2l-ai/d2l-en)、[李沐老师 bilibili 主页](https://space.bilibili.com/1567748478)。
|
||||||
|
|
||||||
在写作过程中,我阅读了许多与数据结构与算法的书籍与教材,这些著作为本书作出了很好的榜样,保证了本书内容的正确性与质量,感谢前辈们的精彩创作!
|
在写作过程中,我阅读了许多数据结构与算法的教材与文章,这些著作为本书作出了很好的榜样,保证了本书内容的正确性与质量,感谢前辈们的精彩创作!
|
||||||
|
|
||||||
感谢父母,你们一贯的支持与鼓励给了我自由度来做这些有趣的事。
|
感谢父母,你们一贯的支持与鼓励给了我自由度来做这些有趣的事。
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
2. 修改 Markdown 源文件内容,并检查内容正确性,尽量保持排版格式统一;
|
2. 修改 Markdown 源文件内容,并检查内容正确性,尽量保持排版格式统一;
|
||||||
3. 在页面底部填写更改说明,然后单击“Propose file change”按钮;页面跳转后,点击“Create pull request”按钮发起拉取请求即可。
|
3. 在页面底部填写更改说明,然后单击“Propose file change”按钮;页面跳转后,点击“Create pull request”按钮发起拉取请求即可。
|
||||||
|
|
||||||
![edit_markdown](contribution.assets/edit_markdown.png)
|
![页面编辑按键](contribution.assets/edit_markdown.png)
|
||||||
|
|
||||||
图片无法直接修改,需要通过新建 [Issue](https://github.com/krahets/hello-algo/issues) 或评论留言来描述图片问题,我会第一时间重新画图并替换图片。
|
图片无法直接修改,需要通过新建 [Issue](https://github.com/krahets/hello-algo/issues) 或评论留言来描述图片问题,我会第一时间重新画图并替换图片。
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
# 编程环境安装
|
# 编程环境安装
|
||||||
|
|
||||||
(TODO 视频教程)
|
|
||||||
|
|
||||||
## 安装 VSCode
|
## 安装 VSCode
|
||||||
|
|
||||||
本书推荐使用开源轻量的 VSCode 作为本地 IDE ,下载并安装 [VSCode](https://code.visualstudio.com/) 。
|
本书推荐使用开源轻量的 VSCode 作为本地 IDE ,下载并安装 [VSCode](https://code.visualstudio.com/) 。
|
||||||
|
|
|
@ -6,11 +6,11 @@
|
||||||
|
|
||||||
标题后标注 `*` 的是选读章节,内容相对较难。如果你的时间有限,建议可以先跳过。
|
标题后标注 `*` 的是选读章节,内容相对较难。如果你的时间有限,建议可以先跳过。
|
||||||
|
|
||||||
文章中的重要名词会用 `「」` 括号标注,例如 `「数组 Array」` 。建议记住这些名词,包括英文翻译,以便后续阅读文献时使用。
|
文章中的重要名词会用 `「括号」` 标注,例如 `「数组 Array」` 。建议记住这些名词,包括英文翻译,以便后续阅读文献时使用。
|
||||||
|
|
||||||
重点内容、总起句、总结句会被 **加粗** ,此类文字值得特别关注。
|
重点内容、总起句、总结句会被 **加粗** ,此类文字值得特别关注。
|
||||||
|
|
||||||
专有名词和有特指含义的词句会使用 `“ ”` 双引号标注,以避免歧义。
|
专有名词和有特指含义的词句会使用 `“双引号”` 标注,以避免歧义。
|
||||||
|
|
||||||
本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。
|
本书部分放弃了编程语言的注释规范,以换取更加紧凑的内容排版。注释主要分为三种类型:标题注释、内容注释、多行注释。
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@
|
||||||
|
|
||||||
阅读本书时,若发现某段内容提供了动画或图解,**建议你以图为主线**,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。
|
阅读本书时,若发现某段内容提供了动画或图解,**建议你以图为主线**,将文字内容(一般在图的上方)对齐到图中内容,综合来理解。
|
||||||
|
|
||||||
![animation](suggestions.assets/animation.gif)
|
![动画图解示例](suggestions.assets/animation.gif)
|
||||||
|
|
||||||
## 在代码实践中加深理解
|
## 在代码实践中加深理解
|
||||||
|
|
||||||
|
@ -171,17 +171,17 @@ git clone https://github.com/krahets/hello-algo.git
|
||||||
|
|
||||||
当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。
|
当然,你也可以点击“Download ZIP”直接下载代码压缩包,解压即可。
|
||||||
|
|
||||||
![download_code](suggestions.assets/download_code.png)
|
![克隆仓库与下载代码](suggestions.assets/download_code.png)
|
||||||
|
|
||||||
### 3) 运行源代码
|
### 3) 运行源代码
|
||||||
|
|
||||||
若代码块的顶部标有文件名称,则可在仓库 `codes` 文件夹中找到对应的 **源代码文件**。
|
若代码块的顶部标有文件名称,则可在仓库 `codes` 文件夹中找到对应的 **源代码文件**。
|
||||||
|
|
||||||
![code_md_to_repo](suggestions.assets/code_md_to_repo.png)
|
![代码块与对应的源代码文件](suggestions.assets/code_md_to_repo.png)
|
||||||
|
|
||||||
源代码文件可以帮助你省去不必要的调试时间,将精力集中在学习内容上。
|
源代码文件可以帮助你省去不必要的调试时间,将精力集中在学习内容上。
|
||||||
|
|
||||||
![running_code](suggestions.assets/running_code.gif)
|
![运行代码示例](suggestions.assets/running_code.gif)
|
||||||
|
|
||||||
## 在提问讨论中共同成长
|
## 在提问讨论中共同成长
|
||||||
|
|
||||||
|
@ -189,4 +189,4 @@ git clone https://github.com/krahets/hello-algo.git
|
||||||
|
|
||||||
同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家互相学习与进步!
|
同时,也希望你可以多花时间逛逛评论区。一方面,可以看看大家遇到了什么问题,反过来查漏补缺,这往往可以引起更加深度的思考。另一方面,也希望你可以慷慨地解答小伙伴们的问题、分享自己的见解,大家互相学习与进步!
|
||||||
|
|
||||||
![comment](suggestions.assets/comment.gif)
|
![评论区示例](suggestions.assets/comment.gif)
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
|
|
||||||
[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).
|
[2] Aditya Bhargava. Grokking Algorithms: An Illustrated Guide for Programmers and Other Curious People (1st Edition).
|
||||||
|
|
||||||
[3] 程杰. 大话数据结构.
|
[3] 严蔚敏. 数据结构( C 语言版).
|
||||||
|
|
||||||
[4] 王争. 数据结构与算法之美.
|
[4] 邓俊辉. 数据结构( C++ 语言版,第三版).
|
||||||
|
|
||||||
[5] 严蔚敏. 数据结构( C 语言版).
|
[5] 马克·艾伦·维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版).
|
||||||
|
|
||||||
[6] 邓俊辉. 数据结构( C++ 语言版,第三版).
|
[6] 程杰. 大话数据结构.
|
||||||
|
|
||||||
[7] 马克·艾伦·维斯著,陈越译. 数据结构与算法分析:Java语言描述(第三版).
|
[7] 王争. 数据结构与算法之美.
|
||||||
|
|
||||||
[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).
|
[8] Gayle Laakmann McDowell. Cracking the Coding Interview: 189 Programming Questions and Solutions (6th Edition).
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
如果我们想要给定数组中的一个目标元素 `target` ,获取该元素的索引,那么可以借助一个哈希表实现查找。
|
如果我们想要给定数组中的一个目标元素 `target` ,获取该元素的索引,那么可以借助一个哈希表实现查找。
|
||||||
|
|
||||||
![hash_search_index](hashing_search.assets/hash_search_index.png)
|
![哈希查找数组索引](hashing_search.assets/hash_search_index.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@
|
||||||
|
|
||||||
再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。
|
再比如,如果我们想要给定一个目标结点值 `target` ,获取对应的链表结点对象,那么也可以使用哈希查找实现。
|
||||||
|
|
||||||
![hash_search_listnode](hashing_search.assets/hash_search_listnode.png)
|
![哈希查找链表结点](hashing_search.assets/hash_search_listnode.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 `nums` 中查找目标元素 `target` 的对应索引,那么可以在数组中进行线性查找。
|
线性查找实质上就是遍历数据结构 + 判断条件。比如,我们想要在数组 `nums` 中查找目标元素 `target` 的对应索引,那么可以在数组中进行线性查找。
|
||||||
|
|
||||||
![linear_search](linear_search.assets/linear_search.png)
|
![在数组中线性查找元素](linear_search.assets/linear_search.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
|
|
@ -39,9 +39,7 @@
|
||||||
2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。
|
2. 同理,对剩余 $n - 1$ 个元素执行「冒泡」,可将第二大元素交换至正确位置,因而待排序元素只剩 $n - 2$ 个。
|
||||||
3. 以此类推…… **循环 $n - 1$ 轮「冒泡」,即可完成整个数组的排序**。
|
3. 以此类推…… **循环 $n - 1$ 轮「冒泡」,即可完成整个数组的排序**。
|
||||||
|
|
||||||
![bubble_sort_overview](bubble_sort.assets/bubble_sort_overview.png)
|
![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 冒泡排序流程 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,7 @@
|
||||||
|
|
||||||
回忆数组插入操作,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后再将 `base` 赋值给目标索引。
|
回忆数组插入操作,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后再将 `base` 赋值给目标索引。
|
||||||
|
|
||||||
![insertion_operation](insertion_sort.assets/insertion_operation.png)
|
![单次插入操作](insertion_sort.assets/insertion_operation.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 插入操作 </p>
|
|
||||||
|
|
||||||
## 算法流程
|
## 算法流程
|
||||||
|
|
||||||
|
@ -16,9 +14,7 @@
|
||||||
2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后,**数组前 3 个元素已完成排序**。
|
2. 第 2 轮选取 **第 3 个元素** 为 `base` ,执行「插入操作」后,**数组前 3 个元素已完成排序**。
|
||||||
3. 以此类推……最后一轮选取 **数组尾元素** 为 `base` ,执行「插入操作」后,**所有元素已完成排序**。
|
3. 以此类推……最后一轮选取 **数组尾元素** 为 `base` ,执行「插入操作」后,**所有元素已完成排序**。
|
||||||
|
|
||||||
![insertion_sort_overview](insertion_sort.assets/insertion_sort_overview.png)
|
![插入排序流程](insertion_sort.assets/insertion_sort_overview.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 插入排序流程 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
- 待排序的列表的 **元素类型** 可以是整数、浮点数、字符、或字符串;
|
- 待排序的列表的 **元素类型** 可以是整数、浮点数、字符、或字符串;
|
||||||
- 排序算法可以根据需要设定 **判断规则**,例如数字大小、字符 ASCII 码顺序、自定义规则;
|
- 排序算法可以根据需要设定 **判断规则**,例如数字大小、字符 ASCII 码顺序、自定义规则;
|
||||||
|
|
||||||
![sorting_examples](intro_to_sort.assets/sorting_examples.png)
|
![排序中不同的元素类型和判断规则](intro_to_sort.assets/sorting_examples.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 排序中的不同元素类型和判断规则 </p>
|
|
||||||
|
|
||||||
## 评价维度
|
## 评价维度
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,7 @@
|
||||||
1. **划分阶段**:通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题;
|
1. **划分阶段**:通过递归不断 **将数组从中点位置划分开**,将长数组的排序问题转化为短数组的排序问题;
|
||||||
2. **合并阶段**:划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序;
|
2. **合并阶段**:划分到子数组长度为 1 时,开始向上合并,不断将 **左、右两个短排序数组** 合并为 **一个长排序数组**,直至合并至原数组时完成排序;
|
||||||
|
|
||||||
![merge_sort_overview](merge_sort.assets/merge_sort_overview.png)
|
![归并排序的划分与合并阶段](merge_sort.assets/merge_sort_overview.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 归并排序两阶段:划分与合并 </p>
|
|
||||||
|
|
||||||
## 算法流程
|
## 算法流程
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,6 @@
|
||||||
=== "<9>"
|
=== "<9>"
|
||||||
![pivot_division_step9](quick_sort.assets/pivot_division_step9.png)
|
![pivot_division_step9](quick_sort.assets/pivot_division_step9.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 哨兵划分 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
```java title="quick_sort.java"
|
```java title="quick_sort.java"
|
||||||
|
@ -125,9 +123,7 @@
|
||||||
|
|
||||||
观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。
|
观察发现,快速排序和「二分查找」的原理类似,都是以对数阶的时间复杂度来缩小处理区间。
|
||||||
|
|
||||||
![quick_sort_overview](quick_sort.assets/quick_sort_overview.png)
|
![快速排序流程](quick_sort.assets/quick_sort_overview.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 快速排序流程 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。
|
对于队列,我们只能在头部删除或在尾部添加元素,而「双向队列 Deque」更加灵活,在其头部和尾部都能执行元素添加或删除操作。
|
||||||
|
|
||||||
![deque_operations](deque.assets/deque_operations.png)
|
![双向队列的操作](deque.assets/deque_operations.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 双向队列的操作 </p>
|
|
||||||
|
|
||||||
## 双向队列常用操作
|
## 双向队列常用操作
|
||||||
|
|
||||||
|
|
|
@ -4,9 +4,7 @@
|
||||||
|
|
||||||
我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。
|
我们将队列头部称为「队首」,队列尾部称为「队尾」,将把元素加入队尾的操作称为「入队」,删除队首元素的操作称为「出队」。
|
||||||
|
|
||||||
![queue_operations](queue.assets/queue_operations.png)
|
![队列的先入先出规则](queue.assets/queue_operations.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 队列的先入先出特性 </p>
|
|
||||||
|
|
||||||
## 队列常用操作
|
## 队列常用操作
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,7 @@
|
||||||
|
|
||||||
我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。
|
我们将这一摞元素的顶部称为「栈顶」,将底部称为「栈底」,将把元素添加到栈顶的操作称为「入栈」,将删除栈顶元素的操作称为「出栈」。
|
||||||
|
|
||||||
![stack_operations](stack.assets/stack_operations.png)
|
![栈的先入后出规则](stack.assets/stack_operations.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 栈的先入后出特性 </p>
|
|
||||||
|
|
||||||
## 栈常用操作
|
## 栈常用操作
|
||||||
|
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
|
|
||||||
如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。
|
如下图所示,执行两步删除结点后,该二叉搜索树就会退化为链表。
|
||||||
|
|
||||||
![avltree_degradation_from_removing_node](avl_tree.assets/avltree_degradation_from_removing_node.png)
|
![AVL 树在删除结点后发生退化](avl_tree.assets/avltree_degradation_from_removing_node.png)
|
||||||
|
|
||||||
再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。
|
再比如,在以下完美二叉树中插入两个结点后,树严重向左偏斜,查找操作的时间复杂度也随之发生劣化。
|
||||||
|
|
||||||
![avltree_degradation_from_inserting_node](avl_tree.assets/avltree_degradation_from_inserting_node.png)
|
![AVL 树在插入结点后发生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png)
|
||||||
|
|
||||||
G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。
|
G. M. Adelson-Velsky 和 E. M. Landis 在其 1962 年发表的论文 "An algorithm for the organization of information" 中提出了「AVL 树」。**论文中描述了一系列操作,使得在不断添加与删除结点后,AVL 树仍然不会发生退化**,进而使得各种操作的时间复杂度均能保持在 $O(\log n)$ 级别。
|
||||||
|
|
||||||
|
@ -323,7 +323,7 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||||
|
|
||||||
进而,如果结点 `child` 本身有右子结点(记为 `grandChild` ),则需要在「右旋」中添加一步:将 `grandChild` 作为 `node` 的左子结点。
|
进而,如果结点 `child` 本身有右子结点(记为 `grandChild` ),则需要在「右旋」中添加一步:将 `grandChild` 作为 `node` 的左子结点。
|
||||||
|
|
||||||
![avltree_right_rotate_with_grandchild](avl_tree.assets/avltree_right_rotate_with_grandchild.png)
|
![有 grandChild 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png)
|
||||||
|
|
||||||
“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。
|
“向右旋转”是一种形象化的说法,实际需要通过修改结点指针实现,代码如下所示。
|
||||||
|
|
||||||
|
@ -391,11 +391,11 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||||
|
|
||||||
类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。
|
类似地,如果将取上述失衡二叉树的“镜像”,那么则需要「左旋」操作。
|
||||||
|
|
||||||
![avltree_left_rotate](avl_tree.assets/avltree_left_rotate.png)
|
![左旋操作](avl_tree.assets/avltree_left_rotate.png)
|
||||||
|
|
||||||
同理,若结点 `child` 本身有左子结点(记为 `grandChild` ),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。
|
同理,若结点 `child` 本身有左子结点(记为 `grandChild` ),则需要在「左旋」中添加一步:将 `grandChild` 作为 `node` 的右子结点。
|
||||||
|
|
||||||
![avltree_left_rotate_with_grandchild](avl_tree.assets/avltree_left_rotate_with_grandchild.png)
|
![有 grandChild 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png)
|
||||||
|
|
||||||
观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` ,即可得到「左旋」代码。
|
观察发现,**「左旋」和「右旋」操作是镜像对称的,两者对应解决的两种失衡情况也是对称的**。根据对称性,我们可以很方便地从「右旋」推导出「左旋」。具体地,只需将「右旋」代码中的把所有的 `left` 替换为 `right` 、所有的 `right` 替换为 `left` ,即可得到「左旋」代码。
|
||||||
|
|
||||||
|
@ -463,19 +463,19 @@ AVL 树的独特之处在于「旋转 Rotation」的操作,其可 **在不影
|
||||||
|
|
||||||
对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。
|
对于下图的失衡结点 3 ,**单一使用左旋或右旋都无法使子树恢复平衡**,此时需要「先左旋后右旋」,即先对 `child` 执行「左旋」,再对 `node` 执行「右旋」。
|
||||||
|
|
||||||
![avltree_left_right_rotate](avl_tree.assets/avltree_left_right_rotate.png)
|
![先左旋后右旋](avl_tree.assets/avltree_left_right_rotate.png)
|
||||||
|
|
||||||
### Case 4 - 先右后左
|
### Case 4 - 先右后左
|
||||||
|
|
||||||
同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 `child` 执行「右旋」,然后对 `node` 执行「左旋」。
|
同理,取以上失衡二叉树的镜像,则需要「先右旋后左旋」,即先对 `child` 执行「右旋」,然后对 `node` 执行「左旋」。
|
||||||
|
|
||||||
![avltree_right_left_rotate](avl_tree.assets/avltree_right_left_rotate.png)
|
![先右旋后左旋](avl_tree.assets/avltree_right_left_rotate.png)
|
||||||
|
|
||||||
### 旋转的选择
|
### 旋转的选择
|
||||||
|
|
||||||
下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 **右旋、左旋、先右后左、先左后右** 的旋转操作。
|
下图描述的四种失衡情况与上述 Cases 逐个对应,分别需采用 **右旋、左旋、先右后左、先左后右** 的旋转操作。
|
||||||
|
|
||||||
![avltree_rotation_cases](avl_tree.assets/avltree_rotation_cases.png)
|
![AVL 树的四种旋转情况](avl_tree.assets/avltree_rotation_cases.png)
|
||||||
|
|
||||||
具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。
|
具体地,在代码中使用 **失衡结点的平衡因子、较高一侧子结点的平衡因子** 来确定失衡结点属于上图中的哪种情况。
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
1. 对于根结点,左子树中所有结点的值 $<$ 根结点的值 $<$ 右子树中所有结点的值;
|
1. 对于根结点,左子树中所有结点的值 $<$ 根结点的值 $<$ 右子树中所有结点的值;
|
||||||
2. 任意结点的左子树和右子树也是二叉搜索树,即也满足条件 `1.` ;
|
2. 任意结点的左子树和右子树也是二叉搜索树,即也满足条件 `1.` ;
|
||||||
|
|
||||||
![binary_search_tree](binary_search_tree.assets/binary_search_tree.png)
|
![二叉搜索树](binary_search_tree.assets/binary_search_tree.png)
|
||||||
|
|
||||||
## 二叉搜索树的操作
|
## 二叉搜索树的操作
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@
|
||||||
|
|
||||||
二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。
|
二叉搜索树不允许存在重复结点,否则将会违背其定义。因此若待插入结点在树中已经存在,则不执行插入,直接返回即可。
|
||||||
|
|
||||||
![bst_insert](binary_search_tree.assets/bst_insert.png)
|
![在二叉搜索树中插入结点](binary_search_tree.assets/bst_insert.png)
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -172,11 +172,11 @@
|
||||||
|
|
||||||
**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。
|
**当待删除结点的子结点数量 $= 0$ 时**,表明待删除结点是叶结点,直接删除即可。
|
||||||
|
|
||||||
![bst_remove_case1](binary_search_tree.assets/bst_remove_case1.png)
|
![在二叉搜索树中删除结点(度为 0)](binary_search_tree.assets/bst_remove_case1.png)
|
||||||
|
|
||||||
**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。
|
**当待删除结点的子结点数量 $= 1$ 时**,将待删除结点替换为其子结点即可。
|
||||||
|
|
||||||
![bst_remove_case2](binary_search_tree.assets/bst_remove_case2.png)
|
![在二叉搜索树中删除结点(度为 1)](binary_search_tree.assets/bst_remove_case2.png)
|
||||||
|
|
||||||
**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步:
|
**当待删除结点的子结点数量 $= 2$ 时**,删除操作分为三步:
|
||||||
|
|
||||||
|
@ -284,7 +284,7 @@
|
||||||
|
|
||||||
借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,而无需额外排序,非常高效。
|
借助中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,而无需额外排序,非常高效。
|
||||||
|
|
||||||
![bst_inorder_traversal](binary_search_tree.assets/bst_inorder_traversal.png)
|
![二叉搜索树的中序遍历序列](binary_search_tree.assets/bst_inorder_traversal.png)
|
||||||
|
|
||||||
## 二叉搜索树的效率
|
## 二叉搜索树的效率
|
||||||
|
|
||||||
|
@ -325,7 +325,7 @@
|
||||||
|
|
||||||
在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。
|
在实际应用中,如何保持二叉搜索树的平衡,也是一个需要重要考虑的问题。
|
||||||
|
|
||||||
![bst_degradation](binary_search_tree.assets/bst_degradation.png)
|
![二叉搜索树的平衡与退化](binary_search_tree.assets/bst_degradation.png)
|
||||||
|
|
||||||
## 二叉搜索树常见应用
|
## 二叉搜索树常见应用
|
||||||
|
|
||||||
|
|
|
@ -127,9 +127,7 @@
|
||||||
|
|
||||||
除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。
|
除了叶结点外,每个结点都有子结点和子树。例如,若将下图的「结点 2」看作父结点,那么其左子结点和右子结点分别为「结点 4」和「结点 5」,左子树和右子树分别为「结点 4 及其以下结点形成的树」和「结点 5 及其以下结点形成的树」。
|
||||||
|
|
||||||
![binary_tree_definition](binary_tree.assets/binary_tree_definition.png)
|
![父结点、子结点、子树](binary_tree.assets/binary_tree_definition.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 子结点与子树 </p>
|
|
||||||
|
|
||||||
## 二叉树常见术语
|
## 二叉树常见术语
|
||||||
|
|
||||||
|
@ -144,9 +142,7 @@
|
||||||
- 结点「深度 Depth」 :根结点到该结点走过边的数量;
|
- 结点「深度 Depth」 :根结点到该结点走过边的数量;
|
||||||
- 结点「高度 Height」:最远叶结点到该结点走过边的数量;
|
- 结点「高度 Height」:最远叶结点到该结点走过边的数量;
|
||||||
|
|
||||||
![binary_tree_terminology](binary_tree.assets/binary_tree_terminology.png)
|
![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的常见术语 </p>
|
|
||||||
|
|
||||||
!!! tip "高度与深度的定义"
|
!!! tip "高度与深度的定义"
|
||||||
|
|
||||||
|
@ -304,9 +300,7 @@
|
||||||
|
|
||||||
**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。
|
**插入与删除结点**。与链表类似,插入与删除结点都可以通过修改指针实现。
|
||||||
|
|
||||||
![binary_tree_add_remove](binary_tree.assets/binary_tree_add_remove.png)
|
![在二叉树中插入与删除结点](binary_tree.assets/binary_tree_add_remove.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 在二叉树中插入与删除结点 </p>
|
|
||||||
|
|
||||||
=== "Java"
|
=== "Java"
|
||||||
|
|
||||||
|
@ -428,7 +422,7 @@
|
||||||
|
|
||||||
在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。
|
在中文社区中,完美二叉树常被称为「满二叉树」,请注意与完满二叉树区分。
|
||||||
|
|
||||||
![perfect_binary_tree](binary_tree.assets/perfect_binary_tree.png)
|
![完美二叉树](binary_tree.assets/perfect_binary_tree.png)
|
||||||
|
|
||||||
### 完全二叉树
|
### 完全二叉树
|
||||||
|
|
||||||
|
@ -436,19 +430,19 @@
|
||||||
|
|
||||||
**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。
|
**完全二叉树非常适合用数组来表示**。如果按照层序遍历序列的顺序来存储,那么空结点 `null` 一定全部出现在序列的尾部,因此我们就可以不用存储这些 null 了。
|
||||||
|
|
||||||
![complete_binary_tree](binary_tree.assets/complete_binary_tree.png)
|
![完全二叉树](binary_tree.assets/complete_binary_tree.png)
|
||||||
|
|
||||||
### 完满二叉树
|
### 完满二叉树
|
||||||
|
|
||||||
「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。
|
「完满二叉树 Full Binary Tree」除了叶结点之外,其余所有结点都有两个子结点。
|
||||||
|
|
||||||
![full_binary_tree](binary_tree.assets/full_binary_tree.png)
|
![完满二叉树](binary_tree.assets/full_binary_tree.png)
|
||||||
|
|
||||||
### 平衡二叉树
|
### 平衡二叉树
|
||||||
|
|
||||||
「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。
|
「平衡二叉树 Balanced Binary Tree」中任意结点的左子树和右子树的高度之差的绝对值 $\leq 1$ 。
|
||||||
|
|
||||||
![balanced_binary_tree](binary_tree.assets/balanced_binary_tree.png)
|
![平衡二叉树](binary_tree.assets/balanced_binary_tree.png)
|
||||||
|
|
||||||
## 二叉树的退化
|
## 二叉树的退化
|
||||||
|
|
||||||
|
@ -457,9 +451,7 @@
|
||||||
- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势;
|
- 完美二叉树是一个二叉树的“最佳状态”,可以完全发挥出二叉树“分治”的优势;
|
||||||
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ;
|
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ ;
|
||||||
|
|
||||||
![binary_tree_corner_cases](binary_tree.assets/binary_tree_corner_cases.png)
|
![二叉树的最佳与最二叉树的最佳和最差结构差情况](binary_tree.assets/binary_tree_corner_cases.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的最佳和最差结构 </p>
|
|
||||||
|
|
||||||
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
|
如下表所示,在最佳和最差结构下,二叉树的叶结点数量、结点总数、高度等达到极大或极小值。
|
||||||
|
|
||||||
|
@ -482,11 +474,11 @@
|
||||||
|
|
||||||
**本质上,映射公式的作用就是链表中的指针**。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。
|
**本质上,映射公式的作用就是链表中的指针**。对于层序遍历序列中的任意结点,我们都可以使用映射公式来访问子结点。因此,可以直接使用层序遍历序列(即数组)来表示完美二叉树。
|
||||||
|
|
||||||
![array_representation_mapping](binary_tree.assets/array_representation_mapping.png)
|
![完美二叉树的数组表示](binary_tree.assets/array_representation_mapping.png)
|
||||||
|
|
||||||
然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 `null` ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。
|
然而,完美二叉树只是个例,二叉树中间层往往存在许多空结点(即 `null` ),而层序遍历序列并不包含这些空结点,并且我们无法单凭序列来猜测空结点的数量和分布位置,**即理论上存在许多种二叉树都符合该层序遍历序列**。显然,这种情况无法使用数组来存储二叉树。
|
||||||
|
|
||||||
![array_representation_without_empty](binary_tree.assets/array_representation_without_empty.png)
|
![给定数组对应多种二叉树可能性](binary_tree.assets/array_representation_without_empty.png)
|
||||||
|
|
||||||
为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,**即在序列中使用特殊符号来显式地表示“空位”**。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。
|
为了解决此问题,考虑按照完美二叉树的形式来表示所有二叉树,**即在序列中使用特殊符号来显式地表示“空位”**。如下图所示,这样处理后,序列(数组)就可以唯一表示二叉树了。
|
||||||
|
|
||||||
|
@ -565,10 +557,10 @@
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
![array_representation_with_empty](binary_tree.assets/array_representation_with_empty.png)
|
![任意类型二叉树的数组表示](binary_tree.assets/array_representation_with_empty.png)
|
||||||
|
|
||||||
回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。
|
回顾「完全二叉树」的定义,其只有最底层有空结点,并且最底层的结点尽量靠左,因而所有空结点都一定出现在层序遍历序列的末尾。**因为我们先验地确定了空位的位置,所以在使用数组表示完全二叉树时,可以省略存储“空位”**。因此,完全二叉树非常适合使用数组来表示。
|
||||||
|
|
||||||
![array_representation_complete_binary_tree](binary_tree.assets/array_representation_complete_binary_tree.png)
|
![完全二叉树的数组表示](binary_tree.assets/array_representation_complete_binary_tree.png)
|
||||||
|
|
||||||
数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。
|
数组表示有两个优点: 一是不需要存储指针,节省空间;二是可以随机访问结点。然而,当二叉树中的“空位”很多时,数组中只包含很少结点的数据,空间利用率很低。
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。
|
层序遍历本质上是「广度优先搜索 Breadth-First Traversal」,其体现着一种“一圈一圈向外”的层进遍历方式。
|
||||||
|
|
||||||
![binary_tree_bfs](binary_tree_traversal.assets/binary_tree_bfs.png)
|
![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的层序遍历 </p>
|
<p align="center"> Fig. 二叉树的层序遍历 </p>
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@
|
||||||
|
|
||||||
如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。
|
如下图所示,左侧是深度优先遍历的的示意图,右上方是对应的递归实现代码。深度优先遍历就像是绕着整个二叉树的外围“走”一圈,走的过程中,在每个结点都会遇到三个位置,分别对应前序遍历、中序遍历、后序遍历。
|
||||||
|
|
||||||
![binary_tree_dfs](binary_tree_traversal.assets/binary_tree_dfs.png)
|
![二叉搜索树的前、中、后序遍历](binary_tree_traversal.assets/binary_tree_dfs.png)
|
||||||
|
|
||||||
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
|
<p align="center"> Fig. 二叉树的前 / 中 / 后序遍历 </p>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue