mirror of
https://github.com/krahets/hello-algo.git
synced 2024-12-26 10:46:29 +08:00
build
This commit is contained in:
parent
9701430089
commit
0105644232
83 changed files with 516 additions and 509 deletions
|
@ -22,7 +22,7 @@ comments: true
|
|||
2. 修改 Markdown 源文件内容,检查内容的正确性,并尽量保持排版格式的统一。
|
||||
3. 在页面底部填写修改说明,然后点击“Propose file change”按钮。页面跳转后,点击“Create pull request”按钮即可发起拉取请求。
|
||||
|
||||
![页面编辑按键](contribution.assets/edit_markdown.png)
|
||||
![页面编辑按键](contribution.assets/edit_markdown.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 16-1 页面编辑按键 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/help-circle-outline
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![附录](../assets/covers/chapter_appendix.jpg){ width="600" }
|
||||
![附录](../assets/covers/chapter_appendix.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ comments: true
|
|||
|
||||
「数组 array」是一种线性数据结构,其将相同类型元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的「索引 index」。图 4-1 展示了数组的主要术语和概念。
|
||||
|
||||
![数组定义与存储方式](array.assets/array_definition.png)
|
||||
![数组定义与存储方式](array.assets/array_definition.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-1 数组定义与存储方式 </p>
|
||||
|
||||
|
@ -123,7 +123,7 @@ comments: true
|
|||
|
||||
数组元素被存储在连续的内存空间中,这意味着计算数组元素的内存地址非常容易。给定数组内存地址(即首元素内存地址)和某个元素的索引,我们可以使用图 4-2 所示的公式计算得到该元素的内存地址,从而直接访问此元素。
|
||||
|
||||
![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png)
|
||||
![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-2 数组元素的内存地址计算 </p>
|
||||
|
||||
|
@ -291,7 +291,7 @@ comments: true
|
|||
|
||||
数组元素在内存中是“紧挨着的”,它们之间没有空间再存放任何数据。如图 4-3 所示,如果想要在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。
|
||||
|
||||
![数组插入元素示例](array.assets/array_insert_element.png)
|
||||
![数组插入元素示例](array.assets/array_insert_element.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-3 数组插入元素示例 </p>
|
||||
|
||||
|
@ -468,7 +468,7 @@ comments: true
|
|||
|
||||
同理,如图 4-4 所示,若想要删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。
|
||||
|
||||
![数组删除元素示例](array.assets/array_remove_element.png)
|
||||
![数组删除元素示例](array.assets/array_remove_element.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-4 数组删除元素示例 </p>
|
||||
|
||||
|
|
|
@ -5,11 +5,7 @@ icon: material/view-list-outline
|
|||
|
||||
# 第 4 章 数组与链表
|
||||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![数组与链表](../assets/covers/chapter_array_and_linkedlist.jpg){ width="600" }
|
||||
|
||||
</div>
|
||||
![数组与链表](../assets/covers/chapter_array_and_linkedlist.jpg){ class="cover-image" }
|
||||
|
||||
!!! abstract
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ comments: true
|
|||
|
||||
链表的设计使得各个节点可以被分散存储在内存各处,它们的内存地址是无须连续的。
|
||||
|
||||
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png)
|
||||
![链表定义与存储方式](linked_list.assets/linkedlist_definition.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-5 链表定义与存储方式 </p>
|
||||
|
||||
|
@ -405,7 +405,7 @@ comments: true
|
|||
|
||||
相比之下,在数组中插入元素的时间复杂度为 $O(n)$ ,在大数据量下的效率较低。
|
||||
|
||||
![链表插入节点示例](linked_list.assets/linkedlist_insert_node.png)
|
||||
![链表插入节点示例](linked_list.assets/linkedlist_insert_node.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-6 链表插入节点示例 </p>
|
||||
|
||||
|
@ -547,7 +547,7 @@ comments: true
|
|||
|
||||
请注意,尽管在删除操作完成后节点 `P` 仍然指向 `n1` ,但实际上遍历此链表已经无法访问到 `P` ,这意味着 `P` 已经不再属于该链表了。
|
||||
|
||||
![链表删除节点](linked_list.assets/linkedlist_remove_node.png)
|
||||
![链表删除节点](linked_list.assets/linkedlist_remove_node.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-7 链表删除节点 </p>
|
||||
|
||||
|
@ -1316,7 +1316,7 @@ comments: true
|
|||
}
|
||||
```
|
||||
|
||||
![常见链表种类](linked_list.assets/linkedlist_common_types.png)
|
||||
![常见链表种类](linked_list.assets/linkedlist_common_types.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 4-8 常见链表种类 </p>
|
||||
|
||||
|
|
|
@ -206,7 +206,7 @@ comments: true
|
|||
[class]{}-[func]{preOrder}
|
||||
```
|
||||
|
||||
![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png)
|
||||
![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-1 在前序遍历中搜索节点 </p>
|
||||
|
||||
|
@ -477,37 +477,37 @@ comments: true
|
|||
观察图 13-2 所示的过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为逆向的。
|
||||
|
||||
=== "<1>"
|
||||
![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png)
|
||||
![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png)
|
||||
![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png)
|
||||
![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png)
|
||||
![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png)
|
||||
![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png)
|
||||
![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png)
|
||||
![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png)
|
||||
![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png)
|
||||
![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png)
|
||||
![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png)
|
||||
![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-2 尝试与回退 </p>
|
||||
|
||||
|
@ -781,7 +781,7 @@ comments: true
|
|||
|
||||
剪枝是一个非常形象的名词。如图 13-3 所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。
|
||||
|
||||
![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png)
|
||||
![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-3 根据约束条件剪枝 </p>
|
||||
|
||||
|
@ -1657,7 +1657,7 @@ comments: true
|
|||
|
||||
根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。图 13-4 对比了保留或删除 `return` 语句的搜索过程。
|
||||
|
||||
![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png)
|
||||
![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-4 保留与删除 return 的搜索过程对比 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/map-marker-path
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![回溯](../assets/covers/chapter_backtracking.jpg){ width="600" }
|
||||
![回溯](../assets/covers/chapter_backtracking.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,13 +10,13 @@ comments: true
|
|||
|
||||
如图 13-15 所示,当 $n = 4$ 时,共可以找到两个解。从回溯算法的角度看,$n \times n$ 大小的棋盘共有 $n^2$ 个格子,给出了所有的选择 `choices` 。在逐个放置皇后的过程中,棋盘状态在不断地变化,每个时刻的棋盘就是状态 `state` 。
|
||||
|
||||
![4 皇后问题的解](n_queens_problem.assets/solution_4_queens.png)
|
||||
![4 皇后问题的解](n_queens_problem.assets/solution_4_queens.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-15 4 皇后问题的解 </p>
|
||||
|
||||
图 13-16 展示了本题的三个约束条件:**多个皇后不能在同一行、同一列、同一对角线**。值得注意的是,对角线分为主对角线 `\` 和次对角线 `/` 两种。
|
||||
|
||||
![n 皇后问题的约束条件](n_queens_problem.assets/n_queens_constraints.png)
|
||||
![n 皇后问题的约束条件](n_queens_problem.assets/n_queens_constraints.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-16 n 皇后问题的约束条件 </p>
|
||||
|
||||
|
@ -28,7 +28,7 @@ comments: true
|
|||
|
||||
如图 13-17 所示,为 $4$ 皇后问题的逐行放置过程。受画幅限制,图 13-17 仅展开了第一行的其中一个搜索分支,并且将不满足列约束和对角线约束的方案都进行了剪枝。
|
||||
|
||||
![逐行放置策略](n_queens_problem.assets/n_queens_placing.png)
|
||||
![逐行放置策略](n_queens_problem.assets/n_queens_placing.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-17 逐行放置策略 </p>
|
||||
|
||||
|
@ -44,7 +44,7 @@ comments: true
|
|||
|
||||
同理,**次对角线上的所有格子的 $row + col$ 是恒定值**。我们同样也可以借助数组 `diags2` 来处理次对角线约束。
|
||||
|
||||
![处理列约束和对角线约束](n_queens_problem.assets/n_queens_cols_diagonals.png)
|
||||
![处理列约束和对角线约束](n_queens_problem.assets/n_queens_cols_diagonals.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-18 处理列约束和对角线约束 </p>
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ comments: true
|
|||
|
||||
如图 13-5 所示,我们可以将搜索过程展开成一个递归树,树中的每个节点代表当前状态 `state` 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。
|
||||
|
||||
![全排列的递归树](permutations_problem.assets/permutations_i.png)
|
||||
![全排列的递归树](permutations_problem.assets/permutations_i.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-5 全排列的递归树 </p>
|
||||
|
||||
|
@ -45,7 +45,7 @@ comments: true
|
|||
|
||||
如图 13-6 所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1 和元素 3 的分支。
|
||||
|
||||
![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png)
|
||||
![全排列剪枝示例](permutations_problem.assets/permutations_i_pruning.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-6 全排列剪枝示例 </p>
|
||||
|
||||
|
@ -481,7 +481,7 @@ comments: true
|
|||
|
||||
如图 13-7 所示,上述方法生成的排列有一半都是重复的。
|
||||
|
||||
![重复排列](permutations_problem.assets/permutations_ii.png)
|
||||
![重复排列](permutations_problem.assets/permutations_ii.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-7 重复排列 </p>
|
||||
|
||||
|
@ -495,7 +495,7 @@ comments: true
|
|||
|
||||
本质上看,**我们的目标是在某一轮选择中,保证多个相等的元素仅被选择一次**。
|
||||
|
||||
![重复排列剪枝](permutations_problem.assets/permutations_ii_pruning.png)
|
||||
![重复排列剪枝](permutations_problem.assets/permutations_ii_pruning.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-8 重复排列剪枝 </p>
|
||||
|
||||
|
@ -955,6 +955,6 @@ comments: true
|
|||
|
||||
图 13-9 展示了两个剪枝条件的生效范围。注意,树中的每个节点代表一个选择,从根节点到叶节点的路径上的各个节点构成一个排列。
|
||||
|
||||
![两种剪枝条件的作用范围](permutations_problem.assets/permutations_ii_pruning_summary.png)
|
||||
![两种剪枝条件的作用范围](permutations_problem.assets/permutations_ii_pruning_summary.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-9 两种剪枝条件的作用范围 </p>
|
||||
|
|
|
@ -432,7 +432,7 @@ comments: true
|
|||
|
||||
这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如图 13-10 所示,先选 $4$ 后选 $5$ 与先选 $5$ 后选 $4$ 是两个不同的分支,但两者对应同一个子集。
|
||||
|
||||
![子集搜索与越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png)
|
||||
![子集搜索与越界剪枝](subset_sum_problem.assets/subset_sum_i_naive.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-10 子集搜索与越界剪枝 </p>
|
||||
|
||||
|
@ -454,7 +454,7 @@ comments: true
|
|||
2. 前两轮选择 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。
|
||||
3. 若第一轮选择 $5$ ,**则第二轮应该跳过 $3$ 和 $4$** ,因为子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 与第 `1.` 和 `2.` 步中描述的子集完全重复。
|
||||
|
||||
![不同选择顺序导致的重复子集](subset_sum_problem.assets/subset_sum_i_pruning.png)
|
||||
![不同选择顺序导致的重复子集](subset_sum_problem.assets/subset_sum_i_pruning.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-11 不同选择顺序导致的重复子集 </p>
|
||||
|
||||
|
@ -908,7 +908,7 @@ comments: true
|
|||
|
||||
如图 13-12 所示,为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入到以上代码后的整体回溯过程。
|
||||
|
||||
![子集和 I 回溯过程](subset_sum_problem.assets/subset_sum_i.png)
|
||||
![子集和 I 回溯过程](subset_sum_problem.assets/subset_sum_i.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-12 子集和 I 回溯过程 </p>
|
||||
|
||||
|
@ -922,7 +922,7 @@ comments: true
|
|||
|
||||
**造成这种重复的原因是相等元素在某轮中被多次选择**。在图 13-13 中,第一轮共有三个选择,其中两个都为 $4$ ,会产生两个重复的搜索分支,从而输出重复子集;同理,第二轮的两个 $4$ 也会产生重复子集。
|
||||
|
||||
![相等元素导致的重复子集](subset_sum_problem.assets/subset_sum_ii_repeat.png)
|
||||
![相等元素导致的重复子集](subset_sum_problem.assets/subset_sum_ii_repeat.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-13 相等元素导致的重复子集 </p>
|
||||
|
||||
|
@ -1427,6 +1427,6 @@ comments: true
|
|||
|
||||
图 13-14 展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。
|
||||
|
||||
![子集和 II 回溯过程](subset_sum_problem.assets/subset_sum_ii.png)
|
||||
![子集和 II 回溯过程](subset_sum_problem.assets/subset_sum_ii.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 13-14 子集和 II 回溯过程 </p>
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/timer-sand
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![复杂度分析](../assets/covers/chapter_complexity_analysis.jpg){ width="600" }
|
||||
![复杂度分析](../assets/covers/chapter_complexity_analysis.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ comments: true
|
|||
|
||||
图 2-1 展示了该求和函数的流程框图。
|
||||
|
||||
![求和函数的流程框图](iteration_and_recursion.assets/iteration.png)
|
||||
![求和函数的流程框图](iteration_and_recursion.assets/iteration.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-1 求和函数的流程框图 </p>
|
||||
|
||||
|
@ -823,7 +823,7 @@ comments: true
|
|||
|
||||
图 2-2 给出了该嵌套循环的流程框图。
|
||||
|
||||
![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png)
|
||||
![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-2 嵌套循环的流程框图 </p>
|
||||
|
||||
|
@ -1028,7 +1028,7 @@ comments: true
|
|||
|
||||
图 2-3 展示了该函数的递归过程。
|
||||
|
||||
![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png)
|
||||
![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-3 求和函数的递归过程 </p>
|
||||
|
||||
|
@ -1051,7 +1051,7 @@ comments: true
|
|||
|
||||
如图 2-4 所示,在触发终止条件前,同时存在 $n$ 个未返回的递归函数,**递归深度为 $n$** 。
|
||||
|
||||
![递归调用深度](iteration_and_recursion.assets/recursion_sum_depth.png)
|
||||
![递归调用深度](iteration_and_recursion.assets/recursion_sum_depth.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-4 递归调用深度 </p>
|
||||
|
||||
|
@ -1227,7 +1227,7 @@ comments: true
|
|||
- **普通递归**:求和操作是在“归”的过程中执行的,每层返回后都要再执行一次求和操作。
|
||||
- **尾递归**:求和操作是在“递”的过程中执行的,“归”的过程只需层层返回。
|
||||
|
||||
![尾递归过程](iteration_and_recursion.assets/tail_recursion_sum.png)
|
||||
![尾递归过程](iteration_and_recursion.assets/tail_recursion_sum.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-5 尾递归过程 </p>
|
||||
|
||||
|
@ -1432,7 +1432,7 @@ comments: true
|
|||
|
||||
观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一个层数为 $n$ 的「递归树 recursion tree」。
|
||||
|
||||
![斐波那契数列的递归树](iteration_and_recursion.assets/recursion_tree.png)
|
||||
![斐波那契数列的递归树](iteration_and_recursion.assets/recursion_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-6 斐波那契数列的递归树 </p>
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ comments: true
|
|||
|
||||
在分析一段程序的空间复杂度时,**我们通常统计暂存数据、栈帧空间和输出数据三部分**。
|
||||
|
||||
![算法使用的相关空间](space_complexity.assets/space_types.png)
|
||||
![算法使用的相关空间](space_complexity.assets/space_types.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-15 算法使用的相关空间 </p>
|
||||
|
||||
|
@ -717,7 +717,7 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
|
|||
\end{aligned}
|
||||
$$
|
||||
|
||||
![常见的空间复杂度类型](space_complexity.assets/space_complexity_common_types.png)
|
||||
![常见的空间复杂度类型](space_complexity.assets/space_complexity_common_types.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-16 常见的空间复杂度类型 </p>
|
||||
|
||||
|
@ -1463,7 +1463,7 @@ $$
|
|||
}
|
||||
```
|
||||
|
||||
![递归函数产生的线性阶空间复杂度](space_complexity.assets/space_complexity_recursive_linear.png)
|
||||
![递归函数产生的线性阶空间复杂度](space_complexity.assets/space_complexity_recursive_linear.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-17 递归函数产生的线性阶空间复杂度 </p>
|
||||
|
||||
|
@ -1842,7 +1842,7 @@ $$
|
|||
}
|
||||
```
|
||||
|
||||
![递归函数产生的平方阶空间复杂度](space_complexity.assets/space_complexity_recursive_quadratic.png)
|
||||
![递归函数产生的平方阶空间复杂度](space_complexity.assets/space_complexity_recursive_quadratic.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-18 递归函数产生的平方阶空间复杂度 </p>
|
||||
|
||||
|
@ -2015,7 +2015,7 @@ $$
|
|||
}
|
||||
```
|
||||
|
||||
![满二叉树产生的指数阶空间复杂度](space_complexity.assets/space_complexity_exponential.png)
|
||||
![满二叉树产生的指数阶空间复杂度](space_complexity.assets/space_complexity_exponential.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-19 满二叉树产生的指数阶空间复杂度 </p>
|
||||
|
||||
|
|
|
@ -462,7 +462,7 @@ $$
|
|||
- 算法 `B` 中的打印操作需要循环 $n$ 次,算法运行时间随着 $n$ 增大呈线性增长。此算法的时间复杂度被称为“线性阶”。
|
||||
- 算法 `C` 中的打印操作需要循环 $1000000$ 次,虽然运行时间很长,但它与输入数据大小 $n$ 无关。因此 `C` 的时间复杂度和 `A` 相同,仍为“常数阶”。
|
||||
|
||||
![算法 A、B 和 C 的时间增长趋势](time_complexity.assets/time_complexity_simple_example.png)
|
||||
![算法 A、B 和 C 的时间增长趋势](time_complexity.assets/time_complexity_simple_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-7 算法 A、B 和 C 的时间增长趋势 </p>
|
||||
|
||||
|
@ -661,7 +661,7 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因
|
|||
|
||||
如图 2-8 所示,计算渐近上界就是寻找一个函数 $f(n)$ ,使得当 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别,仅相差一个常数项 $c$ 的倍数。
|
||||
|
||||
![函数的渐近上界](time_complexity.assets/asymptotic_upper_bound.png)
|
||||
![函数的渐近上界](time_complexity.assets/asymptotic_upper_bound.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-8 函数的渐近上界 </p>
|
||||
|
||||
|
@ -950,7 +950,7 @@ O(1) < O(\log n) < O(n) < O(n \log n) < O(n^2) < O(2^n) < O(n!) \newline
|
|||
\end{aligned}
|
||||
$$
|
||||
|
||||
![常见的时间复杂度类型](time_complexity.assets/time_complexity_common_types.png)
|
||||
![常见的时间复杂度类型](time_complexity.assets/time_complexity_common_types.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-9 常见的时间复杂度类型 </p>
|
||||
|
||||
|
@ -1642,7 +1642,7 @@ $$
|
|||
|
||||
图 2-10 对比了常数阶、线性阶和平方阶三种时间复杂度。
|
||||
|
||||
![常数阶、线性阶和平方阶的时间复杂度](time_complexity.assets/time_complexity_constant_linear_quadratic.png)
|
||||
![常数阶、线性阶和平方阶的时间复杂度](time_complexity.assets/time_complexity_constant_linear_quadratic.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-10 常数阶、线性阶和平方阶的时间复杂度 </p>
|
||||
|
||||
|
@ -2148,7 +2148,7 @@ $$
|
|||
}
|
||||
```
|
||||
|
||||
![指数阶的时间复杂度](time_complexity.assets/time_complexity_exponential.png)
|
||||
![指数阶的时间复杂度](time_complexity.assets/time_complexity_exponential.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-11 指数阶的时间复杂度 </p>
|
||||
|
||||
|
@ -2460,7 +2460,7 @@ $$
|
|||
}
|
||||
```
|
||||
|
||||
![对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic.png)
|
||||
![对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-12 对数阶的时间复杂度 </p>
|
||||
|
||||
|
@ -2790,7 +2790,7 @@ $$
|
|||
|
||||
图 2-13 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。
|
||||
|
||||
![线性对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic_linear.png)
|
||||
![线性对数阶的时间复杂度](time_complexity.assets/time_complexity_logarithmic_linear.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-13 线性对数阶的时间复杂度 </p>
|
||||
|
||||
|
@ -2994,7 +2994,7 @@ $$
|
|||
}
|
||||
```
|
||||
|
||||
![阶乘阶的时间复杂度](time_complexity.assets/time_complexity_factorial.png)
|
||||
![阶乘阶的时间复杂度](time_complexity.assets/time_complexity_factorial.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 2-14 阶乘阶的时间复杂度 </p>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ comments: true
|
|||
|
||||
「ASCII 码」是最早出现的字符集,全称为“美国标准信息交换代码”。它使用 7 位二进制数(即一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如图 3-6 所示,ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号,以及一些控制字符(如换行符和制表符)。
|
||||
|
||||
![ASCII 码](character_encoding.assets/ascii_table.png)
|
||||
![ASCII 码](character_encoding.assets/ascii_table.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-6 ASCII 码 </p>
|
||||
|
||||
|
@ -38,7 +38,7 @@ Unicode 是一种字符集标准,本质上是给每个字符分配一个编号
|
|||
|
||||
对于以上问题,**一种直接的解决方案是将所有字符存储为等长的编码**。如图 3-7 所示,“Hello”中的每个字符占用 1 字节,“算法”中的每个字符占用 2 字节。我们可以通过高位填 0 ,将“Hello 算法”中的所有字符都编码为 2 字节长度。这样系统就可以每隔 2 字节解析一个字符,恢复出这个短语的内容了。
|
||||
|
||||
![Unicode 编码示例](character_encoding.assets/unicode_hello_algo.png)
|
||||
![Unicode 编码示例](character_encoding.assets/unicode_hello_algo.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-7 Unicode 编码示例 </p>
|
||||
|
||||
|
@ -59,7 +59,7 @@ UTF-8 的编码规则并不复杂,分为以下两种情况。
|
|||
|
||||
之所以将 $10$ 当作校验符,是因为在 UTF-8 编码规则下,不可能有字符的最高两位是 $10$ 。这个结论可以用反证法来证明:假设一个字符的最高两位是 $10$ ,说明该字符的长度为 $1$ ,对应 ASCII 码。而 ASCII 码的最高位应该是 $0$ ,与假设矛盾。
|
||||
|
||||
![UTF-8 编码示例](character_encoding.assets/utf-8_hello_algo.png)
|
||||
![UTF-8 编码示例](character_encoding.assets/utf-8_hello_algo.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-8 UTF-8 编码示例 </p>
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ comments: true
|
|||
- **线性数据结构**:数组、链表、栈、队列、哈希表。
|
||||
- **非线性数据结构**:树、堆、图、哈希表。
|
||||
|
||||
![线性与非线性数据结构](classification_of_data_structure.assets/classification_logic_structure.png)
|
||||
![线性与非线性数据结构](classification_of_data_structure.assets/classification_logic_structure.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-1 线性与非线性数据结构 </p>
|
||||
|
||||
|
@ -33,7 +33,7 @@ comments: true
|
|||
|
||||
**系统通过内存地址来访问目标位置的数据**。如图 3-2 所示,计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。
|
||||
|
||||
![内存条、内存空间、内存地址](classification_of_data_structure.assets/computer_memory_location.png)
|
||||
![内存条、内存空间、内存地址](classification_of_data_structure.assets/computer_memory_location.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-2 内存条、内存空间、内存地址 </p>
|
||||
|
||||
|
@ -41,7 +41,7 @@ comments: true
|
|||
|
||||
如图 3-3 所示,**物理结构反映了数据在计算机内存中的存储方式**,可分为连续空间存储(数组)和分散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,同时在时间效率和空间效率方面呈现出互补的特点。
|
||||
|
||||
![连续空间存储与分散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png)
|
||||
![连续空间存储与分散空间存储](classification_of_data_structure.assets/classification_phisical_structure.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-3 连续空间存储与分散空间存储 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/shape-outline
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![数据结构](../assets/covers/chapter_data_structure.jpg){ width="600" }
|
||||
![数据结构](../assets/covers/chapter_data_structure.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ comments: true
|
|||
|
||||
图 3-4 展示了原码、反码和补码之间的转换方法。
|
||||
|
||||
![原码、反码与补码之间的相互转换](number_encoding.assets/1s_2s_complement.png)
|
||||
![原码、反码与补码之间的相互转换](number_encoding.assets/1s_2s_complement.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-4 原码、反码与补码之间的相互转换 </p>
|
||||
|
||||
|
@ -129,7 +129,7 @@ $$
|
|||
\end{aligned}
|
||||
$$
|
||||
|
||||
![IEEE 754 标准下的 float 的计算示例](number_encoding.assets/ieee_754_float.png)
|
||||
![IEEE 754 标准下的 float 的计算示例](number_encoding.assets/ieee_754_float.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 3-5 IEEE 754 标准下的 float 的计算示例 </p>
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ comments: true
|
|||
|
||||
图 12-4 展示了在数组中二分查找元素 $6$ 的分治过程。
|
||||
|
||||
![二分查找的分治过程](binary_search_recur.assets/binary_search_recur.png)
|
||||
![二分查找的分治过程](binary_search_recur.assets/binary_search_recur.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-4 二分查找的分治过程 </p>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
给定一个二叉树的前序遍历 `preorder` 和中序遍历 `inorder` ,请从中构建二叉树,返回二叉树的根节点。假设二叉树中没有值重复的节点。
|
||||
|
||||
![构建二叉树的示例数据](build_binary_tree_problem.assets/build_tree_example.png)
|
||||
![构建二叉树的示例数据](build_binary_tree_problem.assets/build_tree_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-5 构建二叉树的示例数据 </p>
|
||||
|
||||
|
@ -35,7 +35,7 @@ comments: true
|
|||
2. 查找根节点 3 在 `inorder` 中的索引,利用该索引可将 `inorder` 划分为 `[ 9 | 3 | 1 2 7 ]` 。
|
||||
3. 根据 `inorder` 划分结果,易得左子树和右子树的节点数量分别为 1 和 3 ,从而可将 `preorder` 划分为 `[ 3 | 9 | 2 1 7 ]` 。
|
||||
|
||||
![在前序和中序遍历中划分子树](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png)
|
||||
![在前序和中序遍历中划分子树](build_binary_tree_problem.assets/build_tree_preorder_inorder_division.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-6 在前序和中序遍历中划分子树 </p>
|
||||
|
||||
|
@ -63,7 +63,7 @@ comments: true
|
|||
|
||||
请注意,右子树根节点索引中的 $(m-l)$ 的含义是“左子树的节点数量”,建议配合图 12-7 理解。
|
||||
|
||||
![根节点和左右子树的索引区间表示](build_binary_tree_problem.assets/build_tree_division_pointers.png)
|
||||
![根节点和左右子树的索引区间表示](build_binary_tree_problem.assets/build_tree_division_pointers.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-7 根节点和左右子树的索引区间表示 </p>
|
||||
|
||||
|
@ -448,37 +448,37 @@ comments: true
|
|||
图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(即引用)是在向上“归”的过程中建立的。
|
||||
|
||||
=== "<1>"
|
||||
![构建二叉树的递归过程](build_binary_tree_problem.assets/built_tree_step1.png)
|
||||
![构建二叉树的递归过程](build_binary_tree_problem.assets/built_tree_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png)
|
||||
![built_tree_step2](build_binary_tree_problem.assets/built_tree_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png)
|
||||
![built_tree_step3](build_binary_tree_problem.assets/built_tree_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png)
|
||||
![built_tree_step4](build_binary_tree_problem.assets/built_tree_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png)
|
||||
![built_tree_step5](build_binary_tree_problem.assets/built_tree_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png)
|
||||
![built_tree_step6](build_binary_tree_problem.assets/built_tree_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png)
|
||||
![built_tree_step7](build_binary_tree_problem.assets/built_tree_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png)
|
||||
![built_tree_step8](build_binary_tree_problem.assets/built_tree_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png)
|
||||
![built_tree_step9](build_binary_tree_problem.assets/built_tree_step9.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-8 构建二叉树的递归过程 </p>
|
||||
|
||||
每个递归函数内的前序遍历 `preorder` 和中序遍历 `inorder` 的划分结果如图 12-9 所示。
|
||||
|
||||
![每个递归函数中的划分结果](build_binary_tree_problem.assets/built_tree_overall.png)
|
||||
![每个递归函数中的划分结果](build_binary_tree_problem.assets/built_tree_overall.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-9 每个递归函数中的划分结果 </p>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
1. **分**:递归地将原数组(原问题)划分为两个子数组(子问题),直到子数组只剩一个元素(最小子问题)。
|
||||
2. **治**:从底至顶地将有序的子数组(子问题的解)进行合并,从而得到有序的原数组(原问题的解)。
|
||||
|
||||
![归并排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png)
|
||||
![归并排序的分治策略](divide_and_conquer.assets/divide_and_conquer_merge_sort.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-1 归并排序的分治策略 </p>
|
||||
|
||||
|
@ -46,7 +46,7 @@ $$
|
|||
O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n)
|
||||
$$
|
||||
|
||||
![划分数组前后的冒泡排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png)
|
||||
![划分数组前后的冒泡排序](divide_and_conquer.assets/divide_and_conquer_bubble_sort.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-2 划分数组前后的冒泡排序 </p>
|
||||
|
||||
|
@ -74,7 +74,7 @@ $$
|
|||
|
||||
比如在图 12-3 所示的“桶排序”中,我们将海量的数据平均分配到各个桶中,则可所有桶的排序任务分散到各个计算单元,完成后再进行结果合并。
|
||||
|
||||
![桶排序的并行计算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png)
|
||||
![桶排序的并行计算](divide_and_conquer.assets/divide_and_conquer_parallel_computing.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-3 桶排序的并行计算 </p>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
2. 每次只能移动一个圆盘。
|
||||
3. 小圆盘必须时刻位于大圆盘之上。
|
||||
|
||||
![汉诺塔问题示例](hanota_problem.assets/hanota_example.png)
|
||||
![汉诺塔问题示例](hanota_problem.assets/hanota_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-10 汉诺塔问题示例 </p>
|
||||
|
||||
|
@ -25,10 +25,10 @@ comments: true
|
|||
如图 12-11 所示,对于问题 $f(1)$ ,即当只有一个圆盘时,我们将它直接从 `A` 移动至 `C` 即可。
|
||||
|
||||
=== "<1>"
|
||||
![规模为 1 问题的解](hanota_problem.assets/hanota_f1_step1.png)
|
||||
![规模为 1 问题的解](hanota_problem.assets/hanota_f1_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png)
|
||||
![hanota_f1_step2](hanota_problem.assets/hanota_f1_step2.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-11 规模为 1 问题的解 </p>
|
||||
|
||||
|
@ -39,16 +39,16 @@ comments: true
|
|||
3. 最后将小圆盘从 `B` 移至 `C` 。
|
||||
|
||||
=== "<1>"
|
||||
![规模为 2 问题的解](hanota_problem.assets/hanota_f2_step1.png)
|
||||
![规模为 2 问题的解](hanota_problem.assets/hanota_f2_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png)
|
||||
![hanota_f2_step2](hanota_problem.assets/hanota_f2_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png)
|
||||
![hanota_f2_step3](hanota_problem.assets/hanota_f2_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png)
|
||||
![hanota_f2_step4](hanota_problem.assets/hanota_f2_step4.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-12 规模为 2 问题的解 </p>
|
||||
|
||||
|
@ -65,16 +65,16 @@ comments: true
|
|||
3. 令 `C` 为目标柱、`A` 为缓冲柱,将两个圆盘从 `B` 移动至 `C` 。
|
||||
|
||||
=== "<1>"
|
||||
![规模为 3 问题的解](hanota_problem.assets/hanota_f3_step1.png)
|
||||
![规模为 3 问题的解](hanota_problem.assets/hanota_f3_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png)
|
||||
![hanota_f3_step2](hanota_problem.assets/hanota_f3_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png)
|
||||
![hanota_f3_step3](hanota_problem.assets/hanota_f3_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png)
|
||||
![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-13 规模为 3 问题的解 </p>
|
||||
|
||||
|
@ -88,7 +88,7 @@ comments: true
|
|||
|
||||
对于这两个子问题 $f(n-1)$ ,**可以通过相同的方式进行递归划分**,直至达到最小子问题 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移动操作即可。
|
||||
|
||||
![汉诺塔问题的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png)
|
||||
![汉诺塔问题的分治策略](hanota_problem.assets/hanota_divide_and_conquer.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-14 汉诺塔问题的分治策略 </p>
|
||||
|
||||
|
@ -485,7 +485,7 @@ comments: true
|
|||
|
||||
如图 12-15 所示,汉诺塔问题形成一个高度为 $n$ 的递归树,每个节点代表一个子问题、对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。
|
||||
|
||||
![汉诺塔问题的递归树](hanota_problem.assets/hanota_recursive_tree.png)
|
||||
![汉诺塔问题的递归树](hanota_problem.assets/hanota_recursive_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 12-15 汉诺塔问题的递归树 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/set-split
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![分治](../assets/covers/chapter_divide_and_conquer.jpg){ width="600" }
|
||||
![分治](../assets/covers/chapter_divide_and_conquer.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ comments: true
|
|||
|
||||
如图 14-6 所示,若第 $1$、$2$、$3$ 阶的代价分别为 $1$、$10$、$1$ ,则从地面爬到第 $3$ 阶的最小代价为 $2$ 。
|
||||
|
||||
![爬到第 3 阶的最小代价](dp_problem_features.assets/min_cost_cs_example.png)
|
||||
![爬到第 3 阶的最小代价](dp_problem_features.assets/min_cost_cs_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-6 爬到第 3 阶的最小代价 </p>
|
||||
|
||||
|
@ -300,7 +300,7 @@ $$
|
|||
|
||||
图 14-7 展示了以上代码的动态规划过程。
|
||||
|
||||
![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png)
|
||||
![爬楼梯最小代价的动态规划过程](dp_problem_features.assets/min_cost_cs_dp.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-7 爬楼梯最小代价的动态规划过程 </p>
|
||||
|
||||
|
@ -545,7 +545,7 @@ $$
|
|||
|
||||
例如图 14-8 ,爬上第 $3$ 阶仅剩 $2$ 种可行方案,其中连续三次跳 $1$ 阶的方案不满足约束条件,因此被舍弃。
|
||||
|
||||
![带约束爬到第 3 阶的方案数量](dp_problem_features.assets/climbing_stairs_constraint_example.png)
|
||||
![带约束爬到第 3 阶的方案数量](dp_problem_features.assets/climbing_stairs_constraint_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-8 带约束爬到第 3 阶的方案数量 </p>
|
||||
|
||||
|
@ -567,7 +567,7 @@ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2]
|
|||
\end{cases}
|
||||
$$
|
||||
|
||||
![考虑约束下的递推关系](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png)
|
||||
![考虑约束下的递推关系](dp_problem_features.assets/climbing_stairs_constraint_state_transfer.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-9 考虑约束下的递推关系 </p>
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ comments: true
|
|||
|
||||
图 14-10 展示了一个例子,给定网格的最小路径和为 $13$ 。
|
||||
|
||||
![最小路径和示例数据](dp_solution_pipeline.assets/min_path_sum_example.png)
|
||||
![最小路径和示例数据](dp_solution_pipeline.assets/min_path_sum_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-10 最小路径和示例数据 </p>
|
||||
|
||||
|
@ -53,7 +53,7 @@ comments: true
|
|||
|
||||
至此,我们就得到了图 14-11 所示的二维 $dp$ 矩阵,其尺寸与输入网格 $grid$ 相同。
|
||||
|
||||
![状态定义与 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_step1.png)
|
||||
![状态定义与 dp 表](dp_solution_pipeline.assets/min_path_sum_solution_step1.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-11 状态定义与 dp 表 </p>
|
||||
|
||||
|
@ -73,7 +73,7 @@ $$
|
|||
dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j]
|
||||
$$
|
||||
|
||||
![最优子结构与状态转移方程](dp_solution_pipeline.assets/min_path_sum_solution_step2.png)
|
||||
![最优子结构与状态转移方程](dp_solution_pipeline.assets/min_path_sum_solution_step2.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-12 最优子结构与状态转移方程 </p>
|
||||
|
||||
|
@ -89,7 +89,7 @@ $$
|
|||
|
||||
如图 14-13 所示,由于每个格子是由其左方格子和上方格子转移而来,因此我们使用采用循环来遍历矩阵,外循环遍历各行、内循环遍历各列。
|
||||
|
||||
![边界条件与状态转移顺序](dp_solution_pipeline.assets/min_path_sum_solution_step3.png)
|
||||
![边界条件与状态转移顺序](dp_solution_pipeline.assets/min_path_sum_solution_step3.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-13 边界条件与状态转移顺序 </p>
|
||||
|
||||
|
@ -368,7 +368,7 @@ $$
|
|||
|
||||
本质上看,造成重叠子问题的原因为:**存在多条路径可以从左上角到达某一单元格**。
|
||||
|
||||
![暴力搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs.png)
|
||||
![暴力搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-14 暴力搜索递归树 </p>
|
||||
|
||||
|
@ -697,7 +697,7 @@ $$
|
|||
|
||||
如图 14-15 所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。
|
||||
|
||||
![记忆化搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png)
|
||||
![记忆化搜索递归树](dp_solution_pipeline.assets/min_path_sum_dfs_mem.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-15 记忆化搜索递归树 </p>
|
||||
|
||||
|
@ -1039,40 +1039,40 @@ $$
|
|||
数组 `dp` 大小为 $n \times m$ ,**因此空间复杂度为 $O(nm)$** 。
|
||||
|
||||
=== "<1>"
|
||||
![最小路径和的动态规划过程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png)
|
||||
![最小路径和的动态规划过程](dp_solution_pipeline.assets/min_path_sum_dp_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png)
|
||||
![min_path_sum_dp_step2](dp_solution_pipeline.assets/min_path_sum_dp_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png)
|
||||
![min_path_sum_dp_step3](dp_solution_pipeline.assets/min_path_sum_dp_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png)
|
||||
![min_path_sum_dp_step4](dp_solution_pipeline.assets/min_path_sum_dp_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png)
|
||||
![min_path_sum_dp_step5](dp_solution_pipeline.assets/min_path_sum_dp_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png)
|
||||
![min_path_sum_dp_step6](dp_solution_pipeline.assets/min_path_sum_dp_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png)
|
||||
![min_path_sum_dp_step7](dp_solution_pipeline.assets/min_path_sum_dp_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png)
|
||||
![min_path_sum_dp_step8](dp_solution_pipeline.assets/min_path_sum_dp_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png)
|
||||
![min_path_sum_dp_step9](dp_solution_pipeline.assets/min_path_sum_dp_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png)
|
||||
![min_path_sum_dp_step10](dp_solution_pipeline.assets/min_path_sum_dp_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png)
|
||||
![min_path_sum_dp_step11](dp_solution_pipeline.assets/min_path_sum_dp_step11.png){ class="animation-figure" }
|
||||
|
||||
=== "<12>"
|
||||
![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png)
|
||||
![min_path_sum_dp_step12](dp_solution_pipeline.assets/min_path_sum_dp_step12.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-16 最小路径和的动态规划过程 </p>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
|
||||
如图 14-27 所示,将 `kitten` 转换为 `sitting` 需要编辑 3 步,包括 2 次替换操作与 1 次添加操作;将 `hello` 转换为 `algo` 需要 3 步,包括 2 次替换操作和 1 次删除操作。
|
||||
|
||||
![编辑距离的示例数据](edit_distance_problem.assets/edit_distance_example.png)
|
||||
![编辑距离的示例数据](edit_distance_problem.assets/edit_distance_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-27 编辑距离的示例数据 </p>
|
||||
|
||||
|
@ -24,7 +24,7 @@ comments: true
|
|||
|
||||
从决策树的角度看,本题的目标是求解节点 `hello` 和节点 `algo` 之间的最短路径。
|
||||
|
||||
![基于决策树模型表示编辑距离问题](edit_distance_problem.assets/edit_distance_decision_tree.png)
|
||||
![基于决策树模型表示编辑距离问题](edit_distance_problem.assets/edit_distance_decision_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-28 基于决策树模型表示编辑距离问题 </p>
|
||||
|
||||
|
@ -53,7 +53,7 @@ comments: true
|
|||
2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。
|
||||
3. 将 $s[i-1]$ 替换为 $t[j-1]$ ,则剩余子问题 $dp[i-1, j-1]$ 。
|
||||
|
||||
![编辑距离的状态转移](edit_distance_problem.assets/edit_distance_state_transfer.png)
|
||||
![编辑距离的状态转移](edit_distance_problem.assets/edit_distance_state_transfer.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-29 编辑距离的状态转移 </p>
|
||||
|
||||
|
@ -446,49 +446,49 @@ $$
|
|||
如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作是填写一个二维网格的过程。
|
||||
|
||||
=== "<1>"
|
||||
![编辑距离的动态规划过程](edit_distance_problem.assets/edit_distance_dp_step1.png)
|
||||
![编辑距离的动态规划过程](edit_distance_problem.assets/edit_distance_dp_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png)
|
||||
![edit_distance_dp_step2](edit_distance_problem.assets/edit_distance_dp_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png)
|
||||
![edit_distance_dp_step3](edit_distance_problem.assets/edit_distance_dp_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png)
|
||||
![edit_distance_dp_step4](edit_distance_problem.assets/edit_distance_dp_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png)
|
||||
![edit_distance_dp_step5](edit_distance_problem.assets/edit_distance_dp_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png)
|
||||
![edit_distance_dp_step6](edit_distance_problem.assets/edit_distance_dp_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png)
|
||||
![edit_distance_dp_step7](edit_distance_problem.assets/edit_distance_dp_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png)
|
||||
![edit_distance_dp_step8](edit_distance_problem.assets/edit_distance_dp_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png)
|
||||
![edit_distance_dp_step9](edit_distance_problem.assets/edit_distance_dp_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png)
|
||||
![edit_distance_dp_step10](edit_distance_problem.assets/edit_distance_dp_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png)
|
||||
![edit_distance_dp_step11](edit_distance_problem.assets/edit_distance_dp_step11.png){ class="animation-figure" }
|
||||
|
||||
=== "<12>"
|
||||
![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png)
|
||||
![edit_distance_dp_step12](edit_distance_problem.assets/edit_distance_dp_step12.png){ class="animation-figure" }
|
||||
|
||||
=== "<13>"
|
||||
![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png)
|
||||
![edit_distance_dp_step13](edit_distance_problem.assets/edit_distance_dp_step13.png){ class="animation-figure" }
|
||||
|
||||
=== "<14>"
|
||||
![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png)
|
||||
![edit_distance_dp_step14](edit_distance_problem.assets/edit_distance_dp_step14.png){ class="animation-figure" }
|
||||
|
||||
=== "<15>"
|
||||
![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png)
|
||||
![edit_distance_dp_step15](edit_distance_problem.assets/edit_distance_dp_step15.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-30 编辑距离的动态规划过程 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/table-pivot
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![动态规划](../assets/covers/chapter_dynamic_programming.jpg){ width="600" }
|
||||
![动态规划](../assets/covers/chapter_dynamic_programming.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
|
||||
如图 14-1 所示,对于一个 $3$ 阶楼梯,共有 $3$ 种方案可以爬到楼顶。
|
||||
|
||||
![爬到第 3 阶的方案数量](intro_to_dynamic_programming.assets/climbing_stairs_example.png)
|
||||
![爬到第 3 阶的方案数量](intro_to_dynamic_programming.assets/climbing_stairs_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-1 爬到第 3 阶的方案数量 </p>
|
||||
|
||||
|
@ -405,7 +405,7 @@ $$
|
|||
|
||||
这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。图 14-2 展示了该递推关系。
|
||||
|
||||
![方案数量递推关系](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png)
|
||||
![方案数量递推关系](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-2 方案数量递推关系 </p>
|
||||
|
||||
|
@ -640,7 +640,7 @@ $$
|
|||
|
||||
图 14-3 展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。
|
||||
|
||||
![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png)
|
||||
![爬楼梯对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-3 爬楼梯对应递归树 </p>
|
||||
|
||||
|
@ -975,7 +975,7 @@ $$
|
|||
|
||||
观察图 14-4 ,**经过记忆化处理后,所有重叠子问题都只需被计算一次,时间复杂度被优化至 $O(n)$** ,这是一个巨大的飞跃。
|
||||
|
||||
![记忆化搜索对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png)
|
||||
![记忆化搜索对应递归树](intro_to_dynamic_programming.assets/climbing_stairs_dfs_memo_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-4 记忆化搜索对应递归树 </p>
|
||||
|
||||
|
@ -1229,7 +1229,7 @@ $$
|
|||
|
||||
图 14-5 模拟了以上代码的执行过程。
|
||||
|
||||
![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png)
|
||||
![爬楼梯的动态规划过程](intro_to_dynamic_programming.assets/climbing_stairs_dp.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-5 爬楼梯的动态规划过程 </p>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
|
||||
观察图 14-17 ,由于物品编号 $i$ 从 $1$ 开始计数,数组索引从 $0$ 开始计数,因此物品 $i$ 对应重量 $wgt[i-1]$ 和价值 $val[i-1]$ 。
|
||||
|
||||
![0-1 背包的示例数据](knapsack_problem.assets/knapsack_example.png)
|
||||
![0-1 背包的示例数据](knapsack_problem.assets/knapsack_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-17 0-1 背包的示例数据 </p>
|
||||
|
||||
|
@ -320,7 +320,7 @@ $$
|
|||
|
||||
观察递归树,容易发现其中存在重叠子问题,例如 $dp[1, 10]$ 等。而当物品较多、背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅增多。
|
||||
|
||||
![0-1 背包的暴力搜索递归树](knapsack_problem.assets/knapsack_dfs.png)
|
||||
![0-1 背包的暴力搜索递归树](knapsack_problem.assets/knapsack_dfs.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-18 0-1 背包的暴力搜索递归树 </p>
|
||||
|
||||
|
@ -656,7 +656,7 @@ $$
|
|||
|
||||
图 14-19 展示了在记忆化递归中被剪掉的搜索分支。
|
||||
|
||||
![0-1 背包的记忆化搜索递归树](knapsack_problem.assets/knapsack_dfs_mem.png)
|
||||
![0-1 背包的记忆化搜索递归树](knapsack_problem.assets/knapsack_dfs_mem.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-19 0-1 背包的记忆化搜索递归树 </p>
|
||||
|
||||
|
@ -969,46 +969,46 @@ $$
|
|||
如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。
|
||||
|
||||
=== "<1>"
|
||||
![0-1 背包的动态规划过程](knapsack_problem.assets/knapsack_dp_step1.png)
|
||||
![0-1 背包的动态规划过程](knapsack_problem.assets/knapsack_dp_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png)
|
||||
![knapsack_dp_step2](knapsack_problem.assets/knapsack_dp_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png)
|
||||
![knapsack_dp_step3](knapsack_problem.assets/knapsack_dp_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png)
|
||||
![knapsack_dp_step4](knapsack_problem.assets/knapsack_dp_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png)
|
||||
![knapsack_dp_step5](knapsack_problem.assets/knapsack_dp_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png)
|
||||
![knapsack_dp_step6](knapsack_problem.assets/knapsack_dp_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png)
|
||||
![knapsack_dp_step7](knapsack_problem.assets/knapsack_dp_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png)
|
||||
![knapsack_dp_step8](knapsack_problem.assets/knapsack_dp_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png)
|
||||
![knapsack_dp_step9](knapsack_problem.assets/knapsack_dp_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png)
|
||||
![knapsack_dp_step10](knapsack_problem.assets/knapsack_dp_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png)
|
||||
![knapsack_dp_step11](knapsack_problem.assets/knapsack_dp_step11.png){ class="animation-figure" }
|
||||
|
||||
=== "<12>"
|
||||
![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png)
|
||||
![knapsack_dp_step12](knapsack_problem.assets/knapsack_dp_step12.png){ class="animation-figure" }
|
||||
|
||||
=== "<13>"
|
||||
![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png)
|
||||
![knapsack_dp_step13](knapsack_problem.assets/knapsack_dp_step13.png){ class="animation-figure" }
|
||||
|
||||
=== "<14>"
|
||||
![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png)
|
||||
![knapsack_dp_step14](knapsack_problem.assets/knapsack_dp_step14.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-20 0-1 背包的动态规划过程 </p>
|
||||
|
||||
|
@ -1024,22 +1024,22 @@ $$
|
|||
图 14-21 展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。
|
||||
|
||||
=== "<1>"
|
||||
![0-1 背包的空间优化后的动态规划过程](knapsack_problem.assets/knapsack_dp_comp_step1.png)
|
||||
![0-1 背包的空间优化后的动态规划过程](knapsack_problem.assets/knapsack_dp_comp_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png)
|
||||
![knapsack_dp_comp_step2](knapsack_problem.assets/knapsack_dp_comp_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png)
|
||||
![knapsack_dp_comp_step3](knapsack_problem.assets/knapsack_dp_comp_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png)
|
||||
![knapsack_dp_comp_step4](knapsack_problem.assets/knapsack_dp_comp_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png)
|
||||
![knapsack_dp_comp_step5](knapsack_problem.assets/knapsack_dp_comp_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png)
|
||||
![knapsack_dp_comp_step6](knapsack_problem.assets/knapsack_dp_comp_step6.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-21 0-1 背包的空间优化后的动态规划过程 </p>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ comments: true
|
|||
|
||||
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。**每个物品可以重复选取**,问在不超过背包容量下能放入物品的最大价值。
|
||||
|
||||
![完全背包问题的示例数据](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png)
|
||||
![完全背包问题的示例数据](unbounded_knapsack_problem.assets/unbounded_knapsack_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-22 完全背包问题的示例数据 </p>
|
||||
|
||||
|
@ -347,22 +347,22 @@ $$
|
|||
这个遍历顺序与 0-1 背包正好相反。请借助图 14-23 来理解两者的区别。
|
||||
|
||||
=== "<1>"
|
||||
![完全背包的空间优化后的动态规划过程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png)
|
||||
![完全背包的空间优化后的动态规划过程](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png)
|
||||
![unbounded_knapsack_dp_comp_step2](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png)
|
||||
![unbounded_knapsack_dp_comp_step3](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png)
|
||||
![unbounded_knapsack_dp_comp_step4](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png)
|
||||
![unbounded_knapsack_dp_comp_step5](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png)
|
||||
![unbounded_knapsack_dp_comp_step6](unbounded_knapsack_problem.assets/unbounded_knapsack_dp_comp_step6.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-23 完全背包的空间优化后的动态规划过程 </p>
|
||||
|
||||
|
@ -666,7 +666,7 @@ $$
|
|||
|
||||
给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,**每种硬币可以重复选取**,问能够凑出目标金额的最少硬币个数。如果无法凑出目标金额则返回 $-1$ 。
|
||||
|
||||
![零钱兑换问题的示例数据](unbounded_knapsack_problem.assets/coin_change_example.png)
|
||||
![零钱兑换问题的示例数据](unbounded_knapsack_problem.assets/coin_change_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-24 零钱兑换问题的示例数据 </p>
|
||||
|
||||
|
@ -1070,49 +1070,49 @@ $$
|
|||
图 14-25 展示了零钱兑换的动态规划过程,和完全背包非常相似。
|
||||
|
||||
=== "<1>"
|
||||
![零钱兑换问题的动态规划过程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png)
|
||||
![零钱兑换问题的动态规划过程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png)
|
||||
![coin_change_dp_step2](unbounded_knapsack_problem.assets/coin_change_dp_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png)
|
||||
![coin_change_dp_step3](unbounded_knapsack_problem.assets/coin_change_dp_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png)
|
||||
![coin_change_dp_step4](unbounded_knapsack_problem.assets/coin_change_dp_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png)
|
||||
![coin_change_dp_step5](unbounded_knapsack_problem.assets/coin_change_dp_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png)
|
||||
![coin_change_dp_step6](unbounded_knapsack_problem.assets/coin_change_dp_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png)
|
||||
![coin_change_dp_step7](unbounded_knapsack_problem.assets/coin_change_dp_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png)
|
||||
![coin_change_dp_step8](unbounded_knapsack_problem.assets/coin_change_dp_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png)
|
||||
![coin_change_dp_step9](unbounded_knapsack_problem.assets/coin_change_dp_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png)
|
||||
![coin_change_dp_step10](unbounded_knapsack_problem.assets/coin_change_dp_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png)
|
||||
![coin_change_dp_step11](unbounded_knapsack_problem.assets/coin_change_dp_step11.png){ class="animation-figure" }
|
||||
|
||||
=== "<12>"
|
||||
![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png)
|
||||
![coin_change_dp_step12](unbounded_knapsack_problem.assets/coin_change_dp_step12.png){ class="animation-figure" }
|
||||
|
||||
=== "<13>"
|
||||
![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png)
|
||||
![coin_change_dp_step13](unbounded_knapsack_problem.assets/coin_change_dp_step13.png){ class="animation-figure" }
|
||||
|
||||
=== "<14>"
|
||||
![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png)
|
||||
![coin_change_dp_step14](unbounded_knapsack_problem.assets/coin_change_dp_step14.png){ class="animation-figure" }
|
||||
|
||||
=== "<15>"
|
||||
![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png)
|
||||
![coin_change_dp_step15](unbounded_knapsack_problem.assets/coin_change_dp_step15.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-25 零钱兑换问题的动态规划过程 </p>
|
||||
|
||||
|
@ -1450,7 +1450,7 @@ $$
|
|||
|
||||
给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,每种硬币可以重复选取,**问在凑出目标金额的硬币组合数量**。
|
||||
|
||||
![零钱兑换问题 II 的示例数据](unbounded_knapsack_problem.assets/coin_change_ii_example.png)
|
||||
![零钱兑换问题 II 的示例数据](unbounded_knapsack_problem.assets/coin_change_ii_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 14-26 零钱兑换问题 II 的示例数据 </p>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ $$
|
|||
|
||||
如果将顶点看作节点,将边看作连接各个节点的引用(指针),我们就可以将图看作是一种从链表拓展而来的数据结构。如图 9-1 所示,**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高**,从而更为复杂。
|
||||
|
||||
![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png)
|
||||
![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-1 链表、树、图之间的关系 </p>
|
||||
|
||||
|
@ -27,7 +27,7 @@ $$
|
|||
- 在无向图中,边表示两顶点之间的“双向”连接关系,例如微信或 QQ 中的“好友关系”。
|
||||
- 在有向图中,边具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。
|
||||
|
||||
![有向图与无向图](graph.assets/directed_graph.png)
|
||||
![有向图与无向图](graph.assets/directed_graph.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-2 有向图与无向图 </p>
|
||||
|
||||
|
@ -36,13 +36,13 @@ $$
|
|||
- 对于连通图,从某个顶点出发,可以到达其余任意顶点。
|
||||
- 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。
|
||||
|
||||
![连通图与非连通图](graph.assets/connected_graph.png)
|
||||
![连通图与非连通图](graph.assets/connected_graph.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-3 连通图与非连通图 </p>
|
||||
|
||||
我们还可以为边添加“权重”变量,从而得到图 9-4 所示的「有权图 weighted graph」。例如在王者荣耀等手游中,系统会根据共同游戏时间来计算玩家之间的“亲密度”,这种亲密度网络就可以用有权图来表示。
|
||||
|
||||
![有权图与无权图](graph.assets/weighted_graph.png)
|
||||
![有权图与无权图](graph.assets/weighted_graph.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-4 有权图与无权图 </p>
|
||||
|
||||
|
@ -62,7 +62,7 @@ $$
|
|||
|
||||
如图 9-5 所示,设邻接矩阵为 $M$、顶点列表为 $V$ ,那么矩阵元素 $M[i, j] = 1$ 表示顶点 $V[i]$ 到顶点 $V[j]$ 之间存在边,反之 $M[i, j] = 0$ 表示两顶点之间无边。
|
||||
|
||||
![图的邻接矩阵表示](graph.assets/adjacency_matrix.png)
|
||||
![图的邻接矩阵表示](graph.assets/adjacency_matrix.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-5 图的邻接矩阵表示 </p>
|
||||
|
||||
|
@ -78,7 +78,7 @@ $$
|
|||
|
||||
「邻接表 adjacency list」使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。图 9-6 展示了一个使用邻接表存储的图的示例。
|
||||
|
||||
![图的邻接表表示](graph.assets/adjacency_list.png)
|
||||
![图的邻接表表示](graph.assets/adjacency_list.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-6 图的邻接表表示 </p>
|
||||
|
||||
|
|
|
@ -16,19 +16,19 @@ comments: true
|
|||
- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。
|
||||
|
||||
=== "初始化邻接矩阵"
|
||||
![邻接矩阵的初始化、增删边、增删顶点](graph_operations.assets/adjacency_matrix_initialization.png)
|
||||
![邻接矩阵的初始化、增删边、增删顶点](graph_operations.assets/adjacency_matrix_initialization.png){ class="animation-figure" }
|
||||
|
||||
=== "添加边"
|
||||
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_add_edge.png)
|
||||
![adjacency_matrix_add_edge](graph_operations.assets/adjacency_matrix_add_edge.png){ class="animation-figure" }
|
||||
|
||||
=== "删除边"
|
||||
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_remove_edge.png)
|
||||
![adjacency_matrix_remove_edge](graph_operations.assets/adjacency_matrix_remove_edge.png){ class="animation-figure" }
|
||||
|
||||
=== "添加顶点"
|
||||
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_add_vertex.png)
|
||||
![adjacency_matrix_add_vertex](graph_operations.assets/adjacency_matrix_add_vertex.png){ class="animation-figure" }
|
||||
|
||||
=== "删除顶点"
|
||||
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_remove_vertex.png)
|
||||
![adjacency_matrix_remove_vertex](graph_operations.assets/adjacency_matrix_remove_vertex.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-7 邻接矩阵的初始化、增删边、增删顶点 </p>
|
||||
|
||||
|
@ -1056,19 +1056,19 @@ comments: true
|
|||
- **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。
|
||||
|
||||
=== "初始化邻接表"
|
||||
![邻接表的初始化、增删边、增删顶点](graph_operations.assets/adjacency_list_initialization.png)
|
||||
![邻接表的初始化、增删边、增删顶点](graph_operations.assets/adjacency_list_initialization.png){ class="animation-figure" }
|
||||
|
||||
=== "添加边"
|
||||
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_add_edge.png)
|
||||
![adjacency_list_add_edge](graph_operations.assets/adjacency_list_add_edge.png){ class="animation-figure" }
|
||||
|
||||
=== "删除边"
|
||||
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_remove_edge.png)
|
||||
![adjacency_list_remove_edge](graph_operations.assets/adjacency_list_remove_edge.png){ class="animation-figure" }
|
||||
|
||||
=== "添加顶点"
|
||||
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_add_vertex.png)
|
||||
![adjacency_list_add_vertex](graph_operations.assets/adjacency_list_add_vertex.png){ class="animation-figure" }
|
||||
|
||||
=== "删除顶点"
|
||||
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_remove_vertex.png)
|
||||
![adjacency_list_remove_vertex](graph_operations.assets/adjacency_list_remove_vertex.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-8 邻接表的初始化、增删边、增删顶点 </p>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ comments: true
|
|||
|
||||
**广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张**。如图 9-9 所示,从左上角顶点出发,先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。
|
||||
|
||||
![图的广度优先遍历](graph_traversal.assets/graph_bfs.png)
|
||||
![图的广度优先遍历](graph_traversal.assets/graph_bfs.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-9 图的广度优先遍历 </p>
|
||||
|
||||
|
@ -421,37 +421,37 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这
|
|||
代码相对抽象,建议对照图 9-10 来加深理解。
|
||||
|
||||
=== "<1>"
|
||||
![图的广度优先遍历步骤](graph_traversal.assets/graph_bfs_step1.png)
|
||||
![图的广度优先遍历步骤](graph_traversal.assets/graph_bfs_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png)
|
||||
![graph_bfs_step2](graph_traversal.assets/graph_bfs_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png)
|
||||
![graph_bfs_step3](graph_traversal.assets/graph_bfs_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png)
|
||||
![graph_bfs_step4](graph_traversal.assets/graph_bfs_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png)
|
||||
![graph_bfs_step5](graph_traversal.assets/graph_bfs_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png)
|
||||
![graph_bfs_step6](graph_traversal.assets/graph_bfs_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png)
|
||||
![graph_bfs_step7](graph_traversal.assets/graph_bfs_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png)
|
||||
![graph_bfs_step8](graph_traversal.assets/graph_bfs_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png)
|
||||
![graph_bfs_step9](graph_traversal.assets/graph_bfs_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png)
|
||||
![graph_bfs_step10](graph_traversal.assets/graph_bfs_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png)
|
||||
![graph_bfs_step11](graph_traversal.assets/graph_bfs_step11.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-10 图的广度优先遍历步骤 </p>
|
||||
|
||||
|
@ -469,7 +469,7 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这
|
|||
|
||||
**深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。如图 9-11 所示,从左上角顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。
|
||||
|
||||
![图的深度优先遍历](graph_traversal.assets/graph_dfs.png)
|
||||
![图的深度优先遍历](graph_traversal.assets/graph_dfs.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-11 图的深度优先遍历 </p>
|
||||
|
||||
|
@ -829,37 +829,37 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这
|
|||
为了加深理解,建议将图示与代码结合起来,在脑中(或者用笔画下来)模拟整个 DFS 过程,包括每个递归方法何时开启、何时返回。
|
||||
|
||||
=== "<1>"
|
||||
![图的深度优先遍历步骤](graph_traversal.assets/graph_dfs_step1.png)
|
||||
![图的深度优先遍历步骤](graph_traversal.assets/graph_dfs_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png)
|
||||
![graph_dfs_step2](graph_traversal.assets/graph_dfs_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png)
|
||||
![graph_dfs_step3](graph_traversal.assets/graph_dfs_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png)
|
||||
![graph_dfs_step4](graph_traversal.assets/graph_dfs_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png)
|
||||
![graph_dfs_step5](graph_traversal.assets/graph_dfs_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png)
|
||||
![graph_dfs_step6](graph_traversal.assets/graph_dfs_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png)
|
||||
![graph_dfs_step7](graph_traversal.assets/graph_dfs_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png)
|
||||
![graph_dfs_step8](graph_traversal.assets/graph_dfs_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png)
|
||||
![graph_dfs_step9](graph_traversal.assets/graph_dfs_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png)
|
||||
![graph_dfs_step10](graph_traversal.assets/graph_dfs_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png)
|
||||
![graph_dfs_step11](graph_traversal.assets/graph_dfs_step11.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 9-12 图的深度优先遍历步骤 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/graphql
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![图](../assets/covers/chapter_graph.jpg){ width="600" }
|
||||
![图](../assets/covers/chapter_graph.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,**但可以选择物品的一部分,价值根据选择的重量比例计算**,问在不超过背包容量下背包中物品的最大价值。
|
||||
|
||||
![分数背包问题的示例数据](fractional_knapsack_problem.assets/fractional_knapsack_example.png)
|
||||
![分数背包问题的示例数据](fractional_knapsack_problem.assets/fractional_knapsack_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-3 分数背包问题的示例数据 </p>
|
||||
|
||||
|
@ -19,7 +19,7 @@ comments: true
|
|||
1. 对于物品 $i$ ,它在单位重量下的价值为 $val[i-1] / wgt[i-1]$ ,简称为单位价值。
|
||||
2. 假设放入一部分物品 $i$ ,重量为 $w$ ,则背包增加的价值为 $w \times val[i-1] / wgt[i-1]$ 。
|
||||
|
||||
![物品在单位重量下的价值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png)
|
||||
![物品在单位重量下的价值](fractional_knapsack_problem.assets/fractional_knapsack_unit_value.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-4 物品在单位重量下的价值 </p>
|
||||
|
||||
|
@ -31,7 +31,7 @@ comments: true
|
|||
2. 遍历所有物品,**每轮贪心地选择单位价值最高的物品**。
|
||||
3. 若剩余背包容量不足,则使用当前物品的一部分填满背包即可。
|
||||
|
||||
![分数背包的贪心策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png)
|
||||
![分数背包的贪心策略](fractional_knapsack_problem.assets/fractional_knapsack_greedy_strategy.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-5 分数背包的贪心策略 </p>
|
||||
|
||||
|
@ -483,6 +483,6 @@ comments: true
|
|||
|
||||
如图 15-6 所示,如果将物品重量和物品单位价值分别看作一个 2D 图表的横轴和纵轴,则分数背包问题可被转化为“求在有限横轴区间下的最大围成面积”。这个类比可以帮助我们从几何角度理解贪心策略的有效性。
|
||||
|
||||
![分数背包问题的几何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png)
|
||||
![分数背包问题的几何表示](fractional_knapsack_problem.assets/fractional_knapsack_area_chart.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-6 分数背包问题的几何表示 </p>
|
||||
|
|
|
@ -19,7 +19,7 @@ comments: true
|
|||
|
||||
本题的贪心策略如图 15-1 所示。给定目标金额,**我们贪心地选择不大于且最接近它的硬币**,不断循环该步骤,直至凑出目标金额为止。
|
||||
|
||||
![零钱兑换的贪心策略](greedy_algorithm.assets/coin_change_greedy_strategy.png)
|
||||
![零钱兑换的贪心策略](greedy_algorithm.assets/coin_change_greedy_strategy.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-1 零钱兑换的贪心策略 </p>
|
||||
|
||||
|
@ -299,7 +299,7 @@ comments: true
|
|||
- **反例 $coins = [1, 20, 50]$**:假设 $amt = 60$ ,贪心算法只能找到 $50 + 1 \times 10$ 的兑换组合,共计 $11$ 枚硬币,但动态规划可以找到最优解 $20 + 20 + 20$ ,仅需 $3$ 枚硬币。
|
||||
- **反例 $coins = [1, 49, 50]$**:假设 $amt = 98$ ,贪心算法只能找到 $50 + 1 \times 48$ 的兑换组合,共计 $49$ 枚硬币,但动态规划可以找到最优解 $49 + 49$ ,仅需 $2$ 枚硬币。
|
||||
|
||||
![贪心无法找出最优解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png)
|
||||
![贪心无法找出最优解的示例](greedy_algorithm.assets/coin_change_greedy_vs_dp.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-2 贪心无法找出最优解的示例 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/head-heart-outline
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![贪心](../assets/covers/chapter_greedy.jpg){ width="600" }
|
||||
![贪心](../assets/covers/chapter_greedy.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ comments: true
|
|||
|
||||
请在数组中选择两个隔板,使得组成的容器的容量最大,返回最大容量。
|
||||
|
||||
![最大容量问题的示例数据](max_capacity_problem.assets/max_capacity_example.png)
|
||||
![最大容量问题的示例数据](max_capacity_problem.assets/max_capacity_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-7 最大容量问题的示例数据 </p>
|
||||
|
||||
|
@ -30,7 +30,7 @@ $$
|
|||
|
||||
这道题还有更高效率的解法。如图 15-8 所示,现选取一个状态 $[i, j]$ ,其满足索引 $i < j$ 且高度 $ht[i] < ht[j]$ ,即 $i$ 为短板、$j$ 为长板。
|
||||
|
||||
![初始状态](max_capacity_problem.assets/max_capacity_initial_state.png)
|
||||
![初始状态](max_capacity_problem.assets/max_capacity_initial_state.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-8 初始状态 </p>
|
||||
|
||||
|
@ -38,13 +38,13 @@ $$
|
|||
|
||||
这是因为在移动长板 $j$ 后,宽度 $j-i$ 肯定变小;而高度由短板决定,因此高度只可能不变( $i$ 仍为短板)或变小(移动后的 $j$ 成为短板)。
|
||||
|
||||
![向内移动长板后的状态](max_capacity_problem.assets/max_capacity_moving_long_board.png)
|
||||
![向内移动长板后的状态](max_capacity_problem.assets/max_capacity_moving_long_board.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-9 向内移动长板后的状态 </p>
|
||||
|
||||
反向思考,**我们只有向内收缩短板 $i$ ,才有可能使容量变大**。因为虽然宽度一定变小,**但高度可能会变大**(移动后的短板 $i$ 可能会变长)。例如在图 15-10 中,移动短板后面积变大。
|
||||
|
||||
![向内移动短板后的状态](max_capacity_problem.assets/max_capacity_moving_short_board.png)
|
||||
![向内移动短板后的状态](max_capacity_problem.assets/max_capacity_moving_short_board.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-10 向内移动短板后的状态 </p>
|
||||
|
||||
|
@ -58,31 +58,31 @@ $$
|
|||
4. 循环执行第 `2.` 和 `3.` 步,直至 $i$ 和 $j$ 相遇时结束。
|
||||
|
||||
=== "<1>"
|
||||
![最大容量问题的贪心过程](max_capacity_problem.assets/max_capacity_greedy_step1.png)
|
||||
![最大容量问题的贪心过程](max_capacity_problem.assets/max_capacity_greedy_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png)
|
||||
![max_capacity_greedy_step2](max_capacity_problem.assets/max_capacity_greedy_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png)
|
||||
![max_capacity_greedy_step3](max_capacity_problem.assets/max_capacity_greedy_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png)
|
||||
![max_capacity_greedy_step4](max_capacity_problem.assets/max_capacity_greedy_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png)
|
||||
![max_capacity_greedy_step5](max_capacity_problem.assets/max_capacity_greedy_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png)
|
||||
![max_capacity_greedy_step6](max_capacity_problem.assets/max_capacity_greedy_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png)
|
||||
![max_capacity_greedy_step7](max_capacity_problem.assets/max_capacity_greedy_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png)
|
||||
![max_capacity_greedy_step8](max_capacity_problem.assets/max_capacity_greedy_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png)
|
||||
![max_capacity_greedy_step9](max_capacity_problem.assets/max_capacity_greedy_step9.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-11 最大容量问题的贪心过程 </p>
|
||||
|
||||
|
@ -384,7 +384,7 @@ $$
|
|||
cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1]
|
||||
$$
|
||||
|
||||
![移动短板导致被跳过的状态](max_capacity_problem.assets/max_capacity_skipped_states.png)
|
||||
![移动短板导致被跳过的状态](max_capacity_problem.assets/max_capacity_skipped_states.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-12 移动短板导致被跳过的状态 </p>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
给定一个正整数 $n$ ,将其切分为至少两个正整数的和,求切分后所有整数的乘积最大是多少。
|
||||
|
||||
![最大切分乘积的问题定义](max_product_cutting_problem.assets/max_product_cutting_definition.png)
|
||||
![最大切分乘积的问题定义](max_product_cutting_problem.assets/max_product_cutting_definition.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-13 最大切分乘积的问题定义 </p>
|
||||
|
||||
|
@ -42,7 +42,7 @@ $$
|
|||
|
||||
**贪心策略一**:如果切分方案中包含 $\geq 4$ 的因子,那么它就应该被继续切分。最终的切分方案只应出现 $1$、$2$、$3$ 这三种因子。
|
||||
|
||||
![切分导致乘积变大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png)
|
||||
![切分导致乘积变大](max_product_cutting_problem.assets/max_product_cutting_greedy_infer1.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-14 切分导致乘积变大 </p>
|
||||
|
||||
|
@ -52,7 +52,7 @@ $$
|
|||
|
||||
**贪心策略二**:在切分方案中,最多只应存在两个 $2$ 。因为三个 $2$ 总是可以被替换为两个 $3$ ,从而获得更大乘积。
|
||||
|
||||
![最优切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png)
|
||||
![最优切分因子](max_product_cutting_problem.assets/max_product_cutting_greedy_infer2.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-15 最优切分因子 </p>
|
||||
|
||||
|
@ -349,7 +349,7 @@ $$
|
|||
[class]{}-[func]{maxProductCutting}
|
||||
```
|
||||
|
||||
![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png)
|
||||
![最大切分乘积的计算方法](max_product_cutting_problem.assets/max_product_cutting_greedy_calculation.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 15-16 最大切分乘积的计算方法 </p>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如图 6-8 所示,对于链地址哈希表,理想情况下键值对平均分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都被存储到同一个桶中,时间复杂度退化至 $O(n)$ 。
|
||||
|
||||
![哈希冲突的最佳与最差情况](hash_algorithm.assets/hash_collision_best_worst_condition.png)
|
||||
![哈希冲突的最佳与最差情况](hash_algorithm.assets/hash_collision_best_worst_condition.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-8 哈希冲突的最佳与最差情况 </p>
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ comments: true
|
|||
|
||||
在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表,将键值对作为链表节点,将所有发生冲突的键值对都存储在同一链表中。图 6-5 展示了一个链式地址哈希表的例子。
|
||||
|
||||
![链式地址哈希表](hash_collision.assets/hash_table_chaining.png)
|
||||
![链式地址哈希表](hash_collision.assets/hash_table_chaining.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-5 链式地址哈希表 </p>
|
||||
|
||||
|
@ -1333,7 +1333,7 @@ comments: true
|
|||
|
||||
图 6-6 展示了开放寻址(线性探测)哈希表的键值对分布。根据此哈希函数,最后两位相同的 `key` 都会被映射到相同的桶。而通过线性探测,它们被依次存储在该桶以及之下的桶中。
|
||||
|
||||
![开放寻址和线性探测](hash_collision.assets/hash_table_linear_probing.png)
|
||||
![开放寻址和线性探测](hash_collision.assets/hash_table_linear_probing.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-6 开放寻址和线性探测 </p>
|
||||
|
||||
|
@ -1341,7 +1341,7 @@ comments: true
|
|||
|
||||
值得注意的是,**我们不能在开放寻址哈希表中直接删除元素**。这是因为删除元素会在数组内产生一个空桶 $\text{None}$ ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在。
|
||||
|
||||
![在开放寻址中删除元素导致的查询问题](hash_collision.assets/hash_table_open_addressing_deletion.png)
|
||||
![在开放寻址中删除元素导致的查询问题](hash_collision.assets/hash_table_open_addressing_deletion.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-7 在开放寻址中删除元素导致的查询问题 </p>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
如图 6-1 所示,给定 $n$ 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用图 6-1 所示的哈希表来实现。
|
||||
|
||||
![哈希表的抽象表示](hash_map.assets/hash_table_lookup.png)
|
||||
![哈希表的抽象表示](hash_map.assets/hash_table_lookup.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-1 哈希表的抽象表示 </p>
|
||||
|
||||
|
@ -493,7 +493,7 @@ index = hash(key) % capacity
|
|||
|
||||
设数组长度 `capacity = 100`、哈希算法 `hash(key) = key` ,易得哈希函数为 `key % 100` 。图 6-2 以 `key` 学号和 `value` 姓名为例,展示了哈希函数的工作原理。
|
||||
|
||||
![哈希函数工作原理](hash_map.assets/hash_function.png)
|
||||
![哈希函数工作原理](hash_map.assets/hash_function.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-2 哈希函数工作原理 </p>
|
||||
|
||||
|
@ -1650,7 +1650,7 @@ index = hash(key) % capacity
|
|||
|
||||
如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为「哈希冲突 hash collision」。
|
||||
|
||||
![哈希冲突示例](hash_map.assets/hash_collision.png)
|
||||
![哈希冲突示例](hash_map.assets/hash_collision.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-3 哈希冲突示例 </p>
|
||||
|
||||
|
@ -1658,7 +1658,7 @@ index = hash(key) % capacity
|
|||
|
||||
如图 6-4 所示,扩容前键值对 `(136, A)` 和 `(236, D)` 发生冲突,扩容后冲突消失。
|
||||
|
||||
![哈希表扩容](hash_map.assets/hash_table_reshash.png)
|
||||
![哈希表扩容](hash_map.assets/hash_table_reshash.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 6-4 哈希表扩容 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/table-search
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![哈希表](../assets/covers/chapter_hashing.jpg){ width="600" }
|
||||
![哈希表](../assets/covers/chapter_hashing.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -213,7 +213,7 @@ comments: true
|
|||
|
||||
接下来我们来进行更为准确的计算。为了减小计算难度,假设给定一个节点数量为 $n$ ,高度为 $h$ 的“完美二叉树”,该假设不会影响计算结果的正确性。
|
||||
|
||||
![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png)
|
||||
![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-5 完美二叉树的各层节点数量 </p>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ comments: true
|
|||
- 「大顶堆 max heap」:任意节点的值 $\geq$ 其子节点的值。
|
||||
- 「小顶堆 min heap」:任意节点的值 $\leq$ 其子节点的值。
|
||||
|
||||
![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png)
|
||||
![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-1 小顶堆与大顶堆 </p>
|
||||
|
||||
|
@ -367,7 +367,7 @@ comments: true
|
|||
|
||||
如图 8-2 所示,给定索引 $i$ ,其左子节点索引为 $2i + 1$ ,右子节点索引为 $2i + 2$ ,父节点索引为 $(i - 1) / 2$(向下取整)。当索引越界时,表示空节点或节点不存在。
|
||||
|
||||
![堆的表示与存储](heap.assets/representation_of_heap.png)
|
||||
![堆的表示与存储](heap.assets/representation_of_heap.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-2 堆的表示与存储 </p>
|
||||
|
||||
|
@ -718,31 +718,31 @@ comments: true
|
|||
考虑从入堆节点开始,**从底至顶执行堆化**。如图 8-3 所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。
|
||||
|
||||
=== "<1>"
|
||||
![元素入堆步骤](heap.assets/heap_push_step1.png)
|
||||
![元素入堆步骤](heap.assets/heap_push_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![heap_push_step2](heap.assets/heap_push_step2.png)
|
||||
![heap_push_step2](heap.assets/heap_push_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![heap_push_step3](heap.assets/heap_push_step3.png)
|
||||
![heap_push_step3](heap.assets/heap_push_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![heap_push_step4](heap.assets/heap_push_step4.png)
|
||||
![heap_push_step4](heap.assets/heap_push_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![heap_push_step5](heap.assets/heap_push_step5.png)
|
||||
![heap_push_step5](heap.assets/heap_push_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![heap_push_step6](heap.assets/heap_push_step6.png)
|
||||
![heap_push_step6](heap.assets/heap_push_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![heap_push_step7](heap.assets/heap_push_step7.png)
|
||||
![heap_push_step7](heap.assets/heap_push_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![heap_push_step8](heap.assets/heap_push_step8.png)
|
||||
![heap_push_step8](heap.assets/heap_push_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![heap_push_step9](heap.assets/heap_push_step9.png)
|
||||
![heap_push_step9](heap.assets/heap_push_step9.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-3 元素入堆步骤 </p>
|
||||
|
||||
|
@ -1095,34 +1095,34 @@ comments: true
|
|||
如图 8-4 所示,**“从顶至底堆化”的操作方向与“从底至顶堆化”相反**,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。
|
||||
|
||||
=== "<1>"
|
||||
![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png)
|
||||
![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![heap_pop_step2](heap.assets/heap_pop_step2.png)
|
||||
![heap_pop_step2](heap.assets/heap_pop_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![heap_pop_step3](heap.assets/heap_pop_step3.png)
|
||||
![heap_pop_step3](heap.assets/heap_pop_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![heap_pop_step4](heap.assets/heap_pop_step4.png)
|
||||
![heap_pop_step4](heap.assets/heap_pop_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![heap_pop_step5](heap.assets/heap_pop_step5.png)
|
||||
![heap_pop_step5](heap.assets/heap_pop_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![heap_pop_step6](heap.assets/heap_pop_step6.png)
|
||||
![heap_pop_step6](heap.assets/heap_pop_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![heap_pop_step7](heap.assets/heap_pop_step7.png)
|
||||
![heap_pop_step7](heap.assets/heap_pop_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![heap_pop_step8](heap.assets/heap_pop_step8.png)
|
||||
![heap_pop_step8](heap.assets/heap_pop_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![heap_pop_step9](heap.assets/heap_pop_step9.png)
|
||||
![heap_pop_step9](heap.assets/heap_pop_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![heap_pop_step10](heap.assets/heap_pop_step10.png)
|
||||
![heap_pop_step10](heap.assets/heap_pop_step10.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-4 堆顶元素出堆步骤 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/family-tree
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![堆](../assets/covers/chapter_heap.jpg){ width="600" }
|
||||
![堆](../assets/covers/chapter_heap.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ comments: true
|
|||
|
||||
此方法只适用于 $k \ll n$ 的情况,因为当 $k$ 与 $n$ 比较接近时,其时间复杂度趋向于 $O(n^2)$ ,非常耗时。
|
||||
|
||||
![遍历寻找最大的 k 个元素](top_k.assets/top_k_traversal.png)
|
||||
![遍历寻找最大的 k 个元素](top_k.assets/top_k_traversal.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-6 遍历寻找最大的 k 个元素 </p>
|
||||
|
||||
|
@ -30,7 +30,7 @@ comments: true
|
|||
|
||||
显然,该方法“超额”完成任务了,因为我们只需要找出最大的 $k$ 个元素即可,而不需要排序其他元素。
|
||||
|
||||
![排序寻找最大的 k 个元素](top_k.assets/top_k_sorting.png)
|
||||
![排序寻找最大的 k 个元素](top_k.assets/top_k_sorting.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-7 排序寻找最大的 k 个元素 </p>
|
||||
|
||||
|
@ -44,31 +44,31 @@ comments: true
|
|||
4. 遍历完成后,堆中保存的就是最大的 $k$ 个元素。
|
||||
|
||||
=== "<1>"
|
||||
![基于堆寻找最大的 k 个元素](top_k.assets/top_k_heap_step1.png)
|
||||
![基于堆寻找最大的 k 个元素](top_k.assets/top_k_heap_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![top_k_heap_step2](top_k.assets/top_k_heap_step2.png)
|
||||
![top_k_heap_step2](top_k.assets/top_k_heap_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![top_k_heap_step3](top_k.assets/top_k_heap_step3.png)
|
||||
![top_k_heap_step3](top_k.assets/top_k_heap_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![top_k_heap_step4](top_k.assets/top_k_heap_step4.png)
|
||||
![top_k_heap_step4](top_k.assets/top_k_heap_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![top_k_heap_step5](top_k.assets/top_k_heap_step5.png)
|
||||
![top_k_heap_step5](top_k.assets/top_k_heap_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![top_k_heap_step6](top_k.assets/top_k_heap_step6.png)
|
||||
![top_k_heap_step6](top_k.assets/top_k_heap_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![top_k_heap_step7](top_k.assets/top_k_heap_step7.png)
|
||||
![top_k_heap_step7](top_k.assets/top_k_heap_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![top_k_heap_step8](top_k.assets/top_k_heap_step8.png)
|
||||
![top_k_heap_step8](top_k.assets/top_k_heap_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![top_k_heap_step9](top_k.assets/top_k_heap_step9.png)
|
||||
![top_k_heap_step9](top_k.assets/top_k_heap_step9.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 8-8 基于堆寻找最大的 k 个元素 </p>
|
||||
|
||||
|
|
|
@ -15,19 +15,19 @@ comments: true
|
|||
3. 不断重复步骤 `1.` 和 步骤 `2.` ,直至找到拼音首字母为 $r$ 的页码为止。
|
||||
|
||||
=== "<1>"
|
||||
![查字典步骤](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png)
|
||||
![查字典步骤](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png)
|
||||
![binary_search_dictionary_step2](algorithms_are_everywhere.assets/binary_search_dictionary_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png)
|
||||
![binary_search_dictionary_step3](algorithms_are_everywhere.assets/binary_search_dictionary_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png)
|
||||
![binary_search_dictionary_step4](algorithms_are_everywhere.assets/binary_search_dictionary_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png)
|
||||
![binary_search_dictionary_step5](algorithms_are_everywhere.assets/binary_search_dictionary_step5.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 1-1 查字典步骤 </p>
|
||||
|
||||
|
@ -39,7 +39,7 @@ comments: true
|
|||
2. 在无序部分抽出一张扑克牌,插入至有序部分的正确位置;完成后最左 2 张扑克已经有序。
|
||||
3. 不断循环步骤 `2.` ,每一轮将一张扑克牌从无序部分插入至有序部分,直至所有扑克牌都有序。
|
||||
|
||||
![扑克排序步骤](algorithms_are_everywhere.assets/playing_cards_sorting.png)
|
||||
![扑克排序步骤](algorithms_are_everywhere.assets/playing_cards_sorting.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 1-2 扑克排序步骤 </p>
|
||||
|
||||
|
@ -53,7 +53,7 @@ comments: true
|
|||
4. 从剩余可选项中拿出最大的 $1$ 元,剩余 $1 - 1 = 0$ 元。
|
||||
5. 完成找零,方案为 $20 + 10 + 1 = 31$ 元。
|
||||
|
||||
![货币找零过程](algorithms_are_everywhere.assets/greedy_change.png)
|
||||
![货币找零过程](algorithms_are_everywhere.assets/greedy_change.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 1-3 货币找零过程 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/calculator-variant-outline
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![初识算法](../assets/covers/chapter_introduction.jpg){ width="600" }
|
||||
![初识算法](../assets/covers/chapter_introduction.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -33,13 +33,13 @@ comments: true
|
|||
- 算法是数据结构发挥作用的舞台。数据结构本身仅存储数据信息,结合算法才能解决特定问题。
|
||||
- 算法通常可以基于不同的数据结构进行实现,但执行效率可能相差很大,选择合适的数据结构是关键。
|
||||
|
||||
![数据结构与算法的关系](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png)
|
||||
![数据结构与算法的关系](what_is_dsa.assets/relationship_between_data_structure_and_algorithm.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 1-4 数据结构与算法的关系 </p>
|
||||
|
||||
数据结构与算法犹如图 1-5 所示的拼装积木。一套积木,除了包含许多零件之外,还附有详细的组装说明书。我们按照说明书一步步操作,就能组装出精美的积木模型。
|
||||
|
||||
![拼装积木](what_is_dsa.assets/assembling_blocks.png)
|
||||
![拼装积木](what_is_dsa.assets/assembling_blocks.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 1-5 拼装积木 </p>
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ comments: true
|
|||
- **数据结构**:基本数据类型,数据结构的分类方法。数组、链表、栈、队列、哈希表、树、堆、图等数据结构的定义、优缺点、常用操作、常见类型、典型应用、实现方法等。
|
||||
- **算法**:搜索、排序、分治、回溯、动态规划、贪心等算法的定义、优缺点、效率、应用场景、解题步骤、示例题目等。
|
||||
|
||||
![Hello 算法内容结构](about_the_book.assets/hello_algo_mindmap.jpg)
|
||||
![Hello 算法内容结构](about_the_book.assets/hello_algo_mindmap.jpg){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-1 Hello 算法内容结构 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/book-open-outline
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![前言](../assets/covers/chapter_preface.jpg){ width="600" }
|
||||
![前言](../assets/covers/chapter_preface.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -177,7 +177,7 @@ comments: true
|
|||
|
||||
如果你在阅读本书时,发现某段内容提供了图 0-2 所示的动画或图解,**请以图为主、以文字为辅**,综合两者来理解内容。
|
||||
|
||||
![动画图解示例](../index.assets/animation.gif)
|
||||
![动画图解示例](../index.assets/animation.gif){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-2 动画图解示例 </p>
|
||||
|
||||
|
@ -189,7 +189,7 @@ comments: true
|
|||
|
||||
与阅读代码相比,编写代码的过程往往能带来更多收获。**动手学,才是真的学**。
|
||||
|
||||
![运行代码示例](../index.assets/running_code.gif)
|
||||
![运行代码示例](../index.assets/running_code.gif){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-3 运行代码示例 </p>
|
||||
|
||||
|
@ -205,13 +205,13 @@ git clone https://github.com/krahets/hello-algo.git
|
|||
|
||||
当然,你也可以在图 0-4 所示的位置,点击“Download ZIP”直接下载代码压缩包,然后在本地解压即可。
|
||||
|
||||
![克隆仓库与下载代码](suggestions.assets/download_code.png)
|
||||
![克隆仓库与下载代码](suggestions.assets/download_code.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-4 克隆仓库与下载代码 </p>
|
||||
|
||||
**第三步:运行源代码**。如图 0-5 所示,对于顶部标有文件名称的代码块,我们可以在仓库的 `codes` 文件夹内找到对应的源代码文件。源代码文件可一键运行,将帮助你节省不必要的调试时间,让你能够专注于学习内容。
|
||||
|
||||
![代码块与对应的源代码文件](suggestions.assets/code_md_to_repo.png)
|
||||
![代码块与对应的源代码文件](suggestions.assets/code_md_to_repo.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-5 代码块与对应的源代码文件 </p>
|
||||
|
||||
|
@ -221,7 +221,7 @@ git clone https://github.com/krahets/hello-algo.git
|
|||
|
||||
如图 0-6 所示,每篇文章的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享您的见解,帮助他人进步。
|
||||
|
||||
![评论区示例](../index.assets/comment.gif)
|
||||
![评论区示例](../index.assets/comment.gif){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-6 评论区示例 </p>
|
||||
|
||||
|
@ -235,6 +235,6 @@ git clone https://github.com/krahets/hello-algo.git
|
|||
|
||||
如图 0-7 所示,本书内容主要涵盖“第一阶段”,旨在帮助你更高效地展开第二和第三阶段的学习。
|
||||
|
||||
![算法学习路线](suggestions.assets/learning_route.png)
|
||||
![算法学习路线](suggestions.assets/learning_route.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 0-7 算法学习路线 </p>
|
||||
|
|
|
@ -10,7 +10,7 @@ comments: true
|
|||
|
||||
给定一个长度为 $n$ 的数组 `nums` ,元素按从小到大的顺序排列,数组不包含重复元素。请查找并返回元素 `target` 在该数组中的索引。若数组不包含该元素,则返回 $-1$ 。
|
||||
|
||||
![二分查找示例数据](binary_search.assets/binary_search_example.png)
|
||||
![二分查找示例数据](binary_search.assets/binary_search_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-1 二分查找示例数据 </p>
|
||||
|
||||
|
@ -27,25 +27,25 @@ comments: true
|
|||
若数组不包含目标元素,搜索区间最终会缩小为空。此时返回 $-1$ 。
|
||||
|
||||
=== "<1>"
|
||||
![二分查找流程](binary_search.assets/binary_search_step1.png)
|
||||
![二分查找流程](binary_search.assets/binary_search_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![binary_search_step2](binary_search.assets/binary_search_step2.png)
|
||||
![binary_search_step2](binary_search.assets/binary_search_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![binary_search_step3](binary_search.assets/binary_search_step3.png)
|
||||
![binary_search_step3](binary_search.assets/binary_search_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![binary_search_step4](binary_search.assets/binary_search_step4.png)
|
||||
![binary_search_step4](binary_search.assets/binary_search_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![binary_search_step5](binary_search.assets/binary_search_step5.png)
|
||||
![binary_search_step5](binary_search.assets/binary_search_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![binary_search_step6](binary_search.assets/binary_search_step6.png)
|
||||
![binary_search_step6](binary_search.assets/binary_search_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![binary_search_step7](binary_search.assets/binary_search_step7.png)
|
||||
![binary_search_step7](binary_search.assets/binary_search_step7.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-2 二分查找流程 </p>
|
||||
|
||||
|
@ -627,7 +627,7 @@ comments: true
|
|||
|
||||
由于“双闭区间”表示中的左右边界都被定义为闭区间,因此指针 $i$ 和 $j$ 缩小区间操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。
|
||||
|
||||
![两种区间定义](binary_search.assets/binary_search_ranges.png)
|
||||
![两种区间定义](binary_search.assets/binary_search_ranges.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-3 两种区间定义 </p>
|
||||
|
||||
|
|
|
@ -211,7 +211,7 @@ comments: true
|
|||
|
||||
如图 10-7 所示,查找完成后,指针 $i$ 指向最左一个 `target + 1`(如果存在),而 $j$ 指向最右一个 `target` ,**因此返回 $j$ 即可**。
|
||||
|
||||
![将查找右边界转化为查找左边界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png)
|
||||
![将查找右边界转化为查找左边界](binary_search_edge.assets/binary_search_right_edge_by_left_edge.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-7 将查找右边界转化为查找左边界 </p>
|
||||
|
||||
|
@ -428,7 +428,7 @@ comments: true
|
|||
- 查找最左一个 `target` :可以转化为查找 `target - 0.5` ,并返回指针 $i$ 。
|
||||
- 查找最右一个 `target` :可以转化为查找 `target + 0.5` ,并返回指针 $j$ 。
|
||||
|
||||
![将查找边界转化为查找元素](binary_search_edge.assets/binary_search_edge_by_element.png)
|
||||
![将查找边界转化为查找元素](binary_search_edge.assets/binary_search_edge_by_element.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-8 将查找边界转化为查找元素 </p>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ comments: true
|
|||
|
||||
给定一个长度为 $n$ 的有序数组 `nums` 和一个元素 `target` ,数组不存在重复元素。现将 `target` 插入到数组 `nums` 中,并保持其有序性。若数组中已存在元素 `target` ,则插入到其左方。请返回插入后 `target` 在数组中的索引。
|
||||
|
||||
![二分查找插入点示例数据](binary_search_insertion.assets/binary_search_insertion_example.png)
|
||||
![二分查找插入点示例数据](binary_search_insertion.assets/binary_search_insertion_example.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-4 二分查找插入点示例数据 </p>
|
||||
|
||||
|
@ -285,7 +285,7 @@ comments: true
|
|||
1. 执行二分查找,得到任意一个 `target` 的索引,记为 $k$ 。
|
||||
2. 从索引 $k$ 开始,向左进行线性遍历,当找到最左边的 `target` 时返回。
|
||||
|
||||
![线性查找重复元素的插入点](binary_search_insertion.assets/binary_search_insertion_naive.png)
|
||||
![线性查找重复元素的插入点](binary_search_insertion.assets/binary_search_insertion_naive.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-5 线性查找重复元素的插入点 </p>
|
||||
|
||||
|
@ -299,28 +299,28 @@ comments: true
|
|||
循环完成后,$i$ 指向最左边的 `target` ,$j$ 指向首个小于 `target` 的元素,**因此索引 $i$ 就是插入点**。
|
||||
|
||||
=== "<1>"
|
||||
![二分查找重复元素的插入点的步骤](binary_search_insertion.assets/binary_search_insertion_step1.png)
|
||||
![二分查找重复元素的插入点的步骤](binary_search_insertion.assets/binary_search_insertion_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png)
|
||||
![binary_search_insertion_step2](binary_search_insertion.assets/binary_search_insertion_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png)
|
||||
![binary_search_insertion_step3](binary_search_insertion.assets/binary_search_insertion_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png)
|
||||
![binary_search_insertion_step4](binary_search_insertion.assets/binary_search_insertion_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png)
|
||||
![binary_search_insertion_step5](binary_search_insertion.assets/binary_search_insertion_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png)
|
||||
![binary_search_insertion_step6](binary_search_insertion.assets/binary_search_insertion_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png)
|
||||
![binary_search_insertion_step7](binary_search_insertion.assets/binary_search_insertion_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png)
|
||||
![binary_search_insertion_step8](binary_search_insertion.assets/binary_search_insertion_step8.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-6 二分查找重复元素的插入点的步骤 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/text-search
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![搜索](../assets/covers/chapter_searching.jpg){ width="600" }
|
||||
![搜索](../assets/covers/chapter_searching.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
|
||||
考虑直接遍历所有可能的组合。如图 10-9 所示,我们开启一个两层循环,在每轮中判断两个整数的和是否为 `target` ,若是则返回它们的索引。
|
||||
|
||||
![线性查找求解两数之和](replace_linear_by_hashing.assets/two_sum_brute_force.png)
|
||||
![线性查找求解两数之和](replace_linear_by_hashing.assets/two_sum_brute_force.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-9 线性查找求解两数之和 </p>
|
||||
|
||||
|
@ -237,13 +237,13 @@ comments: true
|
|||
2. 将键值对 `nums[i]` 和索引 `i` 添加进哈希表。
|
||||
|
||||
=== "<1>"
|
||||
![辅助哈希表求解两数之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png)
|
||||
![辅助哈希表求解两数之和](replace_linear_by_hashing.assets/two_sum_hashtable_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png)
|
||||
![two_sum_hashtable_step2](replace_linear_by_hashing.assets/two_sum_hashtable_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png)
|
||||
![two_sum_hashtable_step3](replace_linear_by_hashing.assets/two_sum_hashtable_step3.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-10 辅助哈希表求解两数之和 </p>
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ comments: true
|
|||
|
||||
给定大小为 $n$ 的一组数据,我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法在该数据中搜索目标元素。各个方法的工作原理如图 10-11 所示。
|
||||
|
||||
![多种搜索策略](searching_algorithm_revisited.assets/searching_algorithms.png)
|
||||
![多种搜索策略](searching_algorithm_revisited.assets/searching_algorithms.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 10-11 多种搜索策略 </p>
|
||||
|
||||
|
|
|
@ -9,25 +9,25 @@ comments: true
|
|||
如图 11-4 所示,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换它俩。遍历完成后,最大的元素会被移动到数组的最右端。
|
||||
|
||||
=== "<1>"
|
||||
![利用元素交换操作模拟冒泡](bubble_sort.assets/bubble_operation_step1.png)
|
||||
![利用元素交换操作模拟冒泡](bubble_sort.assets/bubble_operation_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png)
|
||||
![bubble_operation_step2](bubble_sort.assets/bubble_operation_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png)
|
||||
![bubble_operation_step3](bubble_sort.assets/bubble_operation_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png)
|
||||
![bubble_operation_step4](bubble_sort.assets/bubble_operation_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png)
|
||||
![bubble_operation_step5](bubble_sort.assets/bubble_operation_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png)
|
||||
![bubble_operation_step6](bubble_sort.assets/bubble_operation_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png)
|
||||
![bubble_operation_step7](bubble_sort.assets/bubble_operation_step7.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-4 利用元素交换操作模拟冒泡 </p>
|
||||
|
||||
|
@ -40,7 +40,7 @@ comments: true
|
|||
3. 以此类推,经过 $n - 1$ 轮“冒泡”后,**前 $n - 1$ 大的元素都被交换至正确位置**。
|
||||
4. 仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。
|
||||
|
||||
![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png)
|
||||
![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-5 冒泡排序流程 </p>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ comments: true
|
|||
2. 对每个桶分别执行排序(本文采用编程语言的内置排序函数)。
|
||||
3. 按照桶的从小到大的顺序,合并结果。
|
||||
|
||||
![桶排序算法流程](bucket_sort.assets/bucket_sort_overview.png)
|
||||
![桶排序算法流程](bucket_sort.assets/bucket_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-13 桶排序算法流程 </p>
|
||||
|
||||
|
@ -409,7 +409,7 @@ comments: true
|
|||
|
||||
如图 11-14 所示,这种方法本质上是创建一个递归树,目标是让叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选择。
|
||||
|
||||
![递归划分桶](bucket_sort.assets/scatter_in_buckets_recursively.png)
|
||||
![递归划分桶](bucket_sort.assets/scatter_in_buckets_recursively.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-14 递归划分桶 </p>
|
||||
|
||||
|
@ -417,6 +417,6 @@ comments: true
|
|||
|
||||
如图 11-15 所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。
|
||||
|
||||
![根据概率分布划分桶](bucket_sort.assets/scatter_in_buckets_distribution.png)
|
||||
![根据概率分布划分桶](bucket_sort.assets/scatter_in_buckets_distribution.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-15 根据概率分布划分桶 </p>
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
2. **借助 `counter` 统计 `nums` 中各数字的出现次数**,其中 `counter[num]` 对应数字 `num` 的出现次数。统计方法很简单,只需遍历 `nums`(设当前数字为 `num`),每轮将 `counter[num]` 增加 $1$ 即可。
|
||||
3. **由于 `counter` 的各个索引天然有序,因此相当于所有数字已经被排序好了**。接下来,我们遍历 `counter` ,根据各数字的出现次数,将它们按从小到大的顺序填入 `nums` 即可。
|
||||
|
||||
![计数排序流程](counting_sort.assets/counting_sort_overview.png)
|
||||
![计数排序流程](counting_sort.assets/counting_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-16 计数排序流程 </p>
|
||||
|
||||
|
@ -339,28 +339,28 @@ $$
|
|||
遍历完成后,数组 `res` 中就是排序好的结果,最后使用 `res` 覆盖原数组 `nums` 即可。图 11-17 展示了完整的计数排序流程。
|
||||
|
||||
=== "<1>"
|
||||
![计数排序步骤](counting_sort.assets/counting_sort_step1.png)
|
||||
![计数排序步骤](counting_sort.assets/counting_sort_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![counting_sort_step2](counting_sort.assets/counting_sort_step2.png)
|
||||
![counting_sort_step2](counting_sort.assets/counting_sort_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![counting_sort_step3](counting_sort.assets/counting_sort_step3.png)
|
||||
![counting_sort_step3](counting_sort.assets/counting_sort_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![counting_sort_step4](counting_sort.assets/counting_sort_step4.png)
|
||||
![counting_sort_step4](counting_sort.assets/counting_sort_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![counting_sort_step5](counting_sort.assets/counting_sort_step5.png)
|
||||
![counting_sort_step5](counting_sort.assets/counting_sort_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![counting_sort_step6](counting_sort.assets/counting_sort_step6.png)
|
||||
![counting_sort_step6](counting_sort.assets/counting_sort_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![counting_sort_step7](counting_sort.assets/counting_sort_step7.png)
|
||||
![counting_sort_step7](counting_sort.assets/counting_sort_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![counting_sort_step8](counting_sort.assets/counting_sort_step8.png)
|
||||
![counting_sort_step8](counting_sort.assets/counting_sort_step8.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-17 计数排序步骤 </p>
|
||||
|
||||
|
|
|
@ -29,40 +29,40 @@ comments: true
|
|||
实际上,元素出堆操作中也包含第 `2.` 和 `3.` 步,只是多了一个弹出元素的步骤。
|
||||
|
||||
=== "<1>"
|
||||
![堆排序步骤](heap_sort.assets/heap_sort_step1.png)
|
||||
![堆排序步骤](heap_sort.assets/heap_sort_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![heap_sort_step2](heap_sort.assets/heap_sort_step2.png)
|
||||
![heap_sort_step2](heap_sort.assets/heap_sort_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![heap_sort_step3](heap_sort.assets/heap_sort_step3.png)
|
||||
![heap_sort_step3](heap_sort.assets/heap_sort_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![heap_sort_step4](heap_sort.assets/heap_sort_step4.png)
|
||||
![heap_sort_step4](heap_sort.assets/heap_sort_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![heap_sort_step5](heap_sort.assets/heap_sort_step5.png)
|
||||
![heap_sort_step5](heap_sort.assets/heap_sort_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![heap_sort_step6](heap_sort.assets/heap_sort_step6.png)
|
||||
![heap_sort_step6](heap_sort.assets/heap_sort_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![heap_sort_step7](heap_sort.assets/heap_sort_step7.png)
|
||||
![heap_sort_step7](heap_sort.assets/heap_sort_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![heap_sort_step8](heap_sort.assets/heap_sort_step8.png)
|
||||
![heap_sort_step8](heap_sort.assets/heap_sort_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![heap_sort_step9](heap_sort.assets/heap_sort_step9.png)
|
||||
![heap_sort_step9](heap_sort.assets/heap_sort_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![heap_sort_step10](heap_sort.assets/heap_sort_step10.png)
|
||||
![heap_sort_step10](heap_sort.assets/heap_sort_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![heap_sort_step11](heap_sort.assets/heap_sort_step11.png)
|
||||
![heap_sort_step11](heap_sort.assets/heap_sort_step11.png){ class="animation-figure" }
|
||||
|
||||
=== "<12>"
|
||||
![heap_sort_step12](heap_sort.assets/heap_sort_step12.png)
|
||||
![heap_sort_step12](heap_sort.assets/heap_sort_step12.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-12 堆排序步骤 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/sort-ascending
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![排序](../assets/covers/chapter_sorting.jpg){ width="600" }
|
||||
![排序](../assets/covers/chapter_sorting.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ comments: true
|
|||
|
||||
图 11-6 展示了数组插入元素的操作流程。设基准元素为 `base` ,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后再将 `base` 赋值给目标索引。
|
||||
|
||||
![单次插入操作](insertion_sort.assets/insertion_operation.png)
|
||||
![单次插入操作](insertion_sort.assets/insertion_operation.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-6 单次插入操作 </p>
|
||||
|
||||
|
@ -23,7 +23,7 @@ comments: true
|
|||
3. 选取第 3 个元素作为 `base` ,将其插入到正确位置后,**数组的前 3 个元素已排序**。
|
||||
4. 以此类推,在最后一轮中,选取最后一个元素作为 `base` ,将其插入到正确位置后,**所有元素均已排序**。
|
||||
|
||||
![插入排序流程](insertion_sort.assets/insertion_sort_overview.png)
|
||||
![插入排序流程](insertion_sort.assets/insertion_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-7 插入排序流程 </p>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ comments: true
|
|||
1. **划分阶段**:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。
|
||||
2. **合并阶段**:当子数组长度为 1 时终止划分,开始合并,持续地将左右两个较短的有序数组合并为一个较长的有序数组,直至结束。
|
||||
|
||||
![归并排序的划分与合并阶段](merge_sort.assets/merge_sort_overview.png)
|
||||
![归并排序的划分与合并阶段](merge_sort.assets/merge_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-10 归并排序的划分与合并阶段 </p>
|
||||
|
||||
|
@ -23,34 +23,34 @@ comments: true
|
|||
“合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。
|
||||
|
||||
=== "<1>"
|
||||
![归并排序步骤](merge_sort.assets/merge_sort_step1.png)
|
||||
![归并排序步骤](merge_sort.assets/merge_sort_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![merge_sort_step2](merge_sort.assets/merge_sort_step2.png)
|
||||
![merge_sort_step2](merge_sort.assets/merge_sort_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![merge_sort_step3](merge_sort.assets/merge_sort_step3.png)
|
||||
![merge_sort_step3](merge_sort.assets/merge_sort_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![merge_sort_step4](merge_sort.assets/merge_sort_step4.png)
|
||||
![merge_sort_step4](merge_sort.assets/merge_sort_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![merge_sort_step5](merge_sort.assets/merge_sort_step5.png)
|
||||
![merge_sort_step5](merge_sort.assets/merge_sort_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![merge_sort_step6](merge_sort.assets/merge_sort_step6.png)
|
||||
![merge_sort_step6](merge_sort.assets/merge_sort_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![merge_sort_step7](merge_sort.assets/merge_sort_step7.png)
|
||||
![merge_sort_step7](merge_sort.assets/merge_sort_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![merge_sort_step8](merge_sort.assets/merge_sort_step8.png)
|
||||
![merge_sort_step8](merge_sort.assets/merge_sort_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![merge_sort_step9](merge_sort.assets/merge_sort_step9.png)
|
||||
![merge_sort_step9](merge_sort.assets/merge_sort_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![merge_sort_step10](merge_sort.assets/merge_sort_step10.png)
|
||||
![merge_sort_step10](merge_sort.assets/merge_sort_step10.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-11 归并排序步骤 </p>
|
||||
|
||||
|
|
|
@ -13,31 +13,31 @@ comments: true
|
|||
3. 循环执行步骤 `2.` ,直到 `i` 和 `j` 相遇时停止,最后将基准数交换至两个子数组的分界线。
|
||||
|
||||
=== "<1>"
|
||||
![哨兵划分步骤](quick_sort.assets/pivot_division_step1.png)
|
||||
![哨兵划分步骤](quick_sort.assets/pivot_division_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![pivot_division_step2](quick_sort.assets/pivot_division_step2.png)
|
||||
![pivot_division_step2](quick_sort.assets/pivot_division_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![pivot_division_step3](quick_sort.assets/pivot_division_step3.png)
|
||||
![pivot_division_step3](quick_sort.assets/pivot_division_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![pivot_division_step4](quick_sort.assets/pivot_division_step4.png)
|
||||
![pivot_division_step4](quick_sort.assets/pivot_division_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![pivot_division_step5](quick_sort.assets/pivot_division_step5.png)
|
||||
![pivot_division_step5](quick_sort.assets/pivot_division_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![pivot_division_step6](quick_sort.assets/pivot_division_step6.png)
|
||||
![pivot_division_step6](quick_sort.assets/pivot_division_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![pivot_division_step7](quick_sort.assets/pivot_division_step7.png)
|
||||
![pivot_division_step7](quick_sort.assets/pivot_division_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![pivot_division_step8](quick_sort.assets/pivot_division_step8.png)
|
||||
![pivot_division_step8](quick_sort.assets/pivot_division_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![pivot_division_step9](quick_sort.assets/pivot_division_step9.png)
|
||||
![pivot_division_step9](quick_sort.assets/pivot_division_step9.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-8 哨兵划分步骤 </p>
|
||||
|
||||
|
@ -366,7 +366,7 @@ comments: true
|
|||
2. 然后,对左子数组和右子数组分别递归执行“哨兵划分”。
|
||||
3. 持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。
|
||||
|
||||
![快速排序流程](quick_sort.assets/quick_sort_overview.png)
|
||||
![快速排序流程](quick_sort.assets/quick_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-9 快速排序流程 </p>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ comments: true
|
|||
2. 对学号的第 $k$ 位执行“计数排序”。完成后,数据会根据第 $k$ 位从小到大排序。
|
||||
3. 将 $k$ 增加 $1$ ,然后返回步骤 `2.` 继续迭代,直到所有位都排序完成后结束。
|
||||
|
||||
![基数排序算法流程](radix_sort.assets/radix_sort_overview.png)
|
||||
![基数排序算法流程](radix_sort.assets/radix_sort_overview.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-18 基数排序算法流程 </p>
|
||||
|
||||
|
|
|
@ -15,37 +15,37 @@ comments: true
|
|||
5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。
|
||||
|
||||
=== "<1>"
|
||||
![选择排序步骤](selection_sort.assets/selection_sort_step1.png)
|
||||
![选择排序步骤](selection_sort.assets/selection_sort_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![selection_sort_step2](selection_sort.assets/selection_sort_step2.png)
|
||||
![selection_sort_step2](selection_sort.assets/selection_sort_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![selection_sort_step3](selection_sort.assets/selection_sort_step3.png)
|
||||
![selection_sort_step3](selection_sort.assets/selection_sort_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![selection_sort_step4](selection_sort.assets/selection_sort_step4.png)
|
||||
![selection_sort_step4](selection_sort.assets/selection_sort_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![selection_sort_step5](selection_sort.assets/selection_sort_step5.png)
|
||||
![selection_sort_step5](selection_sort.assets/selection_sort_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![selection_sort_step6](selection_sort.assets/selection_sort_step6.png)
|
||||
![selection_sort_step6](selection_sort.assets/selection_sort_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![selection_sort_step7](selection_sort.assets/selection_sort_step7.png)
|
||||
![selection_sort_step7](selection_sort.assets/selection_sort_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![selection_sort_step8](selection_sort.assets/selection_sort_step8.png)
|
||||
![selection_sort_step8](selection_sort.assets/selection_sort_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![selection_sort_step9](selection_sort.assets/selection_sort_step9.png)
|
||||
![selection_sort_step9](selection_sort.assets/selection_sort_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![selection_sort_step10](selection_sort.assets/selection_sort_step10.png)
|
||||
![selection_sort_step10](selection_sort.assets/selection_sort_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![selection_sort_step11](selection_sort.assets/selection_sort_step11.png)
|
||||
![selection_sort_step11](selection_sort.assets/selection_sort_step11.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-2 选择排序步骤 </p>
|
||||
|
||||
|
@ -290,6 +290,6 @@ comments: true
|
|||
- **空间复杂度 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。
|
||||
- **非稳定排序**:如图 11-3 所示,元素 `nums[i]` 有可能被交换至与其相等的元素的右边,导致两者相对顺序发生改变。
|
||||
|
||||
![选择排序非稳定示例](selection_sort.assets/selection_sort_instability.png)
|
||||
![选择排序非稳定示例](selection_sort.assets/selection_sort_instability.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-3 选择排序非稳定示例 </p>
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
如图 11-1 所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求设定,如数字大小、字符 ASCII 码顺序或自定义规则。
|
||||
|
||||
![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png)
|
||||
![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-1 数据类型和判断规则示例 </p>
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ comments: true
|
|||
- 总的来说,我们希望找到一种排序算法,具有高效率、稳定、原地以及正向自适应性等优点。然而,正如其他数据结构和算法一样,没有一种排序算法能够同时满足所有这些条件。在实际应用中,我们需要根据数据的特性来选择合适的排序算法。
|
||||
- 图 11-19 对比了主流排序算法的效率、稳定性、就地性和自适应性等。
|
||||
|
||||
![排序算法对比](summary.assets/sorting_algorithms_comparison.png)
|
||||
![排序算法对比](summary.assets/sorting_algorithms_comparison.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 11-19 排序算法对比 </p>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ comments: true
|
|||
|
||||
在队列中,我们仅能在头部删除或在尾部添加元素。如图 5-7 所示,「双向队列 double-ended queue」提供了更高的灵活性,允许在头部和尾部执行元素的添加或删除操作。
|
||||
|
||||
![双向队列的操作](deque.assets/deque_operations.png)
|
||||
![双向队列的操作](deque.assets/deque_operations.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-7 双向队列的操作 </p>
|
||||
|
||||
|
@ -365,19 +365,19 @@ comments: true
|
|||
如图 5-8 所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。
|
||||
|
||||
=== "LinkedListDeque"
|
||||
![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque.png)
|
||||
![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque.png){ class="animation-figure" }
|
||||
|
||||
=== "pushLast()"
|
||||
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_push_last.png)
|
||||
![linkedlist_deque_push_last](deque.assets/linkedlist_deque_push_last.png){ class="animation-figure" }
|
||||
|
||||
=== "pushFirst()"
|
||||
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_push_first.png)
|
||||
![linkedlist_deque_push_first](deque.assets/linkedlist_deque_push_first.png){ class="animation-figure" }
|
||||
|
||||
=== "popLast()"
|
||||
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_pop_last.png)
|
||||
![linkedlist_deque_pop_last](deque.assets/linkedlist_deque_pop_last.png){ class="animation-figure" }
|
||||
|
||||
=== "popFirst()"
|
||||
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_pop_first.png)
|
||||
![linkedlist_deque_pop_first](deque.assets/linkedlist_deque_pop_first.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-8 基于链表实现双向队列的入队出队操作 </p>
|
||||
|
||||
|
@ -2008,19 +2008,19 @@ comments: true
|
|||
如图 5-9 所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。
|
||||
|
||||
=== "ArrayDeque"
|
||||
![基于数组实现双向队列的入队出队操作](deque.assets/array_deque.png)
|
||||
![基于数组实现双向队列的入队出队操作](deque.assets/array_deque.png){ class="animation-figure" }
|
||||
|
||||
=== "pushLast()"
|
||||
![array_deque_push_last](deque.assets/array_deque_push_last.png)
|
||||
![array_deque_push_last](deque.assets/array_deque_push_last.png){ class="animation-figure" }
|
||||
|
||||
=== "pushFirst()"
|
||||
![array_deque_push_first](deque.assets/array_deque_push_first.png)
|
||||
![array_deque_push_first](deque.assets/array_deque_push_first.png){ class="animation-figure" }
|
||||
|
||||
=== "popLast()"
|
||||
![array_deque_pop_last](deque.assets/array_deque_pop_last.png)
|
||||
![array_deque_pop_last](deque.assets/array_deque_pop_last.png){ class="animation-figure" }
|
||||
|
||||
=== "popFirst()"
|
||||
![array_deque_pop_first](deque.assets/array_deque_pop_first.png)
|
||||
![array_deque_pop_first](deque.assets/array_deque_pop_first.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-9 基于数组实现双向队列的入队出队操作 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/stack-overflow
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![栈与队列](../assets/covers/chapter_stack_and_queue.jpg){ width="600" }
|
||||
![栈与队列](../assets/covers/chapter_stack_and_queue.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ comments: true
|
|||
|
||||
如图 5-4 所示,我们将队列的头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。
|
||||
|
||||
![队列的先入先出规则](queue.assets/queue_operations.png)
|
||||
![队列的先入先出规则](queue.assets/queue_operations.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-4 队列的先入先出规则 </p>
|
||||
|
||||
|
@ -325,13 +325,13 @@ comments: true
|
|||
如图 5-5 所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
|
||||
|
||||
=== "LinkedListQueue"
|
||||
![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue.png)
|
||||
![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue.png){ class="animation-figure" }
|
||||
|
||||
=== "push()"
|
||||
![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png)
|
||||
![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png){ class="animation-figure" }
|
||||
|
||||
=== "pop()"
|
||||
![linkedlist_queue_pop](queue.assets/linkedlist_queue_pop.png)
|
||||
![linkedlist_queue_pop](queue.assets/linkedlist_queue_pop.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-5 基于链表实现队列的入队出队操作 </p>
|
||||
|
||||
|
@ -1216,13 +1216,13 @@ comments: true
|
|||
可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。
|
||||
|
||||
=== "ArrayQueue"
|
||||
![基于数组实现队列的入队出队操作](queue.assets/array_queue.png)
|
||||
![基于数组实现队列的入队出队操作](queue.assets/array_queue.png){ class="animation-figure" }
|
||||
|
||||
=== "push()"
|
||||
![array_queue_push](queue.assets/array_queue_push.png)
|
||||
![array_queue_push](queue.assets/array_queue_push.png){ class="animation-figure" }
|
||||
|
||||
=== "pop()"
|
||||
![array_queue_pop](queue.assets/array_queue_pop.png)
|
||||
![array_queue_pop](queue.assets/array_queue_pop.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-6 基于数组实现队列的入队出队操作 </p>
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ comments: true
|
|||
|
||||
如图 5-1 所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫做“入栈”,删除栈顶元素的操作叫做“出栈”。
|
||||
|
||||
![栈的先入后出规则](stack.assets/stack_operations.png)
|
||||
![栈的先入后出规则](stack.assets/stack_operations.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-1 栈的先入后出规则 </p>
|
||||
|
||||
|
@ -325,13 +325,13 @@ comments: true
|
|||
如图 5-2 所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。
|
||||
|
||||
=== "LinkedListStack"
|
||||
![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack.png)
|
||||
![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack.png){ class="animation-figure" }
|
||||
|
||||
=== "push()"
|
||||
![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png)
|
||||
![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png){ class="animation-figure" }
|
||||
|
||||
=== "pop()"
|
||||
![linkedlist_stack_pop](stack.assets/linkedlist_stack_pop.png)
|
||||
![linkedlist_stack_pop](stack.assets/linkedlist_stack_pop.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-2 基于链表实现栈的入栈出栈操作 </p>
|
||||
|
||||
|
@ -1089,13 +1089,13 @@ comments: true
|
|||
使用数组实现栈时,我们可以将数组的尾部作为栈顶。如图 5-3 所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。
|
||||
|
||||
=== "ArrayStack"
|
||||
![基于数组实现栈的入栈出栈操作](stack.assets/array_stack.png)
|
||||
![基于数组实现栈的入栈出栈操作](stack.assets/array_stack.png){ class="animation-figure" }
|
||||
|
||||
=== "push()"
|
||||
![array_stack_push](stack.assets/array_stack_push.png)
|
||||
![array_stack_push](stack.assets/array_stack_push.png){ class="animation-figure" }
|
||||
|
||||
=== "pop()"
|
||||
![array_stack_pop](stack.assets/array_stack_pop.png)
|
||||
![array_stack_pop](stack.assets/array_stack_pop.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 5-3 基于数组实现栈的入栈出栈操作 </p>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
|
||||
根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的“映射公式”:**若节点的索引为 $i$ ,则该节点的左子节点索引为 $2i + 1$ ,右子节点索引为 $2i + 2$** 。图 7-12 展示了各个节点索引之间的映射关系。
|
||||
|
||||
![完美二叉树的数组表示](array_representation_of_tree.assets/array_representation_binary_tree.png)
|
||||
![完美二叉树的数组表示](array_representation_of_tree.assets/array_representation_binary_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-12 完美二叉树的数组表示 </p>
|
||||
|
||||
|
@ -26,7 +26,7 @@ comments: true
|
|||
|
||||
如图 7-13 所示,给定一个非完美二叉树,上述的数组表示方法已经失效。
|
||||
|
||||
![层序遍历序列对应多种二叉树可能性](array_representation_of_tree.assets/array_representation_without_empty.png)
|
||||
![层序遍历序列对应多种二叉树可能性](array_representation_of_tree.assets/array_representation_without_empty.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-13 层序遍历序列对应多种二叉树可能性 </p>
|
||||
|
||||
|
@ -126,7 +126,7 @@ comments: true
|
|||
|
||||
```
|
||||
|
||||
![任意类型二叉树的数组表示](array_representation_of_tree.assets/array_representation_with_empty.png)
|
||||
![任意类型二叉树的数组表示](array_representation_of_tree.assets/array_representation_with_empty.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-14 任意类型二叉树的数组表示 </p>
|
||||
|
||||
|
@ -134,7 +134,7 @@ comments: true
|
|||
|
||||
这意味着使用数组表示完全二叉树时,可以省略存储所有 $\text{None}$ ,非常方便。图 7-15 给出了一个例子。
|
||||
|
||||
![完全二叉树的数组表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png)
|
||||
![完全二叉树的数组表示](array_representation_of_tree.assets/array_representation_complete_binary_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-15 完全二叉树的数组表示 </p>
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@ comments: true
|
|||
|
||||
如图 7-24 所示,经过两次删除节点操作,这个二叉搜索树便会退化为链表。
|
||||
|
||||
![AVL 树在删除节点后发生退化](avl_tree.assets/avltree_degradation_from_removing_node.png)
|
||||
![AVL 树在删除节点后发生退化](avl_tree.assets/avltree_degradation_from_removing_node.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-24 AVL 树在删除节点后发生退化 </p>
|
||||
|
||||
再例如,在图 7-25 的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之恶化。
|
||||
|
||||
![AVL 树在插入节点后发生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png)
|
||||
![AVL 树在插入节点后发生退化](avl_tree.assets/avltree_degradation_from_inserting_node.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-25 AVL 树在插入节点后发生退化 </p>
|
||||
|
||||
|
@ -610,22 +610,22 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
|
|||
如图 7-26 所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树,将该节点记为 `node` ,其左子节点记为 `child` ,执行“右旋”操作。完成右旋后,子树已经恢复平衡,并且仍然保持二叉搜索树的特性。
|
||||
|
||||
=== "<1>"
|
||||
![右旋操作步骤](avl_tree.assets/avltree_right_rotate_step1.png)
|
||||
![右旋操作步骤](avl_tree.assets/avltree_right_rotate_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png)
|
||||
![avltree_right_rotate_step2](avl_tree.assets/avltree_right_rotate_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png)
|
||||
![avltree_right_rotate_step3](avl_tree.assets/avltree_right_rotate_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png)
|
||||
![avltree_right_rotate_step4](avl_tree.assets/avltree_right_rotate_step4.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-26 右旋操作步骤 </p>
|
||||
|
||||
如图 7-27 所示,当节点 `child` 有右子节点(记为 `grandChild` )时,需要在右旋中添加一步:将 `grandChild` 作为 `node` 的左子节点。
|
||||
|
||||
![有 grandChild 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png)
|
||||
![有 grandChild 的右旋操作](avl_tree.assets/avltree_right_rotate_with_grandchild.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-27 有 grandChild 的右旋操作 </p>
|
||||
|
||||
|
@ -856,13 +856,13 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
|
|||
|
||||
相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行图 7-28 所示的“左旋”操作。
|
||||
|
||||
![左旋操作](avl_tree.assets/avltree_left_rotate.png)
|
||||
![左旋操作](avl_tree.assets/avltree_left_rotate.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-28 左旋操作 </p>
|
||||
|
||||
同理,如图 7-29 所示,当节点 `child` 有左子节点(记为 `grandChild` )时,需要在左旋中添加一步:将 `grandChild` 作为 `node` 的右子节点。
|
||||
|
||||
![有 grandChild 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png)
|
||||
![有 grandChild 的左旋操作](avl_tree.assets/avltree_left_rotate_with_grandchild.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-29 有 grandChild 的左旋操作 </p>
|
||||
|
||||
|
@ -1093,7 +1093,7 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
|
|||
|
||||
对于图 7-30 中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 `child` 执行“左旋”,再对 `node` 执行“右旋”。
|
||||
|
||||
![先左旋后右旋](avl_tree.assets/avltree_left_right_rotate.png)
|
||||
![先左旋后右旋](avl_tree.assets/avltree_left_right_rotate.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-30 先左旋后右旋 </p>
|
||||
|
||||
|
@ -1101,7 +1101,7 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
|
|||
|
||||
如图 7-31 所示,对于上述失衡二叉树的镜像情况,需要先对 `child` 执行“右旋”,然后对 `node` 执行“左旋”。
|
||||
|
||||
![先右旋后左旋](avl_tree.assets/avltree_right_left_rotate.png)
|
||||
![先右旋后左旋](avl_tree.assets/avltree_right_left_rotate.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-31 先右旋后左旋 </p>
|
||||
|
||||
|
@ -1109,7 +1109,7 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
|
|||
|
||||
图 7-32 展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、左旋、先右后左、先左后右的旋转操作。
|
||||
|
||||
![AVL 树的四种旋转情况](avl_tree.assets/avltree_rotation_cases.png)
|
||||
![AVL 树的四种旋转情况](avl_tree.assets/avltree_rotation_cases.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-32 AVL 树的四种旋转情况 </p>
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ comments: true
|
|||
1. 对于根节点,左子树中所有节点的值 $<$ 根节点的值 $<$ 右子树中所有节点的值。
|
||||
2. 任意节点的左、右子树也是二叉搜索树,即同样满足条件 `1.` 。
|
||||
|
||||
![二叉搜索树](binary_search_tree.assets/binary_search_tree.png)
|
||||
![二叉搜索树](binary_search_tree.assets/binary_search_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-16 二叉搜索树 </p>
|
||||
|
||||
|
@ -26,16 +26,16 @@ comments: true
|
|||
- 若 `cur.val = num` ,说明找到目标节点,跳出循环并返回该节点。
|
||||
|
||||
=== "<1>"
|
||||
![二叉搜索树查找节点示例](binary_search_tree.assets/bst_search_step1.png)
|
||||
![二叉搜索树查找节点示例](binary_search_tree.assets/bst_search_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![bst_search_step2](binary_search_tree.assets/bst_search_step2.png)
|
||||
![bst_search_step2](binary_search_tree.assets/bst_search_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![bst_search_step3](binary_search_tree.assets/bst_search_step3.png)
|
||||
![bst_search_step3](binary_search_tree.assets/bst_search_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![bst_search_step4](binary_search_tree.assets/bst_search_step4.png)
|
||||
![bst_search_step4](binary_search_tree.assets/bst_search_step4.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-17 二叉搜索树查找节点示例 </p>
|
||||
|
||||
|
@ -325,7 +325,7 @@ comments: true
|
|||
1. **查找插入位置**:与查找操作相似,从根节点出发,根据当前节点值和 `num` 的大小关系循环向下搜索,直到越过叶节点(遍历至 $\text{None}$ )时跳出循环。
|
||||
2. **在该位置插入节点**:初始化节点 `num` ,将该节点置于 $\text{None}$ 的位置。
|
||||
|
||||
![在二叉搜索树中插入节点](binary_search_tree.assets/bst_insert.png)
|
||||
![在二叉搜索树中插入节点](binary_search_tree.assets/bst_insert.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-18 在二叉搜索树中插入节点 </p>
|
||||
|
||||
|
@ -753,13 +753,13 @@ comments: true
|
|||
|
||||
如图 7-19 所示,当待删除节点的度为 $0$ 时,表示该节点是叶节点,可以直接删除。
|
||||
|
||||
![在二叉搜索树中删除节点(度为 0 )](binary_search_tree.assets/bst_remove_case1.png)
|
||||
![在二叉搜索树中删除节点(度为 0 )](binary_search_tree.assets/bst_remove_case1.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-19 在二叉搜索树中删除节点(度为 0 ) </p>
|
||||
|
||||
如图 7-20 所示,当待删除节点的度为 $1$ 时,将待删除节点替换为其子节点即可。
|
||||
|
||||
![在二叉搜索树中删除节点(度为 1 )](binary_search_tree.assets/bst_remove_case2.png)
|
||||
![在二叉搜索树中删除节点(度为 1 )](binary_search_tree.assets/bst_remove_case2.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-20 在二叉搜索树中删除节点(度为 1 ) </p>
|
||||
|
||||
|
@ -771,16 +771,16 @@ comments: true
|
|||
2. 将 `tmp` 的值覆盖待删除节点的值,并在树中递归删除节点 `tmp` 。
|
||||
|
||||
=== "<1>"
|
||||
![在二叉搜索树中删除节点(度为 2 )](binary_search_tree.assets/bst_remove_case3_step1.png)
|
||||
![在二叉搜索树中删除节点(度为 2 )](binary_search_tree.assets/bst_remove_case3_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png)
|
||||
![bst_remove_case3_step2](binary_search_tree.assets/bst_remove_case3_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png)
|
||||
![bst_remove_case3_step3](binary_search_tree.assets/bst_remove_case3_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png)
|
||||
![bst_remove_case3_step4](binary_search_tree.assets/bst_remove_case3_step4.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-21 在二叉搜索树中删除节点(度为 2 ) </p>
|
||||
|
||||
|
@ -1464,7 +1464,7 @@ comments: true
|
|||
|
||||
利用中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,无须进行额外的排序操作,非常高效。
|
||||
|
||||
![二叉搜索树的中序遍历序列](binary_search_tree.assets/bst_inorder_traversal.png)
|
||||
![二叉搜索树的中序遍历序列](binary_search_tree.assets/bst_inorder_traversal.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-22 二叉搜索树的中序遍历序列 </p>
|
||||
|
||||
|
@ -1488,7 +1488,7 @@ comments: true
|
|||
|
||||
然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为图 7-23 所示的链表,这时各种操作的时间复杂度也会退化为 $O(n)$ 。
|
||||
|
||||
![二叉搜索树的退化](binary_search_tree.assets/bst_degradation.png)
|
||||
![二叉搜索树的退化](binary_search_tree.assets/bst_degradation.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-23 二叉搜索树的退化 </p>
|
||||
|
||||
|
|
|
@ -191,7 +191,7 @@ comments: true
|
|||
|
||||
**在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如图 7-1 所示,如果将“节点 2”视为父节点,则其左子节点和右子节点分别是“节点 4”和“节点 5”,左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。
|
||||
|
||||
![父节点、子节点、子树](binary_tree.assets/binary_tree_definition.png)
|
||||
![父节点、子节点、子树](binary_tree.assets/binary_tree_definition.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-1 父节点、子节点、子树 </p>
|
||||
|
||||
|
@ -208,7 +208,7 @@ comments: true
|
|||
- 节点的「深度 depth」:从根节点到该节点所经过的边的数量。
|
||||
- 节点的「高度 height」:从距离该节点最远的叶节点到该节点所经过的边的数量。
|
||||
|
||||
![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png)
|
||||
![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-2 二叉树的常用术语 </p>
|
||||
|
||||
|
@ -416,7 +416,7 @@ comments: true
|
|||
|
||||
与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。图 7-3 给出了一个示例。
|
||||
|
||||
![在二叉树中插入与删除节点](binary_tree.assets/binary_tree_add_remove.png)
|
||||
![在二叉树中插入与删除节点](binary_tree.assets/binary_tree_add_remove.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-3 在二叉树中插入与删除节点 </p>
|
||||
|
||||
|
@ -569,7 +569,7 @@ comments: true
|
|||
|
||||
请注意,在中文社区中,完美二叉树常被称为「满二叉树」。
|
||||
|
||||
![完美二叉树](binary_tree.assets/perfect_binary_tree.png)
|
||||
![完美二叉树](binary_tree.assets/perfect_binary_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-4 完美二叉树 </p>
|
||||
|
||||
|
@ -577,7 +577,7 @@ comments: true
|
|||
|
||||
如图 7-5 所示,「完全二叉树 complete binary tree」只有最底层的节点未被填满,且最底层节点尽量靠左填充。
|
||||
|
||||
![完全二叉树](binary_tree.assets/complete_binary_tree.png)
|
||||
![完全二叉树](binary_tree.assets/complete_binary_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-5 完全二叉树 </p>
|
||||
|
||||
|
@ -585,7 +585,7 @@ comments: true
|
|||
|
||||
如图 7-6 所示,「完满二叉树 full binary tree」除了叶节点之外,其余所有节点都有两个子节点。
|
||||
|
||||
![完满二叉树](binary_tree.assets/full_binary_tree.png)
|
||||
![完满二叉树](binary_tree.assets/full_binary_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-6 完满二叉树 </p>
|
||||
|
||||
|
@ -593,7 +593,7 @@ comments: true
|
|||
|
||||
如图 7-7 所示,「平衡二叉树 balanced binary tree」中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。
|
||||
|
||||
![平衡二叉树](binary_tree.assets/balanced_binary_tree.png)
|
||||
![平衡二叉树](binary_tree.assets/balanced_binary_tree.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-7 平衡二叉树 </p>
|
||||
|
||||
|
@ -604,7 +604,7 @@ comments: true
|
|||
- 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。
|
||||
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ 。
|
||||
|
||||
![二叉树的最佳与最差结构](binary_tree.assets/binary_tree_best_worst_cases.png)
|
||||
![二叉树的最佳与最差结构](binary_tree.assets/binary_tree_best_worst_cases.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-8 二叉树的最佳与最差结构 </p>
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ comments: true
|
|||
|
||||
层序遍历本质上属于「广度优先遍历 breadth-first traversal」,它体现了一种“一圈一圈向外扩展”的逐层遍历方式。
|
||||
|
||||
![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png)
|
||||
![二叉树的层序遍历](binary_tree_traversal.assets/binary_tree_bfs.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-9 二叉树的层序遍历 </p>
|
||||
|
||||
|
@ -335,7 +335,7 @@ comments: true
|
|||
|
||||
图 7-10 展示了对二叉树进行深度优先遍历的工作原理。**深度优先遍历就像是绕着整个二叉树的外围“走”一圈**,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。
|
||||
|
||||
![二叉搜索树的前、中、后序遍历](binary_tree_traversal.assets/binary_tree_dfs.png)
|
||||
![二叉搜索树的前、中、后序遍历](binary_tree_traversal.assets/binary_tree_dfs.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-10 二叉搜索树的前、中、后序遍历 </p>
|
||||
|
||||
|
@ -764,37 +764,37 @@ comments: true
|
|||
2. “归”表示函数返回,代表当前节点已经访问完毕。
|
||||
|
||||
=== "<1>"
|
||||
![前序遍历的递归过程](binary_tree_traversal.assets/preorder_step1.png)
|
||||
![前序遍历的递归过程](binary_tree_traversal.assets/preorder_step1.png){ class="animation-figure" }
|
||||
|
||||
=== "<2>"
|
||||
![preorder_step2](binary_tree_traversal.assets/preorder_step2.png)
|
||||
![preorder_step2](binary_tree_traversal.assets/preorder_step2.png){ class="animation-figure" }
|
||||
|
||||
=== "<3>"
|
||||
![preorder_step3](binary_tree_traversal.assets/preorder_step3.png)
|
||||
![preorder_step3](binary_tree_traversal.assets/preorder_step3.png){ class="animation-figure" }
|
||||
|
||||
=== "<4>"
|
||||
![preorder_step4](binary_tree_traversal.assets/preorder_step4.png)
|
||||
![preorder_step4](binary_tree_traversal.assets/preorder_step4.png){ class="animation-figure" }
|
||||
|
||||
=== "<5>"
|
||||
![preorder_step5](binary_tree_traversal.assets/preorder_step5.png)
|
||||
![preorder_step5](binary_tree_traversal.assets/preorder_step5.png){ class="animation-figure" }
|
||||
|
||||
=== "<6>"
|
||||
![preorder_step6](binary_tree_traversal.assets/preorder_step6.png)
|
||||
![preorder_step6](binary_tree_traversal.assets/preorder_step6.png){ class="animation-figure" }
|
||||
|
||||
=== "<7>"
|
||||
![preorder_step7](binary_tree_traversal.assets/preorder_step7.png)
|
||||
![preorder_step7](binary_tree_traversal.assets/preorder_step7.png){ class="animation-figure" }
|
||||
|
||||
=== "<8>"
|
||||
![preorder_step8](binary_tree_traversal.assets/preorder_step8.png)
|
||||
![preorder_step8](binary_tree_traversal.assets/preorder_step8.png){ class="animation-figure" }
|
||||
|
||||
=== "<9>"
|
||||
![preorder_step9](binary_tree_traversal.assets/preorder_step9.png)
|
||||
![preorder_step9](binary_tree_traversal.assets/preorder_step9.png){ class="animation-figure" }
|
||||
|
||||
=== "<10>"
|
||||
![preorder_step10](binary_tree_traversal.assets/preorder_step10.png)
|
||||
![preorder_step10](binary_tree_traversal.assets/preorder_step10.png){ class="animation-figure" }
|
||||
|
||||
=== "<11>"
|
||||
![preorder_step11](binary_tree_traversal.assets/preorder_step11.png)
|
||||
![preorder_step11](binary_tree_traversal.assets/preorder_step11.png){ class="animation-figure" }
|
||||
|
||||
<p align="center"> 图 7-11 前序遍历的递归过程 </p>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ icon: material/graph-outline
|
|||
|
||||
<div class="center-table" markdown>
|
||||
|
||||
![树](../assets/covers/chapter_tree.jpg){ width="600" }
|
||||
![树](../assets/covers/chapter_tree.jpg){ class="cover-image" }
|
||||
|
||||
</div>
|
||||
|
||||
|
|
|
@ -8,8 +8,8 @@ hide:
|
|||
<h1 align="center"> </h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="index.assets/conceptual_rendering.png" width="250">
|
||||
<img src="index.assets/hello_algo_mindmap_tp.png" width="400">
|
||||
<img src="index.assets/conceptual_rendering.png" width="200">
|
||||
<img src="index.assets/hello_algo_mindmap_tp.png" width="320">
|
||||
</p>
|
||||
|
||||
<h2 align="center"> 《 Hello 算法 》</h2>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
--md-default-fg-color: #adbac7;
|
||||
--md-default-bg-color: #22272e;
|
||||
|
||||
--md-code-bg-color: #1D2126;
|
||||
--md-code-bg-color: #1d2126;
|
||||
--md-code-fg-color: #adbac7;
|
||||
|
||||
--md-accent-fg-color: #aaa;
|
||||
|
@ -43,6 +43,23 @@
|
|||
color: var(--md-default-fg-color) !important;
|
||||
}
|
||||
|
||||
/* Figure class */
|
||||
.animation-figure {
|
||||
border-radius: 0.63rem;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Cover image class */
|
||||
.cover-image {
|
||||
width: 28rem;
|
||||
height: auto;
|
||||
border-radius: 0.3rem;
|
||||
box-shadow: var(--md-shadow-z2);
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Center Markdown Tables (requires md_in_html extension) */
|
||||
.center-table {
|
||||
text-align: center;
|
||||
|
@ -82,12 +99,6 @@
|
|||
text-transform: none;
|
||||
}
|
||||
|
||||
/* Image align center */
|
||||
.center {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* font-family setting for Win10 */
|
||||
body {
|
||||
--md-text-font-family: -apple-system, BlinkMacSystemFont,
|
||||
|
|
Loading…
Reference in a new issue