This commit is contained in:
krahets 2023-11-09 05:13:48 +08:00
parent 9701430089
commit 0105644232
83 changed files with 516 additions and 509 deletions

View file

@ -22,7 +22,7 @@ comments: true
2. 修改 Markdown 源文件内容,检查内容的正确性,并尽量保持排版格式的统一。 2. 修改 Markdown 源文件内容,检查内容的正确性,并尽量保持排版格式的统一。
3. 在页面底部填写修改说明然后点击“Propose file change”按钮。页面跳转后点击“Create pull request”按钮即可发起拉取请求。 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 &nbsp; 页面编辑按键 </p> <p align="center"> 图 16-1 &nbsp; 页面编辑按键 </p>

View file

@ -7,7 +7,7 @@ icon: material/help-circle-outline
<div class="center-table" markdown> <div class="center-table" markdown>
![附录](../assets/covers/chapter_appendix.jpg){ width="600" } ![附录](../assets/covers/chapter_appendix.jpg){ class="cover-image" }
</div> </div>

View file

@ -6,7 +6,7 @@ comments: true
「数组 array」是一种线性数据结构其将相同类型元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的「索引 index」。图 4-1 展示了数组的主要术语和概念。 「数组 array」是一种线性数据结构其将相同类型元素存储在连续的内存空间中。我们将元素在数组中的位置称为该元素的「索引 index」。图 4-1 展示了数组的主要术语和概念。
![数组定义与存储方式](array.assets/array_definition.png) ![数组定义与存储方式](array.assets/array_definition.png){ class="animation-figure" }
<p align="center"> 图 4-1 &nbsp; 数组定义与存储方式 </p> <p align="center"> 图 4-1 &nbsp; 数组定义与存储方式 </p>
@ -123,7 +123,7 @@ comments: true
数组元素被存储在连续的内存空间中,这意味着计算数组元素的内存地址非常容易。给定数组内存地址(即首元素内存地址)和某个元素的索引,我们可以使用图 4-2 所示的公式计算得到该元素的内存地址,从而直接访问此元素。 数组元素被存储在连续的内存空间中,这意味着计算数组元素的内存地址非常容易。给定数组内存地址(即首元素内存地址)和某个元素的索引,我们可以使用图 4-2 所示的公式计算得到该元素的内存地址,从而直接访问此元素。
![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png) ![数组元素的内存地址计算](array.assets/array_memory_location_calculation.png){ class="animation-figure" }
<p align="center"> 图 4-2 &nbsp; 数组元素的内存地址计算 </p> <p align="center"> 图 4-2 &nbsp; 数组元素的内存地址计算 </p>
@ -291,7 +291,7 @@ comments: true
数组元素在内存中是“紧挨着的”,它们之间没有空间再存放任何数据。如图 4-3 所示,如果想要在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。 数组元素在内存中是“紧挨着的”,它们之间没有空间再存放任何数据。如图 4-3 所示,如果想要在数组中间插入一个元素,则需要将该元素之后的所有元素都向后移动一位,之后再把元素赋值给该索引。
![数组插入元素示例](array.assets/array_insert_element.png) ![数组插入元素示例](array.assets/array_insert_element.png){ class="animation-figure" }
<p align="center"> 图 4-3 &nbsp; 数组插入元素示例 </p> <p align="center"> 图 4-3 &nbsp; 数组插入元素示例 </p>
@ -468,7 +468,7 @@ comments: true
同理,如图 4-4 所示,若想要删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。 同理,如图 4-4 所示,若想要删除索引 $i$ 处的元素,则需要把索引 $i$ 之后的元素都向前移动一位。
![数组删除元素示例](array.assets/array_remove_element.png) ![数组删除元素示例](array.assets/array_remove_element.png){ class="animation-figure" }
<p align="center"> 图 4-4 &nbsp; 数组删除元素示例 </p> <p align="center"> 图 4-4 &nbsp; 数组删除元素示例 </p>

View file

@ -5,11 +5,7 @@ icon: material/view-list-outline
# 第 4 章 &nbsp; 数组与链表 # 第 4 章 &nbsp; 数组与链表
<div class="center-table" markdown> ![数组与链表](../assets/covers/chapter_array_and_linkedlist.jpg){ class="cover-image" }
![数组与链表](../assets/covers/chapter_array_and_linkedlist.jpg){ width="600" }
</div>
!!! abstract !!! abstract

View file

@ -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 &nbsp; 链表定义与存储方式 </p> <p align="center"> 图 4-5 &nbsp; 链表定义与存储方式 </p>
@ -405,7 +405,7 @@ comments: true
相比之下,在数组中插入元素的时间复杂度为 $O(n)$ ,在大数据量下的效率较低。 相比之下,在数组中插入元素的时间复杂度为 $O(n)$ ,在大数据量下的效率较低。
![链表插入节点示例](linked_list.assets/linkedlist_insert_node.png) ![链表插入节点示例](linked_list.assets/linkedlist_insert_node.png){ class="animation-figure" }
<p align="center"> 图 4-6 &nbsp; 链表插入节点示例 </p> <p align="center"> 图 4-6 &nbsp; 链表插入节点示例 </p>
@ -547,7 +547,7 @@ comments: true
请注意,尽管在删除操作完成后节点 `P` 仍然指向 `n1` ,但实际上遍历此链表已经无法访问到 `P` ,这意味着 `P` 已经不再属于该链表了。 请注意,尽管在删除操作完成后节点 `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 &nbsp; 链表删除节点 </p> <p align="center"> 图 4-7 &nbsp; 链表删除节点 </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 &nbsp; 常见链表种类 </p> <p align="center"> 图 4-8 &nbsp; 常见链表种类 </p>

View file

@ -206,7 +206,7 @@ comments: true
[class]{}-[func]{preOrder} [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 &nbsp; 在前序遍历中搜索节点 </p> <p align="center"> 图 13-1 &nbsp; 在前序遍历中搜索节点 </p>
@ -477,37 +477,37 @@ comments: true
观察图 13-2 所示的过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为逆向的。 观察图 13-2 所示的过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为逆向的。
=== "<1>" === "<1>"
![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png) ![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 尝试与回退 </p> <p align="center"> 图 13-2 &nbsp; 尝试与回退 </p>
@ -781,7 +781,7 @@ comments: true
剪枝是一个非常形象的名词。如图 13-3 所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。 剪枝是一个非常形象的名词。如图 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 &nbsp; 根据约束条件剪枝 </p> <p align="center"> 图 13-3 &nbsp; 根据约束条件剪枝 </p>
@ -1657,7 +1657,7 @@ comments: true
根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。图 13-4 对比了保留或删除 `return` 语句的搜索过程。 根据题意,我们在找到值为 $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 &nbsp; 保留与删除 return 的搜索过程对比 </p> <p align="center"> 图 13-4 &nbsp; 保留与删除 return 的搜索过程对比 </p>

View file

@ -7,7 +7,7 @@ icon: material/map-marker-path
<div class="center-table" markdown> <div class="center-table" markdown>
![回溯](../assets/covers/chapter_backtracking.jpg){ width="600" } ![回溯](../assets/covers/chapter_backtracking.jpg){ class="cover-image" }
</div> </div>

View file

@ -10,13 +10,13 @@ comments: true
如图 13-15 所示,当 $n = 4$ 时,共可以找到两个解。从回溯算法的角度看,$n \times n$ 大小的棋盘共有 $n^2$ 个格子,给出了所有的选择 `choices` 。在逐个放置皇后的过程中,棋盘状态在不断地变化,每个时刻的棋盘就是状态 `state` 如图 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 &nbsp; 4 皇后问题的解 </p> <p align="center"> 图 13-15 &nbsp; 4 皇后问题的解 </p>
图 13-16 展示了本题的三个约束条件:**多个皇后不能在同一行、同一列、同一对角线**。值得注意的是,对角线分为主对角线 `\` 和次对角线 `/` 两种。 图 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 &nbsp; n 皇后问题的约束条件 </p> <p align="center"> 图 13-16 &nbsp; n 皇后问题的约束条件 </p>
@ -28,7 +28,7 @@ comments: true
如图 13-17 所示,为 $4$ 皇后问题的逐行放置过程。受画幅限制,图 13-17 仅展开了第一行的其中一个搜索分支,并且将不满足列约束和对角线约束的方案都进行了剪枝。 如图 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 &nbsp; 逐行放置策略 </p> <p align="center"> 图 13-17 &nbsp; 逐行放置策略 </p>
@ -44,7 +44,7 @@ comments: true
同理,**次对角线上的所有格子的 $row + col$ 是恒定值**。我们同样也可以借助数组 `diags2` 来处理次对角线约束。 同理,**次对角线上的所有格子的 $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 &nbsp; 处理列约束和对角线约束 </p> <p align="center"> 图 13-18 &nbsp; 处理列约束和对角线约束 </p>

View file

@ -32,7 +32,7 @@ comments: true
如图 13-5 所示,我们可以将搜索过程展开成一个递归树,树中的每个节点代表当前状态 `state` 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。 如图 13-5 所示,我们可以将搜索过程展开成一个递归树,树中的每个节点代表当前状态 `state` 。从根节点开始,经过三轮选择后到达叶节点,每个叶节点都对应一个排列。
![全排列的递归树](permutations_problem.assets/permutations_i.png) ![全排列的递归树](permutations_problem.assets/permutations_i.png){ class="animation-figure" }
<p align="center"> 图 13-5 &nbsp; 全排列的递归树 </p> <p align="center"> 图 13-5 &nbsp; 全排列的递归树 </p>
@ -45,7 +45,7 @@ comments: true
如图 13-6 所示,假设我们第一轮选择 1 ,第二轮选择 3 ,第三轮选择 2 ,则需要在第二轮剪掉元素 1 的分支,在第三轮剪掉元素 1 和元素 3 的分支。 如图 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 &nbsp; 全排列剪枝示例 </p> <p align="center"> 图 13-6 &nbsp; 全排列剪枝示例 </p>
@ -481,7 +481,7 @@ comments: true
如图 13-7 所示,上述方法生成的排列有一半都是重复的。 如图 13-7 所示,上述方法生成的排列有一半都是重复的。
![重复排列](permutations_problem.assets/permutations_ii.png) ![重复排列](permutations_problem.assets/permutations_ii.png){ class="animation-figure" }
<p align="center"> 图 13-7 &nbsp; 重复排列 </p> <p align="center"> 图 13-7 &nbsp; 重复排列 </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 &nbsp; 重复排列剪枝 </p> <p align="center"> 图 13-8 &nbsp; 重复排列剪枝 </p>
@ -955,6 +955,6 @@ comments: true
图 13-9 展示了两个剪枝条件的生效范围。注意,树中的每个节点代表一个选择,从根节点到叶节点的路径上的各个节点构成一个排列。 图 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 &nbsp; 两种剪枝条件的作用范围 </p> <p align="center"> 图 13-9 &nbsp; 两种剪枝条件的作用范围 </p>

View file

@ -432,7 +432,7 @@ comments: true
这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如图 13-10 所示,先选 $4$ 后选 $5$ 与先选 $5$ 后选 $4$ 是两个不同的分支,但两者对应同一个子集。 这是因为搜索过程是区分选择顺序的,然而子集不区分选择顺序。如图 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 &nbsp; 子集搜索与越界剪枝 </p> <p align="center"> 图 13-10 &nbsp; 子集搜索与越界剪枝 </p>
@ -454,7 +454,7 @@ comments: true
2. 前两轮选择 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。 2. 前两轮选择 $4$ 和 $5$ ,生成子集 $[4, 5, \dots]$ 。
3. 若第一轮选择 $5$ **则第二轮应该跳过 $3$ 和 $4$** ,因为子集 $[5, 3, \dots]$ 和 $[5, 4, \dots]$ 与第 `1.``2.` 步中描述的子集完全重复。 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 &nbsp; 不同选择顺序导致的重复子集 </p> <p align="center"> 图 13-11 &nbsp; 不同选择顺序导致的重复子集 </p>
@ -908,7 +908,7 @@ comments: true
如图 13-12 所示,为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入到以上代码后的整体回溯过程。 如图 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 &nbsp; 子集和 I 回溯过程 </p> <p align="center"> 图 13-12 &nbsp; 子集和 I 回溯过程 </p>
@ -922,7 +922,7 @@ comments: true
**造成这种重复的原因是相等元素在某轮中被多次选择**。在图 13-13 中,第一轮共有三个选择,其中两个都为 $4$ ,会产生两个重复的搜索分支,从而输出重复子集;同理,第二轮的两个 $4$ 也会产生重复子集。 **造成这种重复的原因是相等元素在某轮中被多次选择**。在图 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 &nbsp; 相等元素导致的重复子集 </p> <p align="center"> 图 13-13 &nbsp; 相等元素导致的重复子集 </p>
@ -1427,6 +1427,6 @@ comments: true
图 13-14 展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 图 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 &nbsp; 子集和 II 回溯过程 </p> <p align="center"> 图 13-14 &nbsp; 子集和 II 回溯过程 </p>

View file

@ -7,7 +7,7 @@ icon: material/timer-sand
<div class="center-table" markdown> <div class="center-table" markdown>
![复杂度分析](../assets/covers/chapter_complexity_analysis.jpg){ width="600" } ![复杂度分析](../assets/covers/chapter_complexity_analysis.jpg){ class="cover-image" }
</div> </div>

View file

@ -184,7 +184,7 @@ comments: true
图 2-1 展示了该求和函数的流程框图。 图 2-1 展示了该求和函数的流程框图。
![求和函数的流程框图](iteration_and_recursion.assets/iteration.png) ![求和函数的流程框图](iteration_and_recursion.assets/iteration.png){ class="animation-figure" }
<p align="center"> 图 2-1 &nbsp; 求和函数的流程框图 </p> <p align="center"> 图 2-1 &nbsp; 求和函数的流程框图 </p>
@ -823,7 +823,7 @@ comments: true
图 2-2 给出了该嵌套循环的流程框图。 图 2-2 给出了该嵌套循环的流程框图。
![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png) ![嵌套循环的流程框图](iteration_and_recursion.assets/nested_iteration.png){ class="animation-figure" }
<p align="center"> 图 2-2 &nbsp; 嵌套循环的流程框图 </p> <p align="center"> 图 2-2 &nbsp; 嵌套循环的流程框图 </p>
@ -1028,7 +1028,7 @@ comments: true
图 2-3 展示了该函数的递归过程。 图 2-3 展示了该函数的递归过程。
![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png) ![求和函数的递归过程](iteration_and_recursion.assets/recursion_sum.png){ class="animation-figure" }
<p align="center"> 图 2-3 &nbsp; 求和函数的递归过程 </p> <p align="center"> 图 2-3 &nbsp; 求和函数的递归过程 </p>
@ -1051,7 +1051,7 @@ comments: true
如图 2-4 所示,在触发终止条件前,同时存在 $n$ 个未返回的递归函数,**递归深度为 $n$** 。 如图 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 &nbsp; 递归调用深度 </p> <p align="center"> 图 2-4 &nbsp; 递归调用深度 </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 &nbsp; 尾递归过程 </p> <p align="center"> 图 2-5 &nbsp; 尾递归过程 </p>
@ -1432,7 +1432,7 @@ comments: true
观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一个层数为 $n$ 的「递归树 recursion tree」。 观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 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 &nbsp; 斐波那契数列的递归树 </p> <p align="center"> 图 2-6 &nbsp; 斐波那契数列的递归树 </p>

View file

@ -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 &nbsp; 算法使用的相关空间 </p> <p align="center"> 图 2-15 &nbsp; 算法使用的相关空间 </p>
@ -717,7 +717,7 @@ O(1) < O(\log n) < O(n) < O(n^2) < O(2^n) \newline
\end{aligned} \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 &nbsp; 常见的空间复杂度类型 </p> <p align="center"> 图 2-16 &nbsp; 常见的空间复杂度类型 </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 &nbsp; 递归函数产生的线性阶空间复杂度 </p> <p align="center"> 图 2-17 &nbsp; 递归函数产生的线性阶空间复杂度 </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 &nbsp; 递归函数产生的平方阶空间复杂度 </p> <p align="center"> 图 2-18 &nbsp; 递归函数产生的平方阶空间复杂度 </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 &nbsp; 满二叉树产生的指数阶空间复杂度 </p> <p align="center"> 图 2-19 &nbsp; 满二叉树产生的指数阶空间复杂度 </p>

View file

@ -462,7 +462,7 @@ $$
- 算法 `B` 中的打印操作需要循环 $n$ 次,算法运行时间随着 $n$ 增大呈线性增长。此算法的时间复杂度被称为“线性阶”。 - 算法 `B` 中的打印操作需要循环 $n$ 次,算法运行时间随着 $n$ 增大呈线性增长。此算法的时间复杂度被称为“线性阶”。
- 算法 `C` 中的打印操作需要循环 $1000000$ 次,虽然运行时间很长,但它与输入数据大小 $n$ 无关。因此 `C` 的时间复杂度和 `A` 相同,仍为“常数阶”。 - 算法 `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 &nbsp; 算法 A、B 和 C 的时间增长趋势 </p> <p align="center"> 图 2-7 &nbsp; 算法 A、B 和 C 的时间增长趋势 </p>
@ -661,7 +661,7 @@ $T(n)$ 是一次函数,说明其运行时间的增长趋势是线性的,因
如图 2-8 所示,计算渐近上界就是寻找一个函数 $f(n)$ ,使得当 $n$ 趋向于无穷大时,$T(n)$ 和 $f(n)$ 处于相同的增长级别,仅相差一个常数项 $c$ 的倍数。 如图 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 &nbsp; 函数的渐近上界 </p> <p align="center"> 图 2-8 &nbsp; 函数的渐近上界 </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} \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 &nbsp; 常见的时间复杂度类型 </p> <p align="center"> 图 2-9 &nbsp; 常见的时间复杂度类型 </p>
@ -1642,7 +1642,7 @@ $$
图 2-10 对比了常数阶、线性阶和平方阶三种时间复杂度。 图 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 &nbsp; 常数阶、线性阶和平方阶的时间复杂度 </p> <p align="center"> 图 2-10 &nbsp; 常数阶、线性阶和平方阶的时间复杂度 </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 &nbsp; 指数阶的时间复杂度 </p> <p align="center"> 图 2-11 &nbsp; 指数阶的时间复杂度 </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 &nbsp; 对数阶的时间复杂度 </p> <p align="center"> 图 2-12 &nbsp; 对数阶的时间复杂度 </p>
@ -2790,7 +2790,7 @@ $$
图 2-13 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。 图 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 &nbsp; 线性对数阶的时间复杂度 </p> <p align="center"> 图 2-13 &nbsp; 线性对数阶的时间复杂度 </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 &nbsp; 阶乘阶的时间复杂度 </p> <p align="center"> 图 2-14 &nbsp; 阶乘阶的时间复杂度 </p>

View file

@ -10,7 +10,7 @@ comments: true
「ASCII 码」是最早出现的字符集,全称为“美国标准信息交换代码”。它使用 7 位二进制数(即一个字节的低 7 位)表示一个字符,最多能够表示 128 个不同的字符。如图 3-6 所示ASCII 码包括英文字母的大小写、数字 0 ~ 9、一些标点符号以及一些控制字符如换行符和制表符 「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 &nbsp; ASCII 码 </p> <p align="center"> 图 3-6 &nbsp; ASCII 码 </p>
@ -38,7 +38,7 @@ Unicode 是一种字符集标准,本质上是给每个字符分配一个编号
对于以上问题,**一种直接的解决方案是将所有字符存储为等长的编码**。如图 3-7 所示“Hello”中的每个字符占用 1 字节,“算法”中的每个字符占用 2 字节。我们可以通过高位填 0 将“Hello 算法”中的所有字符都编码为 2 字节长度。这样系统就可以每隔 2 字节解析一个字符,恢复出这个短语的内容了。 对于以上问题,**一种直接的解决方案是将所有字符存储为等长的编码**。如图 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 &nbsp; Unicode 编码示例 </p> <p align="center"> 图 3-7 &nbsp; Unicode 编码示例 </p>
@ -59,7 +59,7 @@ UTF-8 的编码规则并不复杂,分为以下两种情况。
之所以将 $10$ 当作校验符,是因为在 UTF-8 编码规则下,不可能有字符的最高两位是 $10$ 。这个结论可以用反证法来证明:假设一个字符的最高两位是 $10$ ,说明该字符的长度为 $1$ ,对应 ASCII 码。而 ASCII 码的最高位应该是 $0$ ,与假设矛盾。 之所以将 $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 &nbsp; UTF-8 编码示例 </p> <p align="center"> 图 3-8 &nbsp; UTF-8 编码示例 </p>

View file

@ -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 &nbsp; 线性与非线性数据结构 </p> <p align="center"> 图 3-1 &nbsp; 线性与非线性数据结构 </p>
@ -33,7 +33,7 @@ comments: true
**系统通过内存地址来访问目标位置的数据**。如图 3-2 所示,计算机根据特定规则为表格中的每个单元格分配编号,确保每个内存空间都有唯一的内存地址。有了这些地址,程序便可以访问内存中的数据。 **系统通过内存地址来访问目标位置的数据**。如图 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 &nbsp; 内存条、内存空间、内存地址 </p> <p align="center"> 图 3-2 &nbsp; 内存条、内存空间、内存地址 </p>
@ -41,7 +41,7 @@ comments: true
如图 3-3 所示,**物理结构反映了数据在计算机内存中的存储方式**,可分为连续空间存储(数组)和分散空间存储(链表)。物理结构从底层决定了数据的访问、更新、增删等操作方法,同时在时间效率和空间效率方面呈现出互补的特点。 如图 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 &nbsp; 连续空间存储与分散空间存储 </p> <p align="center"> 图 3-3 &nbsp; 连续空间存储与分散空间存储 </p>

View file

@ -7,7 +7,7 @@ icon: material/shape-outline
<div class="center-table" markdown> <div class="center-table" markdown>
![数据结构](../assets/covers/chapter_data_structure.jpg){ width="600" } ![数据结构](../assets/covers/chapter_data_structure.jpg){ class="cover-image" }
</div> </div>

View file

@ -20,7 +20,7 @@ comments: true
图 3-4 展示了原码、反码和补码之间的转换方法。 图 3-4 展示了原码、反码和补码之间的转换方法。
![原码、反码与补码之间的相互转换](number_encoding.assets/1s_2s_complement.png) ![原码、反码与补码之间的相互转换](number_encoding.assets/1s_2s_complement.png){ class="animation-figure" }
<p align="center"> 图 3-4 &nbsp; 原码、反码与补码之间的相互转换 </p> <p align="center"> 图 3-4 &nbsp; 原码、反码与补码之间的相互转换 </p>
@ -129,7 +129,7 @@ $$
\end{aligned} \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 &nbsp; IEEE 754 标准下的 float 的计算示例 </p> <p align="center"> 图 3-5 &nbsp; IEEE 754 标准下的 float 的计算示例 </p>

View file

@ -40,7 +40,7 @@ comments: true
图 12-4 展示了在数组中二分查找元素 $6$ 的分治过程。 图 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 &nbsp; 二分查找的分治过程 </p> <p align="center"> 图 12-4 &nbsp; 二分查找的分治过程 </p>

View file

@ -8,7 +8,7 @@ comments: true
给定一个二叉树的前序遍历 `preorder` 和中序遍历 `inorder` ,请从中构建二叉树,返回二叉树的根节点。假设二叉树中没有值重复的节点。 给定一个二叉树的前序遍历 `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 &nbsp; 构建二叉树的示例数据 </p> <p align="center"> 图 12-5 &nbsp; 构建二叉树的示例数据 </p>
@ -35,7 +35,7 @@ comments: true
2. 查找根节点 3 在 `inorder` 中的索引,利用该索引可将 `inorder` 划分为 `[ 9 | 3 1 2 7 ]` 2. 查找根节点 3 在 `inorder` 中的索引,利用该索引可将 `inorder` 划分为 `[ 9 | 3 1 2 7 ]`
3. 根据 `inorder` 划分结果,易得左子树和右子树的节点数量分别为 1 和 3 ,从而可将 `preorder` 划分为 `[ 3 | 9 | 2 1 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 &nbsp; 在前序和中序遍历中划分子树 </p> <p align="center"> 图 12-6 &nbsp; 在前序和中序遍历中划分子树 </p>
@ -63,7 +63,7 @@ comments: true
请注意,右子树根节点索引中的 $(m-l)$ 的含义是“左子树的节点数量”,建议配合图 12-7 理解。 请注意,右子树根节点索引中的 $(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 &nbsp; 根节点和左右子树的索引区间表示 </p> <p align="center"> 图 12-7 &nbsp; 根节点和左右子树的索引区间表示 </p>
@ -448,37 +448,37 @@ comments: true
图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(即引用)是在向上“归”的过程中建立的。 图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(即引用)是在向上“归”的过程中建立的。
=== "<1>" === "<1>"
![构建二叉树的递归过程](build_binary_tree_problem.assets/built_tree_step1.png) ![构建二叉树的递归过程](build_binary_tree_problem.assets/built_tree_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 构建二叉树的递归过程 </p> <p align="center"> 图 12-8 &nbsp; 构建二叉树的递归过程 </p>
每个递归函数内的前序遍历 `preorder` 和中序遍历 `inorder` 的划分结果如图 12-9 所示。 每个递归函数内的前序遍历 `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 &nbsp; 每个递归函数中的划分结果 </p> <p align="center"> 图 12-9 &nbsp; 每个递归函数中的划分结果 </p>

View file

@ -14,7 +14,7 @@ comments: true
1. **分**:递归地将原数组(原问题)划分为两个子数组(子问题),直到子数组只剩一个元素(最小子问题)。 1. **分**:递归地将原数组(原问题)划分为两个子数组(子问题),直到子数组只剩一个元素(最小子问题)。
2. **治**:从底至顶地将有序的子数组(子问题的解)进行合并,从而得到有序的原数组(原问题的解)。 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 &nbsp; 归并排序的分治策略 </p> <p align="center"> 图 12-1 &nbsp; 归并排序的分治策略 </p>
@ -46,7 +46,7 @@ $$
O(n + (\frac{n}{2})^2 \times 2 + n) = O(\frac{n^2}{2} + 2n) 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 &nbsp; 划分数组前后的冒泡排序 </p> <p align="center"> 图 12-2 &nbsp; 划分数组前后的冒泡排序 </p>
@ -74,7 +74,7 @@ $$
比如在图 12-3 所示的“桶排序”中,我们将海量的数据平均分配到各个桶中,则可所有桶的排序任务分散到各个计算单元,完成后再进行结果合并。 比如在图 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 &nbsp; 桶排序的并行计算 </p> <p align="center"> 图 12-3 &nbsp; 桶排序的并行计算 </p>

View file

@ -14,7 +14,7 @@ comments: true
2. 每次只能移动一个圆盘。 2. 每次只能移动一个圆盘。
3. 小圆盘必须时刻位于大圆盘之上。 3. 小圆盘必须时刻位于大圆盘之上。
![汉诺塔问题示例](hanota_problem.assets/hanota_example.png) ![汉诺塔问题示例](hanota_problem.assets/hanota_example.png){ class="animation-figure" }
<p align="center"> 图 12-10 &nbsp; 汉诺塔问题示例 </p> <p align="center"> 图 12-10 &nbsp; 汉诺塔问题示例 </p>
@ -25,10 +25,10 @@ comments: true
如图 12-11 所示,对于问题 $f(1)$ ,即当只有一个圆盘时,我们将它直接从 `A` 移动至 `C` 即可。 如图 12-11 所示,对于问题 $f(1)$ ,即当只有一个圆盘时,我们将它直接从 `A` 移动至 `C` 即可。
=== "<1>" === "<1>"
![规模为 1 问题的解](hanota_problem.assets/hanota_f1_step1.png) ![规模为 1 问题的解](hanota_problem.assets/hanota_f1_step1.png){ class="animation-figure" }
=== "<2>" === "<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 &nbsp; 规模为 1 问题的解 </p> <p align="center"> 图 12-11 &nbsp; 规模为 1 问题的解 </p>
@ -39,16 +39,16 @@ comments: true
3. 最后将小圆盘从 `B` 移至 `C` 3. 最后将小圆盘从 `B` 移至 `C`
=== "<1>" === "<1>"
![规模为 2 问题的解](hanota_problem.assets/hanota_f2_step1.png) ![规模为 2 问题的解](hanota_problem.assets/hanota_f2_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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 &nbsp; 规模为 2 问题的解 </p> <p align="center"> 图 12-12 &nbsp; 规模为 2 问题的解 </p>
@ -65,16 +65,16 @@ comments: true
3. 令 `C` 为目标柱、`A` 为缓冲柱,将两个圆盘从 `B` 移动至 `C` 3. 令 `C` 为目标柱、`A` 为缓冲柱,将两个圆盘从 `B` 移动至 `C`
=== "<1>" === "<1>"
![规模为 3 问题的解](hanota_problem.assets/hanota_f3_step1.png) ![规模为 3 问题的解](hanota_problem.assets/hanota_f3_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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 &nbsp; 规模为 3 问题的解 </p> <p align="center"> 图 12-13 &nbsp; 规模为 3 问题的解 </p>
@ -88,7 +88,7 @@ comments: true
对于这两个子问题 $f(n-1)$ **可以通过相同的方式进行递归划分**,直至达到最小子问题 $f(1)$ 。而 $f(1)$ 的解是已知的,只需一次移动操作即可。 对于这两个子问题 $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 &nbsp; 汉诺塔问题的分治策略 </p> <p align="center"> 图 12-14 &nbsp; 汉诺塔问题的分治策略 </p>
@ -485,7 +485,7 @@ comments: true
如图 12-15 所示,汉诺塔问题形成一个高度为 $n$ 的递归树,每个节点代表一个子问题、对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 如图 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 &nbsp; 汉诺塔问题的递归树 </p> <p align="center"> 图 12-15 &nbsp; 汉诺塔问题的递归树 </p>

View file

@ -7,7 +7,7 @@ icon: material/set-split
<div class="center-table" markdown> <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> </div>

View file

@ -22,7 +22,7 @@ comments: true
如图 14-6 所示,若第 $1$、$2$、$3$ 阶的代价分别为 $1$、$10$、$1$ ,则从地面爬到第 $3$ 阶的最小代价为 $2$ 。 如图 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 &nbsp; 爬到第 3 阶的最小代价 </p> <p align="center"> 图 14-6 &nbsp; 爬到第 3 阶的最小代价 </p>
@ -300,7 +300,7 @@ $$
图 14-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 &nbsp; 爬楼梯最小代价的动态规划过程 </p> <p align="center"> 图 14-7 &nbsp; 爬楼梯最小代价的动态规划过程 </p>
@ -545,7 +545,7 @@ $$
例如图 14-8 ,爬上第 $3$ 阶仅剩 $2$ 种可行方案,其中连续三次跳 $1$ 阶的方案不满足约束条件,因此被舍弃。 例如图 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 &nbsp; 带约束爬到第 3 阶的方案数量 </p> <p align="center"> 图 14-8 &nbsp; 带约束爬到第 3 阶的方案数量 </p>
@ -567,7 +567,7 @@ dp[i, 2] = dp[i-2, 1] + dp[i-2, 2]
\end{cases} \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 &nbsp; 考虑约束下的递推关系 </p> <p align="center"> 图 14-9 &nbsp; 考虑约束下的递推关系 </p>

View file

@ -41,7 +41,7 @@ comments: true
图 14-10 展示了一个例子,给定网格的最小路径和为 $13$ 。 图 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 &nbsp; 最小路径和示例数据 </p> <p align="center"> 图 14-10 &nbsp; 最小路径和示例数据 </p>
@ -53,7 +53,7 @@ comments: true
至此,我们就得到了图 14-11 所示的二维 $dp$ 矩阵,其尺寸与输入网格 $grid$ 相同。 至此,我们就得到了图 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 &nbsp; 状态定义与 dp 表 </p> <p align="center"> 图 14-11 &nbsp; 状态定义与 dp 表 </p>
@ -73,7 +73,7 @@ $$
dp[i, j] = \min(dp[i-1, j], dp[i, j-1]) + grid[i, j] 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 &nbsp; 最优子结构与状态转移方程 </p> <p align="center"> 图 14-12 &nbsp; 最优子结构与状态转移方程 </p>
@ -89,7 +89,7 @@ $$
如图 14-13 所示,由于每个格子是由其左方格子和上方格子转移而来,因此我们使用采用循环来遍历矩阵,外循环遍历各行、内循环遍历各列。 如图 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 &nbsp; 边界条件与状态转移顺序 </p> <p align="center"> 图 14-13 &nbsp; 边界条件与状态转移顺序 </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 &nbsp; 暴力搜索递归树 </p> <p align="center"> 图 14-14 &nbsp; 暴力搜索递归树 </p>
@ -697,7 +697,7 @@ $$
如图 14-15 所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 如图 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 &nbsp; 记忆化搜索递归树 </p> <p align="center"> 图 14-15 &nbsp; 记忆化搜索递归树 </p>
@ -1039,40 +1039,40 @@ $$
数组 `dp` 大小为 $n \times m$ **因此空间复杂度为 $O(nm)$** 。 数组 `dp` 大小为 $n \times m$ **因此空间复杂度为 $O(nm)$** 。
=== "<1>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 最小路径和的动态规划过程 </p> <p align="center"> 图 14-16 &nbsp; 最小路径和的动态规划过程 </p>

View file

@ -14,7 +14,7 @@ comments: true
如图 14-27 所示,将 `kitten` 转换为 `sitting` 需要编辑 3 步,包括 2 次替换操作与 1 次添加操作;将 `hello` 转换为 `algo` 需要 3 步,包括 2 次替换操作和 1 次删除操作。 如图 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 &nbsp; 编辑距离的示例数据 </p> <p align="center"> 图 14-27 &nbsp; 编辑距离的示例数据 </p>
@ -24,7 +24,7 @@ comments: true
从决策树的角度看,本题的目标是求解节点 `hello` 和节点 `algo` 之间的最短路径。 从决策树的角度看,本题的目标是求解节点 `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 &nbsp; 基于决策树模型表示编辑距离问题 </p> <p align="center"> 图 14-28 &nbsp; 基于决策树模型表示编辑距离问题 </p>
@ -53,7 +53,7 @@ comments: true
2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。 2. 删除 $s[i-1]$ ,则剩余子问题 $dp[i-1, j]$ 。
3. 将 $s[i-1]$ 替换为 $t[j-1]$ ,则剩余子问题 $dp[i-1, j-1]$ 。 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 &nbsp; 编辑距离的状态转移 </p> <p align="center"> 图 14-29 &nbsp; 编辑距离的状态转移 </p>
@ -446,49 +446,49 @@ $$
如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作是填写一个二维网格的过程。 如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作是填写一个二维网格的过程。
=== "<1>" === "<1>"
![编辑距离的动态规划过程](edit_distance_problem.assets/edit_distance_dp_step1.png) ![编辑距离的动态规划过程](edit_distance_problem.assets/edit_distance_dp_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 编辑距离的动态规划过程 </p> <p align="center"> 图 14-30 &nbsp; 编辑距离的动态规划过程 </p>

View file

@ -7,7 +7,7 @@ icon: material/table-pivot
<div class="center-table" markdown> <div class="center-table" markdown>
![动态规划](../assets/covers/chapter_dynamic_programming.jpg){ width="600" } ![动态规划](../assets/covers/chapter_dynamic_programming.jpg){ class="cover-image" }
</div> </div>

View file

@ -14,7 +14,7 @@ comments: true
如图 14-1 所示,对于一个 $3$ 阶楼梯,共有 $3$ 种方案可以爬到楼顶。 如图 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 &nbsp; 爬到第 3 阶的方案数量 </p> <p align="center"> 图 14-1 &nbsp; 爬到第 3 阶的方案数量 </p>
@ -405,7 +405,7 @@ $$
这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。图 14-2 展示了该递推关系。 这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。图 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 &nbsp; 方案数量递推关系 </p> <p align="center"> 图 14-2 &nbsp; 方案数量递推关系 </p>
@ -640,7 +640,7 @@ $$
图 14-3 展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 图 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 &nbsp; 爬楼梯对应递归树 </p> <p align="center"> 图 14-3 &nbsp; 爬楼梯对应递归树 </p>
@ -975,7 +975,7 @@ $$
观察图 14-4 **经过记忆化处理后,所有重叠子问题都只需被计算一次,时间复杂度被优化至 $O(n)$** ,这是一个巨大的飞跃。 观察图 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 &nbsp; 记忆化搜索对应递归树 </p> <p align="center"> 图 14-4 &nbsp; 记忆化搜索对应递归树 </p>
@ -1229,7 +1229,7 @@ $$
图 14-5 模拟了以上代码的执行过程。 图 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 &nbsp; 爬楼梯的动态规划过程 </p> <p align="center"> 图 14-5 &nbsp; 爬楼梯的动态规划过程 </p>

View file

@ -14,7 +14,7 @@ comments: true
观察图 14-17 ,由于物品编号 $i$ 从 $1$ 开始计数,数组索引从 $0$ 开始计数,因此物品 $i$ 对应重量 $wgt[i-1]$ 和价值 $val[i-1]$ 。 观察图 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 &nbsp; 0-1 背包的示例数据 </p> <p align="center"> 图 14-17 &nbsp; 0-1 背包的示例数据 </p>
@ -320,7 +320,7 @@ $$
观察递归树,容易发现其中存在重叠子问题,例如 $dp[1, 10]$ 等。而当物品较多、背包容量较大,尤其是相同重量的物品较多时,重叠子问题的数量将会大幅增多。 观察递归树,容易发现其中存在重叠子问题,例如 $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 &nbsp; 0-1 背包的暴力搜索递归树 </p> <p align="center"> 图 14-18 &nbsp; 0-1 背包的暴力搜索递归树 </p>
@ -656,7 +656,7 @@ $$
图 14-19 展示了在记忆化递归中被剪掉的搜索分支。 图 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 &nbsp; 0-1 背包的记忆化搜索递归树 </p> <p align="center"> 图 14-19 &nbsp; 0-1 背包的记忆化搜索递归树 </p>
@ -969,46 +969,46 @@ $$
如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。
=== "<1>" === "<1>"
![0-1 背包的动态规划过程](knapsack_problem.assets/knapsack_dp_step1.png) ![0-1 背包的动态规划过程](knapsack_problem.assets/knapsack_dp_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 0-1 背包的动态规划过程 </p> <p align="center"> 图 14-20 &nbsp; 0-1 背包的动态规划过程 </p>
@ -1024,22 +1024,22 @@ $$
图 14-21 展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。 图 14-21 展示了在单个数组下从第 $i = 1$ 行转换至第 $i = 2$ 行的过程。请思考正序遍历和倒序遍历的区别。
=== "<1>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 0-1 背包的空间优化后的动态规划过程 </p> <p align="center"> 图 14-21 &nbsp; 0-1 背包的空间优化后的动态规划过程 </p>

View file

@ -12,7 +12,7 @@ comments: true
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。**每个物品可以重复选取**,问在不超过背包容量下能放入物品的最大价值。 给定 $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 &nbsp; 完全背包问题的示例数据 </p> <p align="center"> 图 14-22 &nbsp; 完全背包问题的示例数据 </p>
@ -347,22 +347,22 @@ $$
这个遍历顺序与 0-1 背包正好相反。请借助图 14-23 来理解两者的区别。 这个遍历顺序与 0-1 背包正好相反。请借助图 14-23 来理解两者的区别。
=== "<1>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 完全背包的空间优化后的动态规划过程 </p> <p align="center"> 图 14-23 &nbsp; 完全背包的空间优化后的动态规划过程 </p>
@ -666,7 +666,7 @@ $$
给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ **每种硬币可以重复选取**,问能够凑出目标金额的最少硬币个数。如果无法凑出目标金额则返回 $-1$ 。 给定 $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 &nbsp; 零钱兑换问题的示例数据 </p> <p align="center"> 图 14-24 &nbsp; 零钱兑换问题的示例数据 </p>
@ -1070,49 +1070,49 @@ $$
图 14-25 展示了零钱兑换的动态规划过程,和完全背包非常相似。 图 14-25 展示了零钱兑换的动态规划过程,和完全背包非常相似。
=== "<1>" === "<1>"
![零钱兑换问题的动态规划过程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png) ![零钱兑换问题的动态规划过程](unbounded_knapsack_problem.assets/coin_change_dp_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 零钱兑换问题的动态规划过程 </p> <p align="center"> 图 14-25 &nbsp; 零钱兑换问题的动态规划过程 </p>
@ -1450,7 +1450,7 @@ $$
给定 $n$ 种硬币,第 $i$ 种硬币的面值为 $coins[i - 1]$ ,目标金额为 $amt$ ,每种硬币可以重复选取,**问在凑出目标金额的硬币组合数量**。 给定 $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 &nbsp; 零钱兑换问题 II 的示例数据 </p> <p align="center"> 图 14-26 &nbsp; 零钱兑换问题 II 的示例数据 </p>

View file

@ -16,7 +16,7 @@ $$
如果将顶点看作节点,将边看作连接各个节点的引用(指针),我们就可以将图看作是一种从链表拓展而来的数据结构。如图 9-1 所示,**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高**,从而更为复杂。 如果将顶点看作节点,将边看作连接各个节点的引用(指针),我们就可以将图看作是一种从链表拓展而来的数据结构。如图 9-1 所示,**相较于线性关系(链表)和分治关系(树),网络关系(图)的自由度更高**,从而更为复杂。
![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png) ![链表、树、图之间的关系](graph.assets/linkedlist_tree_graph.png){ class="animation-figure" }
<p align="center"> 图 9-1 &nbsp; 链表、树、图之间的关系 </p> <p align="center"> 图 9-1 &nbsp; 链表、树、图之间的关系 </p>
@ -27,7 +27,7 @@ $$
- 在无向图中,边表示两顶点之间的“双向”连接关系,例如微信或 QQ 中的“好友关系”。 - 在无向图中,边表示两顶点之间的“双向”连接关系,例如微信或 QQ 中的“好友关系”。
- 在有向图中,边具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。 - 在有向图中,边具有方向性,即 $A \rightarrow B$ 和 $A \leftarrow B$ 两个方向的边是相互独立的,例如微博或抖音上的“关注”与“被关注”关系。
![有向图与无向图](graph.assets/directed_graph.png) ![有向图与无向图](graph.assets/directed_graph.png){ class="animation-figure" }
<p align="center"> 图 9-2 &nbsp; 有向图与无向图 </p> <p align="center"> 图 9-2 &nbsp; 有向图与无向图 </p>
@ -36,13 +36,13 @@ $$
- 对于连通图,从某个顶点出发,可以到达其余任意顶点。 - 对于连通图,从某个顶点出发,可以到达其余任意顶点。
- 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。 - 对于非连通图,从某个顶点出发,至少有一个顶点无法到达。
![连通图与非连通图](graph.assets/connected_graph.png) ![连通图与非连通图](graph.assets/connected_graph.png){ class="animation-figure" }
<p align="center"> 图 9-3 &nbsp; 连通图与非连通图 </p> <p align="center"> 图 9-3 &nbsp; 连通图与非连通图 </p>
我们还可以为边添加“权重”变量,从而得到图 9-4 所示的「有权图 weighted graph」。例如在王者荣耀等手游中系统会根据共同游戏时间来计算玩家之间的“亲密度”这种亲密度网络就可以用有权图来表示。 我们还可以为边添加“权重”变量,从而得到图 9-4 所示的「有权图 weighted graph」。例如在王者荣耀等手游中系统会根据共同游戏时间来计算玩家之间的“亲密度”这种亲密度网络就可以用有权图来表示。
![有权图与无权图](graph.assets/weighted_graph.png) ![有权图与无权图](graph.assets/weighted_graph.png){ class="animation-figure" }
<p align="center"> 图 9-4 &nbsp; 有权图与无权图 </p> <p align="center"> 图 9-4 &nbsp; 有权图与无权图 </p>
@ -62,7 +62,7 @@ $$
如图 9-5 所示,设邻接矩阵为 $M$、顶点列表为 $V$ ,那么矩阵元素 $M[i, j] = 1$ 表示顶点 $V[i]$ 到顶点 $V[j]$ 之间存在边,反之 $M[i, j] = 0$ 表示两顶点之间无边。 如图 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 &nbsp; 图的邻接矩阵表示 </p> <p align="center"> 图 9-5 &nbsp; 图的邻接矩阵表示 </p>
@ -78,7 +78,7 @@ $$
「邻接表 adjacency list」使用 $n$ 个链表来表示图,链表节点表示顶点。第 $i$ 条链表对应顶点 $i$ ,其中存储了该顶点的所有邻接顶点(即与该顶点相连的顶点)。图 9-6 展示了一个使用邻接表存储的图的示例。 「邻接表 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 &nbsp; 图的邻接表表示 </p> <p align="center"> 图 9-6 &nbsp; 图的邻接表表示 </p>

View file

@ -16,19 +16,19 @@ comments: true
- **初始化**:传入 $n$ 个顶点,初始化长度为 $n$ 的顶点列表 `vertices` ,使用 $O(n)$ 时间;初始化 $n \times n$ 大小的邻接矩阵 `adjMat` ,使用 $O(n^2)$ 时间。 - **初始化**:传入 $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 &nbsp; 邻接矩阵的初始化、增删边、增删顶点 </p> <p align="center"> 图 9-7 &nbsp; 邻接矩阵的初始化、增删边、增删顶点 </p>
@ -1056,19 +1056,19 @@ comments: true
- **初始化**:在邻接表中创建 $n$ 个顶点和 $2m$ 条边,使用 $O(n + m)$ 时间。 - **初始化**:在邻接表中创建 $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 &nbsp; 邻接表的初始化、增删边、增删顶点 </p> <p align="center"> 图 9-8 &nbsp; 邻接表的初始化、增删边、增删顶点 </p>

View file

@ -12,7 +12,7 @@ comments: true
**广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张**。如图 9-9 所示,从左上角顶点出发,先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。 **广度优先遍历是一种由近及远的遍历方式,从某个节点出发,始终优先访问距离最近的顶点,并一层层向外扩张**。如图 9-9 所示,从左上角顶点出发,先遍历该顶点的所有邻接顶点,然后遍历下一个顶点的所有邻接顶点,以此类推,直至所有顶点访问完毕。
![图的广度优先遍历](graph_traversal.assets/graph_bfs.png) ![图的广度优先遍历](graph_traversal.assets/graph_bfs.png){ class="animation-figure" }
<p align="center"> 图 9-9 &nbsp; 图的广度优先遍历 </p> <p align="center"> 图 9-9 &nbsp; 图的广度优先遍历 </p>
@ -421,37 +421,37 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这
代码相对抽象,建议对照图 9-10 来加深理解。 代码相对抽象,建议对照图 9-10 来加深理解。
=== "<1>" === "<1>"
![图的广度优先遍历步骤](graph_traversal.assets/graph_bfs_step1.png) ![图的广度优先遍历步骤](graph_traversal.assets/graph_bfs_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 图的广度优先遍历步骤 </p> <p align="center"> 图 9-10 &nbsp; 图的广度优先遍历步骤 </p>
@ -469,7 +469,7 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这
**深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。如图 9-11 所示,从左上角顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。 **深度优先遍历是一种优先走到底、无路可走再回头的遍历方式**。如图 9-11 所示,从左上角顶点出发,访问当前顶点的某个邻接顶点,直到走到尽头时返回,再继续走到尽头并返回,以此类推,直至所有顶点遍历完成。
![图的深度优先遍历](graph_traversal.assets/graph_dfs.png) ![图的深度优先遍历](graph_traversal.assets/graph_dfs.png){ class="animation-figure" }
<p align="center"> 图 9-11 &nbsp; 图的深度优先遍历 </p> <p align="center"> 图 9-11 &nbsp; 图的深度优先遍历 </p>
@ -829,37 +829,37 @@ BFS 通常借助队列来实现。队列具有“先入先出”的性质,这
为了加深理解,建议将图示与代码结合起来,在脑中(或者用笔画下来)模拟整个 DFS 过程,包括每个递归方法何时开启、何时返回。 为了加深理解,建议将图示与代码结合起来,在脑中(或者用笔画下来)模拟整个 DFS 过程,包括每个递归方法何时开启、何时返回。
=== "<1>" === "<1>"
![图的深度优先遍历步骤](graph_traversal.assets/graph_dfs_step1.png) ![图的深度优先遍历步骤](graph_traversal.assets/graph_dfs_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 图的深度优先遍历步骤 </p> <p align="center"> 图 9-12 &nbsp; 图的深度优先遍历步骤 </p>

View file

@ -7,7 +7,7 @@ icon: material/graphql
<div class="center-table" markdown> <div class="center-table" markdown>
![](../assets/covers/chapter_graph.jpg){ width="600" } ![](../assets/covers/chapter_graph.jpg){ class="cover-image" }
</div> </div>

View file

@ -8,7 +8,7 @@ comments: true
给定 $n$ 个物品,第 $i$ 个物品的重量为 $wgt[i-1]$、价值为 $val[i-1]$ ,和一个容量为 $cap$ 的背包。每个物品只能选择一次,**但可以选择物品的一部分,价值根据选择的重量比例计算**,问在不超过背包容量下背包中物品的最大价值。 给定 $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 &nbsp; 分数背包问题的示例数据 </p> <p align="center"> 图 15-3 &nbsp; 分数背包问题的示例数据 </p>
@ -19,7 +19,7 @@ comments: true
1. 对于物品 $i$ ,它在单位重量下的价值为 $val[i-1] / wgt[i-1]$ ,简称为单位价值。 1. 对于物品 $i$ ,它在单位重量下的价值为 $val[i-1] / wgt[i-1]$ ,简称为单位价值。
2. 假设放入一部分物品 $i$ ,重量为 $w$ ,则背包增加的价值为 $w \times 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 &nbsp; 物品在单位重量下的价值 </p> <p align="center"> 图 15-4 &nbsp; 物品在单位重量下的价值 </p>
@ -31,7 +31,7 @@ comments: true
2. 遍历所有物品,**每轮贪心地选择单位价值最高的物品**。 2. 遍历所有物品,**每轮贪心地选择单位价值最高的物品**。
3. 若剩余背包容量不足,则使用当前物品的一部分填满背包即可。 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 &nbsp; 分数背包的贪心策略 </p> <p align="center"> 图 15-5 &nbsp; 分数背包的贪心策略 </p>
@ -483,6 +483,6 @@ comments: true
如图 15-6 所示,如果将物品重量和物品单位价值分别看作一个 2D 图表的横轴和纵轴,则分数背包问题可被转化为“求在有限横轴区间下的最大围成面积”。这个类比可以帮助我们从几何角度理解贪心策略的有效性。 如图 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 &nbsp; 分数背包问题的几何表示 </p> <p align="center"> 图 15-6 &nbsp; 分数背包问题的几何表示 </p>

View file

@ -19,7 +19,7 @@ comments: true
本题的贪心策略如图 15-1 所示。给定目标金额,**我们贪心地选择不大于且最接近它的硬币**,不断循环该步骤,直至凑出目标金额为止。 本题的贪心策略如图 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 &nbsp; 零钱兑换的贪心策略 </p> <p align="center"> 图 15-1 &nbsp; 零钱兑换的贪心策略 </p>
@ -299,7 +299,7 @@ comments: true
- **反例 $coins = [1, 20, 50]$**:假设 $amt = 60$ ,贪心算法只能找到 $50 + 1 \times 10$ 的兑换组合,共计 $11$ 枚硬币,但动态规划可以找到最优解 $20 + 20 + 20$ ,仅需 $3$ 枚硬币。 - **反例 $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$ 枚硬币。 - **反例 $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 &nbsp; 贪心无法找出最优解的示例 </p> <p align="center"> 图 15-2 &nbsp; 贪心无法找出最优解的示例 </p>

View file

@ -7,7 +7,7 @@ icon: material/head-heart-outline
<div class="center-table" markdown> <div class="center-table" markdown>
![贪心](../assets/covers/chapter_greedy.jpg){ width="600" } ![贪心](../assets/covers/chapter_greedy.jpg){ class="cover-image" }
</div> </div>

View file

@ -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 &nbsp; 最大容量问题的示例数据 </p> <p align="center"> 图 15-7 &nbsp; 最大容量问题的示例数据 </p>
@ -30,7 +30,7 @@ $$
这道题还有更高效率的解法。如图 15-8 所示,现选取一个状态 $[i, j]$ ,其满足索引 $i < j$ 且高度 $ht[i] < ht[j]$ $i$ 为短板$j$ 为长板 这道题还有更高效率的解法。如图 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 &nbsp; 初始状态 </p> <p align="center"> 图 15-8 &nbsp; 初始状态 </p>
@ -38,13 +38,13 @@ $$
这是因为在移动长板 $j$ 后,宽度 $j-i$ 肯定变小;而高度由短板决定,因此高度只可能不变( $i$ 仍为短板)或变小(移动后的 $j$ 成为短板)。 这是因为在移动长板 $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 &nbsp; 向内移动长板后的状态 </p> <p align="center"> 图 15-9 &nbsp; 向内移动长板后的状态 </p>
反向思考,**我们只有向内收缩短板 $i$ ,才有可能使容量变大**。因为虽然宽度一定变小,**但高度可能会变大**(移动后的短板 $i$ 可能会变长)。例如在图 15-10 中,移动短板后面积变大。 反向思考,**我们只有向内收缩短板 $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 &nbsp; 向内移动短板后的状态 </p> <p align="center"> 图 15-10 &nbsp; 向内移动短板后的状态 </p>
@ -58,31 +58,31 @@ $$
4. 循环执行第 `2.``3.` 步,直至 $i$ 和 $j$ 相遇时结束。 4. 循环执行第 `2.``3.` 步,直至 $i$ 和 $j$ 相遇时结束。
=== "<1>" === "<1>"
![最大容量问题的贪心过程](max_capacity_problem.assets/max_capacity_greedy_step1.png) ![最大容量问题的贪心过程](max_capacity_problem.assets/max_capacity_greedy_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 最大容量问题的贪心过程 </p> <p align="center"> 图 15-11 &nbsp; 最大容量问题的贪心过程 </p>
@ -384,7 +384,7 @@ $$
cap[i, i+1], cap[i, i+2], \dots, cap[i, j-2], cap[i, j-1] 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 &nbsp; 移动短板导致被跳过的状态 </p> <p align="center"> 图 15-12 &nbsp; 移动短板导致被跳过的状态 </p>

View file

@ -8,7 +8,7 @@ comments: true
给定一个正整数 $n$ ,将其切分为至少两个正整数的和,求切分后所有整数的乘积最大是多少。 给定一个正整数 $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 &nbsp; 最大切分乘积的问题定义 </p> <p align="center"> 图 15-13 &nbsp; 最大切分乘积的问题定义 </p>
@ -42,7 +42,7 @@ $$
**贪心策略一**:如果切分方案中包含 $\geq 4$ 的因子,那么它就应该被继续切分。最终的切分方案只应出现 $1$、$2$、$3$ 这三种因子。 **贪心策略一**:如果切分方案中包含 $\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 &nbsp; 切分导致乘积变大 </p> <p align="center"> 图 15-14 &nbsp; 切分导致乘积变大 </p>
@ -52,7 +52,7 @@ $$
**贪心策略二**:在切分方案中,最多只应存在两个 $2$ 。因为三个 $2$ 总是可以被替换为两个 $3$ ,从而获得更大乘积。 **贪心策略二**:在切分方案中,最多只应存在两个 $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 &nbsp; 最优切分因子 </p> <p align="center"> 图 15-15 &nbsp; 最优切分因子 </p>
@ -349,7 +349,7 @@ $$
[class]{}-[func]{maxProductCutting} [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 &nbsp; 最大切分乘积的计算方法 </p> <p align="center"> 图 15-16 &nbsp; 最大切分乘积的计算方法 </p>

View file

@ -8,7 +8,7 @@ comments: true
如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如图 6-8 所示,对于链地址哈希表,理想情况下键值对平均分布在各个桶中,达到最佳查询效率;最差情况下所有键值对都被存储到同一个桶中,时间复杂度退化至 $O(n)$ 。 如果哈希冲突过于频繁,哈希表的性能则会急剧劣化。如图 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 &nbsp; 哈希冲突的最佳与最差情况 </p> <p align="center"> 图 6-8 &nbsp; 哈希冲突的最佳与最差情况 </p>

View file

@ -17,7 +17,7 @@ comments: true
在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 separate chaining」将单个元素转换为链表将键值对作为链表节点将所有发生冲突的键值对都存储在同一链表中。图 6-5 展示了一个链式地址哈希表的例子。 在原始哈希表中,每个桶仅能存储一个键值对。「链式地址 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 &nbsp; 链式地址哈希表 </p> <p align="center"> 图 6-5 &nbsp; 链式地址哈希表 </p>
@ -1333,7 +1333,7 @@ comments: true
图 6-6 展示了开放寻址(线性探测)哈希表的键值对分布。根据此哈希函数,最后两位相同的 `key` 都会被映射到相同的桶。而通过线性探测,它们被依次存储在该桶以及之下的桶中。 图 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 &nbsp; 开放寻址和线性探测 </p> <p align="center"> 图 6-6 &nbsp; 开放寻址和线性探测 </p>
@ -1341,7 +1341,7 @@ comments: true
值得注意的是,**我们不能在开放寻址哈希表中直接删除元素**。这是因为删除元素会在数组内产生一个空桶 $\text{None}$ ,而当查询元素时,线性探测到该空桶就会返回,因此在该空桶之下的元素都无法再被访问到,程序可能误判这些元素不存在。 值得注意的是,**我们不能在开放寻址哈希表中直接删除元素**。这是因为删除元素会在数组内产生一个空桶 $\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 &nbsp; 在开放寻址中删除元素导致的查询问题 </p> <p align="center"> 图 6-7 &nbsp; 在开放寻址中删除元素导致的查询问题 </p>

View file

@ -8,7 +8,7 @@ comments: true
如图 6-1 所示,给定 $n$ 个学生,每个学生都有“姓名”和“学号”两项数据。假如我们希望实现“输入一个学号,返回对应的姓名”的查询功能,则可以采用图 6-1 所示的哈希表来实现。 如图 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 &nbsp; 哈希表的抽象表示 </p> <p align="center"> 图 6-1 &nbsp; 哈希表的抽象表示 </p>
@ -493,7 +493,7 @@ index = hash(key) % capacity
设数组长度 `capacity = 100`、哈希算法 `hash(key) = key` ,易得哈希函数为 `key % 100` 。图 6-2 以 `key` 学号和 `value` 姓名为例,展示了哈希函数的工作原理。 设数组长度 `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 &nbsp; 哈希函数工作原理 </p> <p align="center"> 图 6-2 &nbsp; 哈希函数工作原理 </p>
@ -1650,7 +1650,7 @@ index = hash(key) % capacity
如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为「哈希冲突 hash collision」。 如图 6-3 所示,两个学号指向了同一个姓名,这显然是不对的。我们将这种多个输入对应同一输出的情况称为「哈希冲突 hash collision」。
![哈希冲突示例](hash_map.assets/hash_collision.png) ![哈希冲突示例](hash_map.assets/hash_collision.png){ class="animation-figure" }
<p align="center"> 图 6-3 &nbsp; 哈希冲突示例 </p> <p align="center"> 图 6-3 &nbsp; 哈希冲突示例 </p>
@ -1658,7 +1658,7 @@ index = hash(key) % capacity
如图 6-4 所示,扩容前键值对 `(136, A)``(236, D)` 发生冲突,扩容后冲突消失。 如图 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 &nbsp; 哈希表扩容 </p> <p align="center"> 图 6-4 &nbsp; 哈希表扩容 </p>

View file

@ -7,7 +7,7 @@ icon: material/table-search
<div class="center-table" markdown> <div class="center-table" markdown>
![哈希表](../assets/covers/chapter_hashing.jpg){ width="600" } ![哈希表](../assets/covers/chapter_hashing.jpg){ class="cover-image" }
</div> </div>

View file

@ -213,7 +213,7 @@ comments: true
接下来我们来进行更为准确的计算。为了减小计算难度,假设给定一个节点数量为 $n$ ,高度为 $h$ 的“完美二叉树”,该假设不会影响计算结果的正确性。 接下来我们来进行更为准确的计算。为了减小计算难度,假设给定一个节点数量为 $n$ ,高度为 $h$ 的“完美二叉树”,该假设不会影响计算结果的正确性。
![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png) ![完美二叉树的各层节点数量](build_heap.assets/heapify_operations_count.png){ class="animation-figure" }
<p align="center"> 图 8-5 &nbsp; 完美二叉树的各层节点数量 </p> <p align="center"> 图 8-5 &nbsp; 完美二叉树的各层节点数量 </p>

View file

@ -9,7 +9,7 @@ comments: true
- 「大顶堆 max heap」任意节点的值 $\geq$ 其子节点的值。 - 「大顶堆 max heap」任意节点的值 $\geq$ 其子节点的值。
- 「小顶堆 min heap」任意节点的值 $\leq$ 其子节点的值。 - 「小顶堆 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 &nbsp; 小顶堆与大顶堆 </p> <p align="center"> 图 8-1 &nbsp; 小顶堆与大顶堆 </p>
@ -367,7 +367,7 @@ comments: true
如图 8-2 所示,给定索引 $i$ ,其左子节点索引为 $2i + 1$ ,右子节点索引为 $2i + 2$ ,父节点索引为 $(i - 1) / 2$(向下取整)。当索引越界时,表示空节点或节点不存在。 如图 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 &nbsp; 堆的表示与存储 </p> <p align="center"> 图 8-2 &nbsp; 堆的表示与存储 </p>
@ -718,31 +718,31 @@ comments: true
考虑从入堆节点开始,**从底至顶执行堆化**。如图 8-3 所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。 考虑从入堆节点开始,**从底至顶执行堆化**。如图 8-3 所示,我们比较插入节点与其父节点的值,如果插入节点更大,则将它们交换。然后继续执行此操作,从底至顶修复堆中的各个节点,直至越过根节点或遇到无须交换的节点时结束。
=== "<1>" === "<1>"
![元素入堆步骤](heap.assets/heap_push_step1.png) ![元素入堆步骤](heap.assets/heap_push_step1.png){ class="animation-figure" }
=== "<2>" === "<2>"
![heap_push_step2](heap.assets/heap_push_step2.png) ![heap_push_step2](heap.assets/heap_push_step2.png){ class="animation-figure" }
=== "<3>" === "<3>"
![heap_push_step3](heap.assets/heap_push_step3.png) ![heap_push_step3](heap.assets/heap_push_step3.png){ class="animation-figure" }
=== "<4>" === "<4>"
![heap_push_step4](heap.assets/heap_push_step4.png) ![heap_push_step4](heap.assets/heap_push_step4.png){ class="animation-figure" }
=== "<5>" === "<5>"
![heap_push_step5](heap.assets/heap_push_step5.png) ![heap_push_step5](heap.assets/heap_push_step5.png){ class="animation-figure" }
=== "<6>" === "<6>"
![heap_push_step6](heap.assets/heap_push_step6.png) ![heap_push_step6](heap.assets/heap_push_step6.png){ class="animation-figure" }
=== "<7>" === "<7>"
![heap_push_step7](heap.assets/heap_push_step7.png) ![heap_push_step7](heap.assets/heap_push_step7.png){ class="animation-figure" }
=== "<8>" === "<8>"
![heap_push_step8](heap.assets/heap_push_step8.png) ![heap_push_step8](heap.assets/heap_push_step8.png){ class="animation-figure" }
=== "<9>" === "<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 &nbsp; 元素入堆步骤 </p> <p align="center"> 图 8-3 &nbsp; 元素入堆步骤 </p>
@ -1095,34 +1095,34 @@ comments: true
如图 8-4 所示,**“从顶至底堆化”的操作方向与“从底至顶堆化”相反**,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。 如图 8-4 所示,**“从顶至底堆化”的操作方向与“从底至顶堆化”相反**,我们将根节点的值与其两个子节点的值进行比较,将最大的子节点与根节点交换。然后循环执行此操作,直到越过叶节点或遇到无须交换的节点时结束。
=== "<1>" === "<1>"
![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png) ![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png){ class="animation-figure" }
=== "<2>" === "<2>"
![heap_pop_step2](heap.assets/heap_pop_step2.png) ![heap_pop_step2](heap.assets/heap_pop_step2.png){ class="animation-figure" }
=== "<3>" === "<3>"
![heap_pop_step3](heap.assets/heap_pop_step3.png) ![heap_pop_step3](heap.assets/heap_pop_step3.png){ class="animation-figure" }
=== "<4>" === "<4>"
![heap_pop_step4](heap.assets/heap_pop_step4.png) ![heap_pop_step4](heap.assets/heap_pop_step4.png){ class="animation-figure" }
=== "<5>" === "<5>"
![heap_pop_step5](heap.assets/heap_pop_step5.png) ![heap_pop_step5](heap.assets/heap_pop_step5.png){ class="animation-figure" }
=== "<6>" === "<6>"
![heap_pop_step6](heap.assets/heap_pop_step6.png) ![heap_pop_step6](heap.assets/heap_pop_step6.png){ class="animation-figure" }
=== "<7>" === "<7>"
![heap_pop_step7](heap.assets/heap_pop_step7.png) ![heap_pop_step7](heap.assets/heap_pop_step7.png){ class="animation-figure" }
=== "<8>" === "<8>"
![heap_pop_step8](heap.assets/heap_pop_step8.png) ![heap_pop_step8](heap.assets/heap_pop_step8.png){ class="animation-figure" }
=== "<9>" === "<9>"
![heap_pop_step9](heap.assets/heap_pop_step9.png) ![heap_pop_step9](heap.assets/heap_pop_step9.png){ class="animation-figure" }
=== "<10>" === "<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 &nbsp; 堆顶元素出堆步骤 </p> <p align="center"> 图 8-4 &nbsp; 堆顶元素出堆步骤 </p>

View file

@ -7,7 +7,7 @@ icon: material/family-tree
<div class="center-table" markdown> <div class="center-table" markdown>
![](../assets/covers/chapter_heap.jpg){ width="600" } ![](../assets/covers/chapter_heap.jpg){ class="cover-image" }
</div> </div>

View file

@ -16,7 +16,7 @@ comments: true
此方法只适用于 $k \ll n$ 的情况,因为当 $k$ 与 $n$ 比较接近时,其时间复杂度趋向于 $O(n^2)$ ,非常耗时。 此方法只适用于 $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 &nbsp; 遍历寻找最大的 k 个元素 </p> <p align="center"> 图 8-6 &nbsp; 遍历寻找最大的 k 个元素 </p>
@ -30,7 +30,7 @@ comments: true
显然,该方法“超额”完成任务了,因为我们只需要找出最大的 $k$ 个元素即可,而不需要排序其他元素。 显然,该方法“超额”完成任务了,因为我们只需要找出最大的 $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 &nbsp; 排序寻找最大的 k 个元素 </p> <p align="center"> 图 8-7 &nbsp; 排序寻找最大的 k 个元素 </p>
@ -44,31 +44,31 @@ comments: true
4. 遍历完成后,堆中保存的就是最大的 $k$ 个元素。 4. 遍历完成后,堆中保存的就是最大的 $k$ 个元素。
=== "<1>" === "<1>"
![基于堆寻找最大的 k 个元素](top_k.assets/top_k_heap_step1.png) ![基于堆寻找最大的 k 个元素](top_k.assets/top_k_heap_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 基于堆寻找最大的 k 个元素 </p> <p align="center"> 图 8-8 &nbsp; 基于堆寻找最大的 k 个元素 </p>

View file

@ -15,19 +15,19 @@ comments: true
3. 不断重复步骤 `1.` 和 步骤 `2.` ,直至找到拼音首字母为 $r$ 的页码为止。 3. 不断重复步骤 `1.` 和 步骤 `2.` ,直至找到拼音首字母为 $r$ 的页码为止。
=== "<1>" === "<1>"
![查字典步骤](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png) ![查字典步骤](algorithms_are_everywhere.assets/binary_search_dictionary_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 查字典步骤 </p> <p align="center"> 图 1-1 &nbsp; 查字典步骤 </p>
@ -39,7 +39,7 @@ comments: true
2. 在无序部分抽出一张扑克牌,插入至有序部分的正确位置;完成后最左 2 张扑克已经有序。 2. 在无序部分抽出一张扑克牌,插入至有序部分的正确位置;完成后最左 2 张扑克已经有序。
3. 不断循环步骤 `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 &nbsp; 扑克排序步骤 </p> <p align="center"> 图 1-2 &nbsp; 扑克排序步骤 </p>
@ -53,7 +53,7 @@ comments: true
4. 从剩余可选项中拿出最大的 $1$ 元,剩余 $1 - 1 = 0$ 元。 4. 从剩余可选项中拿出最大的 $1$ 元,剩余 $1 - 1 = 0$ 元。
5. 完成找零,方案为 $20 + 10 + 1 = 31$ 元。 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 &nbsp; 货币找零过程 </p> <p align="center"> 图 1-3 &nbsp; 货币找零过程 </p>

View file

@ -7,7 +7,7 @@ icon: material/calculator-variant-outline
<div class="center-table" markdown> <div class="center-table" markdown>
![初识算法](../assets/covers/chapter_introduction.jpg){ width="600" } ![初识算法](../assets/covers/chapter_introduction.jpg){ class="cover-image" }
</div> </div>

View file

@ -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 &nbsp; 数据结构与算法的关系 </p> <p align="center"> 图 1-4 &nbsp; 数据结构与算法的关系 </p>
数据结构与算法犹如图 1-5 所示的拼装积木。一套积木,除了包含许多零件之外,还附有详细的组装说明书。我们按照说明书一步步操作,就能组装出精美的积木模型。 数据结构与算法犹如图 1-5 所示的拼装积木。一套积木,除了包含许多零件之外,还附有详细的组装说明书。我们按照说明书一步步操作,就能组装出精美的积木模型。
![拼装积木](what_is_dsa.assets/assembling_blocks.png) ![拼装积木](what_is_dsa.assets/assembling_blocks.png){ class="animation-figure" }
<p align="center"> 图 1-5 &nbsp; 拼装积木 </p> <p align="center"> 图 1-5 &nbsp; 拼装积木 </p>

View file

@ -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 &nbsp; Hello 算法内容结构 </p> <p align="center"> 图 0-1 &nbsp; Hello 算法内容结构 </p>

View file

@ -7,7 +7,7 @@ icon: material/book-open-outline
<div class="center-table" markdown> <div class="center-table" markdown>
![前言](../assets/covers/chapter_preface.jpg){ width="600" } ![前言](../assets/covers/chapter_preface.jpg){ class="cover-image" }
</div> </div>

View file

@ -177,7 +177,7 @@ comments: true
如果你在阅读本书时,发现某段内容提供了图 0-2 所示的动画或图解,**请以图为主、以文字为辅**,综合两者来理解内容。 如果你在阅读本书时,发现某段内容提供了图 0-2 所示的动画或图解,**请以图为主、以文字为辅**,综合两者来理解内容。
![动画图解示例](../index.assets/animation.gif) ![动画图解示例](../index.assets/animation.gif){ class="animation-figure" }
<p align="center"> 图 0-2 &nbsp; 动画图解示例 </p> <p align="center"> 图 0-2 &nbsp; 动画图解示例 </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 &nbsp; 运行代码示例 </p> <p align="center"> 图 0-3 &nbsp; 运行代码示例 </p>
@ -205,13 +205,13 @@ git clone https://github.com/krahets/hello-algo.git
当然,你也可以在图 0-4 所示的位置点击“Download ZIP”直接下载代码压缩包然后在本地解压即可。 当然,你也可以在图 0-4 所示的位置点击“Download ZIP”直接下载代码压缩包然后在本地解压即可。
![克隆仓库与下载代码](suggestions.assets/download_code.png) ![克隆仓库与下载代码](suggestions.assets/download_code.png){ class="animation-figure" }
<p align="center"> 图 0-4 &nbsp; 克隆仓库与下载代码 </p> <p align="center"> 图 0-4 &nbsp; 克隆仓库与下载代码 </p>
**第三步:运行源代码**。如图 0-5 所示,对于顶部标有文件名称的代码块,我们可以在仓库的 `codes` 文件夹内找到对应的源代码文件。源代码文件可一键运行,将帮助你节省不必要的调试时间,让你能够专注于学习内容。 **第三步:运行源代码**。如图 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 &nbsp; 代码块与对应的源代码文件 </p> <p align="center"> 图 0-5 &nbsp; 代码块与对应的源代码文件 </p>
@ -221,7 +221,7 @@ git clone https://github.com/krahets/hello-algo.git
如图 0-6 所示,每篇文章的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享您的见解,帮助他人进步。 如图 0-6 所示,每篇文章的底部都配有评论区。希望你能多关注评论区的内容。一方面,你可以了解大家遇到的问题,从而查漏补缺,激发更深入的思考。另一方面,期待你能慷慨地回答其他小伙伴的问题,分享您的见解,帮助他人进步。
![评论区示例](../index.assets/comment.gif) ![评论区示例](../index.assets/comment.gif){ class="animation-figure" }
<p align="center"> 图 0-6 &nbsp; 评论区示例 </p> <p align="center"> 图 0-6 &nbsp; 评论区示例 </p>
@ -235,6 +235,6 @@ git clone https://github.com/krahets/hello-algo.git
如图 0-7 所示,本书内容主要涵盖“第一阶段”,旨在帮助你更高效地展开第二和第三阶段的学习。 如图 0-7 所示,本书内容主要涵盖“第一阶段”,旨在帮助你更高效地展开第二和第三阶段的学习。
![算法学习路线](suggestions.assets/learning_route.png) ![算法学习路线](suggestions.assets/learning_route.png){ class="animation-figure" }
<p align="center"> 图 0-7 &nbsp; 算法学习路线 </p> <p align="center"> 图 0-7 &nbsp; 算法学习路线 </p>

View file

@ -10,7 +10,7 @@ comments: true
给定一个长度为 $n$ 的数组 `nums` ,元素按从小到大的顺序排列,数组不包含重复元素。请查找并返回元素 `target` 在该数组中的索引。若数组不包含该元素,则返回 $-1$ 。 给定一个长度为 $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 &nbsp; 二分查找示例数据 </p> <p align="center"> 图 10-1 &nbsp; 二分查找示例数据 </p>
@ -27,25 +27,25 @@ comments: true
若数组不包含目标元素,搜索区间最终会缩小为空。此时返回 $-1$ 。 若数组不包含目标元素,搜索区间最终会缩小为空。此时返回 $-1$ 。
=== "<1>" === "<1>"
![二分查找流程](binary_search.assets/binary_search_step1.png) ![二分查找流程](binary_search.assets/binary_search_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 二分查找流程 </p> <p align="center"> 图 10-2 &nbsp; 二分查找流程 </p>
@ -627,7 +627,7 @@ comments: true
由于“双闭区间”表示中的左右边界都被定义为闭区间,因此指针 $i$ 和 $j$ 缩小区间操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。 由于“双闭区间”表示中的左右边界都被定义为闭区间,因此指针 $i$ 和 $j$ 缩小区间操作也是对称的。这样更不容易出错,**因此一般建议采用“双闭区间”的写法**。
![两种区间定义](binary_search.assets/binary_search_ranges.png) ![两种区间定义](binary_search.assets/binary_search_ranges.png){ class="animation-figure" }
<p align="center"> 图 10-3 &nbsp; 两种区间定义 </p> <p align="center"> 图 10-3 &nbsp; 两种区间定义 </p>

View file

@ -211,7 +211,7 @@ comments: true
如图 10-7 所示,查找完成后,指针 $i$ 指向最左一个 `target + 1`(如果存在),而 $j$ 指向最右一个 `target` **因此返回 $j$ 即可**。 如图 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 &nbsp; 将查找右边界转化为查找左边界 </p> <p align="center"> 图 10-7 &nbsp; 将查找右边界转化为查找左边界 </p>
@ -428,7 +428,7 @@ comments: true
- 查找最左一个 `target` :可以转化为查找 `target - 0.5` ,并返回指针 $i$ 。 - 查找最左一个 `target` :可以转化为查找 `target - 0.5` ,并返回指针 $i$ 。
- 查找最右一个 `target` :可以转化为查找 `target + 0.5` ,并返回指针 $j$ 。 - 查找最右一个 `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 &nbsp; 将查找边界转化为查找元素 </p> <p align="center"> 图 10-8 &nbsp; 将查找边界转化为查找元素 </p>

View file

@ -12,7 +12,7 @@ comments: true
给定一个长度为 $n$ 的有序数组 `nums` 和一个元素 `target` ,数组不存在重复元素。现将 `target` 插入到数组 `nums` 中,并保持其有序性。若数组中已存在元素 `target` ,则插入到其左方。请返回插入后 `target` 在数组中的索引。 给定一个长度为 $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 &nbsp; 二分查找插入点示例数据 </p> <p align="center"> 图 10-4 &nbsp; 二分查找插入点示例数据 </p>
@ -285,7 +285,7 @@ comments: true
1. 执行二分查找,得到任意一个 `target` 的索引,记为 $k$ 。 1. 执行二分查找,得到任意一个 `target` 的索引,记为 $k$ 。
2. 从索引 $k$ 开始,向左进行线性遍历,当找到最左边的 `target` 时返回。 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 &nbsp; 线性查找重复元素的插入点 </p> <p align="center"> 图 10-5 &nbsp; 线性查找重复元素的插入点 </p>
@ -299,28 +299,28 @@ comments: true
循环完成后,$i$ 指向最左边的 `target` $j$ 指向首个小于 `target` 的元素,**因此索引 $i$ 就是插入点**。 循环完成后,$i$ 指向最左边的 `target` $j$ 指向首个小于 `target` 的元素,**因此索引 $i$ 就是插入点**。
=== "<1>" === "<1>"
![二分查找重复元素的插入点的步骤](binary_search_insertion.assets/binary_search_insertion_step1.png) ![二分查找重复元素的插入点的步骤](binary_search_insertion.assets/binary_search_insertion_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 二分查找重复元素的插入点的步骤 </p> <p align="center"> 图 10-6 &nbsp; 二分查找重复元素的插入点的步骤 </p>

View file

@ -7,7 +7,7 @@ icon: material/text-search
<div class="center-table" markdown> <div class="center-table" markdown>
![搜索](../assets/covers/chapter_searching.jpg){ width="600" } ![搜索](../assets/covers/chapter_searching.jpg){ class="cover-image" }
</div> </div>

View file

@ -14,7 +14,7 @@ comments: true
考虑直接遍历所有可能的组合。如图 10-9 所示,我们开启一个两层循环,在每轮中判断两个整数的和是否为 `target` ,若是则返回它们的索引。 考虑直接遍历所有可能的组合。如图 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 &nbsp; 线性查找求解两数之和 </p> <p align="center"> 图 10-9 &nbsp; 线性查找求解两数之和 </p>
@ -237,13 +237,13 @@ comments: true
2. 将键值对 `nums[i]` 和索引 `i` 添加进哈希表。 2. 将键值对 `nums[i]` 和索引 `i` 添加进哈希表。
=== "<1>" === "<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>" === "<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>" === "<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 &nbsp; 辅助哈希表求解两数之和 </p> <p align="center"> 图 10-10 &nbsp; 辅助哈希表求解两数之和 </p>

View file

@ -44,7 +44,7 @@ comments: true
给定大小为 $n$ 的一组数据,我们可以使用线性搜索、二分查找、树查找、哈希查找等多种方法在该数据中搜索目标元素。各个方法的工作原理如图 10-11 所示。 给定大小为 $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 &nbsp; 多种搜索策略 </p> <p align="center"> 图 10-11 &nbsp; 多种搜索策略 </p>

View file

@ -9,25 +9,25 @@ comments: true
如图 11-4 所示,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换它俩。遍历完成后,最大的元素会被移动到数组的最右端。 如图 11-4 所示,冒泡过程可以利用元素交换操作来模拟:从数组最左端开始向右遍历,依次比较相邻元素大小,如果“左元素 > 右元素”就交换它俩。遍历完成后,最大的元素会被移动到数组的最右端。
=== "<1>" === "<1>"
![利用元素交换操作模拟冒泡](bubble_sort.assets/bubble_operation_step1.png) ![利用元素交换操作模拟冒泡](bubble_sort.assets/bubble_operation_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 利用元素交换操作模拟冒泡 </p> <p align="center"> 图 11-4 &nbsp; 利用元素交换操作模拟冒泡 </p>
@ -40,7 +40,7 @@ comments: true
3. 以此类推,经过 $n - 1$ 轮“冒泡”后,**前 $n - 1$ 大的元素都被交换至正确位置**。 3. 以此类推,经过 $n - 1$ 轮“冒泡”后,**前 $n - 1$ 大的元素都被交换至正确位置**。
4. 仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。 4. 仅剩的一个元素必定是最小元素,无须排序,因此数组排序完成。
![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png) ![冒泡排序流程](bubble_sort.assets/bubble_sort_overview.png){ class="animation-figure" }
<p align="center"> 图 11-5 &nbsp; 冒泡排序流程 </p> <p align="center"> 图 11-5 &nbsp; 冒泡排序流程 </p>

View file

@ -16,7 +16,7 @@ comments: true
2. 对每个桶分别执行排序(本文采用编程语言的内置排序函数)。 2. 对每个桶分别执行排序(本文采用编程语言的内置排序函数)。
3. 按照桶的从小到大的顺序,合并结果。 3. 按照桶的从小到大的顺序,合并结果。
![桶排序算法流程](bucket_sort.assets/bucket_sort_overview.png) ![桶排序算法流程](bucket_sort.assets/bucket_sort_overview.png){ class="animation-figure" }
<p align="center"> 图 11-13 &nbsp; 桶排序算法流程 </p> <p align="center"> 图 11-13 &nbsp; 桶排序算法流程 </p>
@ -409,7 +409,7 @@ comments: true
如图 11-14 所示,这种方法本质上是创建一个递归树,目标是让叶节点的值尽可能平均。当然,不一定要每轮将数据划分为 3 个桶,具体划分方式可根据数据特点灵活选择。 如图 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 &nbsp; 递归划分桶 </p> <p align="center"> 图 11-14 &nbsp; 递归划分桶 </p>
@ -417,6 +417,6 @@ comments: true
如图 11-15 所示,我们假设商品价格服从正态分布,这样就可以合理地设定价格区间,从而将商品平均分配到各个桶中。 如图 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 &nbsp; 根据概率分布划分桶 </p> <p align="center"> 图 11-15 &nbsp; 根据概率分布划分桶 </p>

View file

@ -14,7 +14,7 @@ comments: true
2. **借助 `counter` 统计 `nums` 中各数字的出现次数**,其中 `counter[num]` 对应数字 `num` 的出现次数。统计方法很简单,只需遍历 `nums`(设当前数字为 `num`),每轮将 `counter[num]` 增加 $1$ 即可。 2. **借助 `counter` 统计 `nums` 中各数字的出现次数**,其中 `counter[num]` 对应数字 `num` 的出现次数。统计方法很简单,只需遍历 `nums`(设当前数字为 `num`),每轮将 `counter[num]` 增加 $1$ 即可。
3. **由于 `counter` 的各个索引天然有序,因此相当于所有数字已经被排序好了**。接下来,我们遍历 `counter` ,根据各数字的出现次数,将它们按从小到大的顺序填入 `nums` 即可。 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 &nbsp; 计数排序流程 </p> <p align="center"> 图 11-16 &nbsp; 计数排序流程 </p>
@ -339,28 +339,28 @@ $$
遍历完成后,数组 `res` 中就是排序好的结果,最后使用 `res` 覆盖原数组 `nums` 即可。图 11-17 展示了完整的计数排序流程。 遍历完成后,数组 `res` 中就是排序好的结果,最后使用 `res` 覆盖原数组 `nums` 即可。图 11-17 展示了完整的计数排序流程。
=== "<1>" === "<1>"
![计数排序步骤](counting_sort.assets/counting_sort_step1.png) ![计数排序步骤](counting_sort.assets/counting_sort_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 计数排序步骤 </p> <p align="center"> 图 11-17 &nbsp; 计数排序步骤 </p>

View file

@ -29,40 +29,40 @@ comments: true
实际上,元素出堆操作中也包含第 `2.``3.` 步,只是多了一个弹出元素的步骤。 实际上,元素出堆操作中也包含第 `2.``3.` 步,只是多了一个弹出元素的步骤。
=== "<1>" === "<1>"
![堆排序步骤](heap_sort.assets/heap_sort_step1.png) ![堆排序步骤](heap_sort.assets/heap_sort_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 堆排序步骤 </p> <p align="center"> 图 11-12 &nbsp; 堆排序步骤 </p>

View file

@ -7,7 +7,7 @@ icon: material/sort-ascending
<div class="center-table" markdown> <div class="center-table" markdown>
![排序](../assets/covers/chapter_sorting.jpg){ width="600" } ![排序](../assets/covers/chapter_sorting.jpg){ class="cover-image" }
</div> </div>

View file

@ -10,7 +10,7 @@ comments: true
图 11-6 展示了数组插入元素的操作流程。设基准元素为 `base` ,我们需要将从目标索引到 `base` 之间的所有元素向右移动一位,然后再将 `base` 赋值给目标索引。 图 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 &nbsp; 单次插入操作 </p> <p align="center"> 图 11-6 &nbsp; 单次插入操作 </p>
@ -23,7 +23,7 @@ comments: true
3. 选取第 3 个元素作为 `base` ,将其插入到正确位置后,**数组的前 3 个元素已排序**。 3. 选取第 3 个元素作为 `base` ,将其插入到正确位置后,**数组的前 3 个元素已排序**。
4. 以此类推,在最后一轮中,选取最后一个元素作为 `base` ,将其插入到正确位置后,**所有元素均已排序**。 4. 以此类推,在最后一轮中,选取最后一个元素作为 `base` ,将其插入到正确位置后,**所有元素均已排序**。
![插入排序流程](insertion_sort.assets/insertion_sort_overview.png) ![插入排序流程](insertion_sort.assets/insertion_sort_overview.png){ class="animation-figure" }
<p align="center"> 图 11-7 &nbsp; 插入排序流程 </p> <p align="center"> 图 11-7 &nbsp; 插入排序流程 </p>

View file

@ -9,7 +9,7 @@ comments: true
1. **划分阶段**:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。 1. **划分阶段**:通过递归不断地将数组从中点处分开,将长数组的排序问题转换为短数组的排序问题。
2. **合并阶段**:当子数组长度为 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 &nbsp; 归并排序的划分与合并阶段 </p> <p align="center"> 图 11-10 &nbsp; 归并排序的划分与合并阶段 </p>
@ -23,34 +23,34 @@ comments: true
“合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。 “合并阶段”从底至顶地将左子数组和右子数组合并为一个有序数组。需要注意的是,从长度为 1 的子数组开始合并,合并阶段中的每个子数组都是有序的。
=== "<1>" === "<1>"
![归并排序步骤](merge_sort.assets/merge_sort_step1.png) ![归并排序步骤](merge_sort.assets/merge_sort_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 归并排序步骤 </p> <p align="center"> 图 11-11 &nbsp; 归并排序步骤 </p>

View file

@ -13,31 +13,31 @@ comments: true
3. 循环执行步骤 `2.` ,直到 `i``j` 相遇时停止,最后将基准数交换至两个子数组的分界线。 3. 循环执行步骤 `2.` ,直到 `i``j` 相遇时停止,最后将基准数交换至两个子数组的分界线。
=== "<1>" === "<1>"
![哨兵划分步骤](quick_sort.assets/pivot_division_step1.png) ![哨兵划分步骤](quick_sort.assets/pivot_division_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 哨兵划分步骤 </p> <p align="center"> 图 11-8 &nbsp; 哨兵划分步骤 </p>
@ -366,7 +366,7 @@ comments: true
2. 然后,对左子数组和右子数组分别递归执行“哨兵划分”。 2. 然后,对左子数组和右子数组分别递归执行“哨兵划分”。
3. 持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。 3. 持续递归,直至子数组长度为 1 时终止,从而完成整个数组的排序。
![快速排序流程](quick_sort.assets/quick_sort_overview.png) ![快速排序流程](quick_sort.assets/quick_sort_overview.png){ class="animation-figure" }
<p align="center"> 图 11-9 &nbsp; 快速排序流程 </p> <p align="center"> 图 11-9 &nbsp; 快速排序流程 </p>

View file

@ -16,7 +16,7 @@ comments: true
2. 对学号的第 $k$ 位执行“计数排序”。完成后,数据会根据第 $k$ 位从小到大排序。 2. 对学号的第 $k$ 位执行“计数排序”。完成后,数据会根据第 $k$ 位从小到大排序。
3. 将 $k$ 增加 $1$ ,然后返回步骤 `2.` 继续迭代,直到所有位都排序完成后结束。 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 &nbsp; 基数排序算法流程 </p> <p align="center"> 图 11-18 &nbsp; 基数排序算法流程 </p>

View file

@ -15,37 +15,37 @@ comments: true
5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。 5. 仅剩的一个元素必定是最大元素,无须排序,因此数组排序完成。
=== "<1>" === "<1>"
![选择排序步骤](selection_sort.assets/selection_sort_step1.png) ![选择排序步骤](selection_sort.assets/selection_sort_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 选择排序步骤 </p> <p align="center"> 图 11-2 &nbsp; 选择排序步骤 </p>
@ -290,6 +290,6 @@ comments: true
- **空间复杂度 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。 - **空间复杂度 $O(1)$、原地排序**:指针 $i$ 和 $j$ 使用常数大小的额外空间。
- **非稳定排序**:如图 11-3 所示,元素 `nums[i]` 有可能被交换至与其相等的元素的右边,导致两者相对顺序发生改变。 - **非稳定排序**:如图 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 &nbsp; 选择排序非稳定示例 </p> <p align="center"> 图 11-3 &nbsp; 选择排序非稳定示例 </p>

View file

@ -8,7 +8,7 @@ comments: true
如图 11-1 所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求设定,如数字大小、字符 ASCII 码顺序或自定义规则。 如图 11-1 所示,排序算法中的数据类型可以是整数、浮点数、字符或字符串等。排序的判断规则可根据需求设定,如数字大小、字符 ASCII 码顺序或自定义规则。
![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png) ![数据类型和判断规则示例](sorting_algorithm.assets/sorting_examples.png){ class="animation-figure" }
<p align="center"> 图 11-1 &nbsp; 数据类型和判断规则示例 </p> <p align="center"> 图 11-1 &nbsp; 数据类型和判断规则示例 </p>

View file

@ -16,7 +16,7 @@ comments: true
- 总的来说,我们希望找到一种排序算法,具有高效率、稳定、原地以及正向自适应性等优点。然而,正如其他数据结构和算法一样,没有一种排序算法能够同时满足所有这些条件。在实际应用中,我们需要根据数据的特性来选择合适的排序算法。 - 总的来说,我们希望找到一种排序算法,具有高效率、稳定、原地以及正向自适应性等优点。然而,正如其他数据结构和算法一样,没有一种排序算法能够同时满足所有这些条件。在实际应用中,我们需要根据数据的特性来选择合适的排序算法。
- 图 11-19 对比了主流排序算法的效率、稳定性、就地性和自适应性等。 - 图 11-19 对比了主流排序算法的效率、稳定性、就地性和自适应性等。
![排序算法对比](summary.assets/sorting_algorithms_comparison.png) ![排序算法对比](summary.assets/sorting_algorithms_comparison.png){ class="animation-figure" }
<p align="center"> 图 11-19 &nbsp; 排序算法对比 </p> <p align="center"> 图 11-19 &nbsp; 排序算法对比 </p>

View file

@ -6,7 +6,7 @@ comments: true
在队列中,我们仅能在头部删除或在尾部添加元素。如图 5-7 所示,「双向队列 double-ended queue」提供了更高的灵活性允许在头部和尾部执行元素的添加或删除操作。 在队列中,我们仅能在头部删除或在尾部添加元素。如图 5-7 所示,「双向队列 double-ended queue」提供了更高的灵活性允许在头部和尾部执行元素的添加或删除操作。
![双向队列的操作](deque.assets/deque_operations.png) ![双向队列的操作](deque.assets/deque_operations.png){ class="animation-figure" }
<p align="center"> 图 5-7 &nbsp; 双向队列的操作 </p> <p align="center"> 图 5-7 &nbsp; 双向队列的操作 </p>
@ -365,19 +365,19 @@ comments: true
如图 5-8 所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。 如图 5-8 所示,我们将双向链表的头节点和尾节点视为双向队列的队首和队尾,同时实现在两端添加和删除节点的功能。
=== "LinkedListDeque" === "LinkedListDeque"
![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque.png) ![基于链表实现双向队列的入队出队操作](deque.assets/linkedlist_deque.png){ class="animation-figure" }
=== "pushLast()" === "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()" === "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()" === "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()" === "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 &nbsp; 基于链表实现双向队列的入队出队操作 </p> <p align="center"> 图 5-8 &nbsp; 基于链表实现双向队列的入队出队操作 </p>
@ -2008,19 +2008,19 @@ comments: true
如图 5-9 所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。 如图 5-9 所示,与基于数组实现队列类似,我们也可以使用环形数组来实现双向队列。
=== "ArrayDeque" === "ArrayDeque"
![基于数组实现双向队列的入队出队操作](deque.assets/array_deque.png) ![基于数组实现双向队列的入队出队操作](deque.assets/array_deque.png){ class="animation-figure" }
=== "pushLast()" === "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()" === "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()" === "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()" === "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 &nbsp; 基于数组实现双向队列的入队出队操作 </p> <p align="center"> 图 5-9 &nbsp; 基于数组实现双向队列的入队出队操作 </p>

View file

@ -7,7 +7,7 @@ icon: material/stack-overflow
<div class="center-table" markdown> <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> </div>

View file

@ -8,7 +8,7 @@ comments: true
如图 5-4 所示,我们将队列的头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。 如图 5-4 所示,我们将队列的头部称为“队首”,尾部称为“队尾”,将把元素加入队尾的操作称为“入队”,删除队首元素的操作称为“出队”。
![队列的先入先出规则](queue.assets/queue_operations.png) ![队列的先入先出规则](queue.assets/queue_operations.png){ class="animation-figure" }
<p align="center"> 图 5-4 &nbsp; 队列的先入先出规则 </p> <p align="center"> 图 5-4 &nbsp; 队列的先入先出规则 </p>
@ -325,13 +325,13 @@ comments: true
如图 5-5 所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。 如图 5-5 所示,我们可以将链表的“头节点”和“尾节点”分别视为“队首”和“队尾”,规定队尾仅可添加节点,队首仅可删除节点。
=== "LinkedListQueue" === "LinkedListQueue"
![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue.png) ![基于链表实现队列的入队出队操作](queue.assets/linkedlist_queue.png){ class="animation-figure" }
=== "push()" === "push()"
![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png) ![linkedlist_queue_push](queue.assets/linkedlist_queue_push.png){ class="animation-figure" }
=== "pop()" === "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 &nbsp; 基于链表实现队列的入队出队操作 </p> <p align="center"> 图 5-5 &nbsp; 基于链表实现队列的入队出队操作 </p>
@ -1216,13 +1216,13 @@ comments: true
可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。 可以看到,入队和出队操作都只需进行一次操作,时间复杂度均为 $O(1)$ 。
=== "ArrayQueue" === "ArrayQueue"
![基于数组实现队列的入队出队操作](queue.assets/array_queue.png) ![基于数组实现队列的入队出队操作](queue.assets/array_queue.png){ class="animation-figure" }
=== "push()" === "push()"
![array_queue_push](queue.assets/array_queue_push.png) ![array_queue_push](queue.assets/array_queue_push.png){ class="animation-figure" }
=== "pop()" === "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 &nbsp; 基于数组实现队列的入队出队操作 </p> <p align="center"> 图 5-6 &nbsp; 基于数组实现队列的入队出队操作 </p>

View file

@ -10,7 +10,7 @@ comments: true
如图 5-1 所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫做“入栈”,删除栈顶元素的操作叫做“出栈”。 如图 5-1 所示,我们把堆叠元素的顶部称为“栈顶”,底部称为“栈底”。将把元素添加到栈顶的操作叫做“入栈”,删除栈顶元素的操作叫做“出栈”。
![栈的先入后出规则](stack.assets/stack_operations.png) ![栈的先入后出规则](stack.assets/stack_operations.png){ class="animation-figure" }
<p align="center"> 图 5-1 &nbsp; 栈的先入后出规则 </p> <p align="center"> 图 5-1 &nbsp; 栈的先入后出规则 </p>
@ -325,13 +325,13 @@ comments: true
如图 5-2 所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。 如图 5-2 所示,对于入栈操作,我们只需将元素插入链表头部,这种节点插入方法被称为“头插法”。而对于出栈操作,只需将头节点从链表中删除即可。
=== "LinkedListStack" === "LinkedListStack"
![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack.png) ![基于链表实现栈的入栈出栈操作](stack.assets/linkedlist_stack.png){ class="animation-figure" }
=== "push()" === "push()"
![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png) ![linkedlist_stack_push](stack.assets/linkedlist_stack_push.png){ class="animation-figure" }
=== "pop()" === "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 &nbsp; 基于链表实现栈的入栈出栈操作 </p> <p align="center"> 图 5-2 &nbsp; 基于链表实现栈的入栈出栈操作 </p>
@ -1089,13 +1089,13 @@ comments: true
使用数组实现栈时,我们可以将数组的尾部作为栈顶。如图 5-3 所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。 使用数组实现栈时,我们可以将数组的尾部作为栈顶。如图 5-3 所示,入栈与出栈操作分别对应在数组尾部添加元素与删除元素,时间复杂度都为 $O(1)$ 。
=== "ArrayStack" === "ArrayStack"
![基于数组实现栈的入栈出栈操作](stack.assets/array_stack.png) ![基于数组实现栈的入栈出栈操作](stack.assets/array_stack.png){ class="animation-figure" }
=== "push()" === "push()"
![array_stack_push](stack.assets/array_stack_push.png) ![array_stack_push](stack.assets/array_stack_push.png){ class="animation-figure" }
=== "pop()" === "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 &nbsp; 基于数组实现栈的入栈出栈操作 </p> <p align="center"> 图 5-3 &nbsp; 基于数组实现栈的入栈出栈操作 </p>

View file

@ -14,7 +14,7 @@ comments: true
根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的“映射公式”:**若节点的索引为 $i$ ,则该节点的左子节点索引为 $2i + 1$ ,右子节点索引为 $2i + 2$** 。图 7-12 展示了各个节点索引之间的映射关系。 根据层序遍历的特性,我们可以推导出父节点索引与子节点索引之间的“映射公式”:**若节点的索引为 $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 &nbsp; 完美二叉树的数组表示 </p> <p align="center"> 图 7-12 &nbsp; 完美二叉树的数组表示 </p>
@ -26,7 +26,7 @@ comments: true
如图 7-13 所示,给定一个非完美二叉树,上述的数组表示方法已经失效。 如图 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 &nbsp; 层序遍历序列对应多种二叉树可能性 </p> <p align="center"> 图 7-13 &nbsp; 层序遍历序列对应多种二叉树可能性 </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 &nbsp; 任意类型二叉树的数组表示 </p> <p align="center"> 图 7-14 &nbsp; 任意类型二叉树的数组表示 </p>
@ -134,7 +134,7 @@ comments: true
这意味着使用数组表示完全二叉树时,可以省略存储所有 $\text{None}$ ,非常方便。图 7-15 给出了一个例子。 这意味着使用数组表示完全二叉树时,可以省略存储所有 $\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 &nbsp; 完全二叉树的数组表示 </p> <p align="center"> 图 7-15 &nbsp; 完全二叉树的数组表示 </p>

View file

@ -8,13 +8,13 @@ comments: true
如图 7-24 所示,经过两次删除节点操作,这个二叉搜索树便会退化为链表。 如图 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 &nbsp; AVL 树在删除节点后发生退化 </p> <p align="center"> 图 7-24 &nbsp; AVL 树在删除节点后发生退化 </p>
再例如,在图 7-25 的完美二叉树中插入两个节点后,树将严重向左倾斜,查找操作的时间复杂度也随之恶化。 再例如,在图 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 &nbsp; AVL 树在插入节点后发生退化 </p> <p align="center"> 图 7-25 &nbsp; AVL 树在插入节点后发生退化 </p>
@ -610,22 +610,22 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
如图 7-26 所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树将该节点记为 `node` ,其左子节点记为 `child` ,执行“右旋”操作。完成右旋后,子树已经恢复平衡,并且仍然保持二叉搜索树的特性。 如图 7-26 所示,节点下方为平衡因子。从底至顶看,二叉树中首个失衡节点是“节点 3”。我们关注以该失衡节点为根节点的子树将该节点记为 `node` ,其左子节点记为 `child` ,执行“右旋”操作。完成右旋后,子树已经恢复平衡,并且仍然保持二叉搜索树的特性。
=== "<1>" === "<1>"
![右旋操作步骤](avl_tree.assets/avltree_right_rotate_step1.png) ![右旋操作步骤](avl_tree.assets/avltree_right_rotate_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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 &nbsp; 右旋操作步骤 </p> <p align="center"> 图 7-26 &nbsp; 右旋操作步骤 </p>
如图 7-27 所示,当节点 `child` 有右子节点(记为 `grandChild` )时,需要在右旋中添加一步:将 `grandChild` 作为 `node` 的左子节点。 如图 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 &nbsp; 有 grandChild 的右旋操作 </p> <p align="center"> 图 7-27 &nbsp; 有 grandChild 的右旋操作 </p>
@ -856,13 +856,13 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行图 7-28 所示的“左旋”操作。 相应的,如果考虑上述失衡二叉树的“镜像”,则需要执行图 7-28 所示的“左旋”操作。
![左旋操作](avl_tree.assets/avltree_left_rotate.png) ![左旋操作](avl_tree.assets/avltree_left_rotate.png){ class="animation-figure" }
<p align="center"> 图 7-28 &nbsp; 左旋操作 </p> <p align="center"> 图 7-28 &nbsp; 左旋操作 </p>
同理,如图 7-29 所示,当节点 `child` 有左子节点(记为 `grandChild` )时,需要在左旋中添加一步:将 `grandChild` 作为 `node` 的右子节点。 同理,如图 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 &nbsp; 有 grandChild 的左旋操作 </p> <p align="center"> 图 7-29 &nbsp; 有 grandChild 的左旋操作 </p>
@ -1093,7 +1093,7 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
对于图 7-30 中的失衡节点 3 ,仅使用左旋或右旋都无法使子树恢复平衡。此时需要先对 `child` 执行“左旋”,再对 `node` 执行“右旋”。 对于图 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 &nbsp; 先左旋后右旋 </p> <p align="center"> 图 7-30 &nbsp; 先左旋后右旋 </p>
@ -1101,7 +1101,7 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
如图 7-31 所示,对于上述失衡二叉树的镜像情况,需要先对 `child` 执行“右旋”,然后对 `node` 执行“左旋”。 如图 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 &nbsp; 先右旋后左旋 </p> <p align="center"> 图 7-31 &nbsp; 先右旋后左旋 </p>
@ -1109,7 +1109,7 @@ AVL 树的特点在于“旋转”操作,它能够在不影响二叉树的中
图 7-32 展示的四种失衡情况与上述案例逐个对应,分别需要采用右旋、左旋、先右后左、先左后右的旋转操作。 图 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 &nbsp; AVL 树的四种旋转情况 </p> <p align="center"> 图 7-32 &nbsp; AVL 树的四种旋转情况 </p>

View file

@ -9,7 +9,7 @@ comments: true
1. 对于根节点,左子树中所有节点的值 $<$ 根节点的值 $<$ 右子树中所有节点的值。 1. 对于根节点,左子树中所有节点的值 $<$ 根节点的值 $<$ 右子树中所有节点的值。
2. 任意节点的左、右子树也是二叉搜索树,即同样满足条件 `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 &nbsp; 二叉搜索树 </p> <p align="center"> 图 7-16 &nbsp; 二叉搜索树 </p>
@ -26,16 +26,16 @@ comments: true
- 若 `cur.val = num` ,说明找到目标节点,跳出循环并返回该节点。 - 若 `cur.val = num` ,说明找到目标节点,跳出循环并返回该节点。
=== "<1>" === "<1>"
![二叉搜索树查找节点示例](binary_search_tree.assets/bst_search_step1.png) ![二叉搜索树查找节点示例](binary_search_tree.assets/bst_search_step1.png){ class="animation-figure" }
=== "<2>" === "<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>" === "<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>" === "<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 &nbsp; 二叉搜索树查找节点示例 </p> <p align="center"> 图 7-17 &nbsp; 二叉搜索树查找节点示例 </p>
@ -325,7 +325,7 @@ comments: true
1. **查找插入位置**:与查找操作相似,从根节点出发,根据当前节点值和 `num` 的大小关系循环向下搜索,直到越过叶节点(遍历至 $\text{None}$ )时跳出循环。 1. **查找插入位置**:与查找操作相似,从根节点出发,根据当前节点值和 `num` 的大小关系循环向下搜索,直到越过叶节点(遍历至 $\text{None}$ )时跳出循环。
2. **在该位置插入节点**:初始化节点 `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 &nbsp; 在二叉搜索树中插入节点 </p> <p align="center"> 图 7-18 &nbsp; 在二叉搜索树中插入节点 </p>
@ -753,13 +753,13 @@ comments: true
如图 7-19 所示,当待删除节点的度为 $0$ 时,表示该节点是叶节点,可以直接删除。 如图 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 &nbsp; 在二叉搜索树中删除节点(度为 0 </p> <p align="center"> 图 7-19 &nbsp; 在二叉搜索树中删除节点(度为 0 </p>
如图 7-20 所示,当待删除节点的度为 $1$ 时,将待删除节点替换为其子节点即可。 如图 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 &nbsp; 在二叉搜索树中删除节点(度为 1 </p> <p align="center"> 图 7-20 &nbsp; 在二叉搜索树中删除节点(度为 1 </p>
@ -771,16 +771,16 @@ comments: true
2. 将 `tmp` 的值覆盖待删除节点的值,并在树中递归删除节点 `tmp` 2. 将 `tmp` 的值覆盖待删除节点的值,并在树中递归删除节点 `tmp`
=== "<1>" === "<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>" === "<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>" === "<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>" === "<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 &nbsp; 在二叉搜索树中删除节点(度为 2 </p> <p align="center"> 图 7-21 &nbsp; 在二叉搜索树中删除节点(度为 2 </p>
@ -1464,7 +1464,7 @@ comments: true
利用中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $O(n)$ 时间,无须进行额外的排序操作,非常高效。 利用中序遍历升序的性质,我们在二叉搜索树中获取有序数据仅需 $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 &nbsp; 二叉搜索树的中序遍历序列 </p> <p align="center"> 图 7-22 &nbsp; 二叉搜索树的中序遍历序列 </p>
@ -1488,7 +1488,7 @@ comments: true
然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为图 7-23 所示的链表,这时各种操作的时间复杂度也会退化为 $O(n)$ 。 然而,如果我们在二叉搜索树中不断地插入和删除节点,可能导致二叉树退化为图 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 &nbsp; 二叉搜索树的退化 </p> <p align="center"> 图 7-23 &nbsp; 二叉搜索树的退化 </p>

View file

@ -191,7 +191,7 @@ comments: true
**在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如图 7-1 所示,如果将“节点 2”视为父节点则其左子节点和右子节点分别是“节点 4”和“节点 5”左子树是“节点 4 及其以下节点形成的树”,右子树是“节点 5 及其以下节点形成的树”。 **在二叉树中,除叶节点外,其他所有节点都包含子节点和非空子树**。如图 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 &nbsp; 父节点、子节点、子树 </p> <p align="center"> 图 7-1 &nbsp; 父节点、子节点、子树 </p>
@ -208,7 +208,7 @@ comments: true
- 节点的「深度 depth」从根节点到该节点所经过的边的数量。 - 节点的「深度 depth」从根节点到该节点所经过的边的数量。
- 节点的「高度 height」从距离该节点最远的叶节点到该节点所经过的边的数量。 - 节点的「高度 height」从距离该节点最远的叶节点到该节点所经过的边的数量。
![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png) ![二叉树的常用术语](binary_tree.assets/binary_tree_terminology.png){ class="animation-figure" }
<p align="center"> 图 7-2 &nbsp; 二叉树的常用术语 </p> <p align="center"> 图 7-2 &nbsp; 二叉树的常用术语 </p>
@ -416,7 +416,7 @@ comments: true
与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。图 7-3 给出了一个示例。 与链表类似,在二叉树中插入与删除节点可以通过修改指针来实现。图 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 &nbsp; 在二叉树中插入与删除节点 </p> <p align="center"> 图 7-3 &nbsp; 在二叉树中插入与删除节点 </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 &nbsp; 完美二叉树 </p> <p align="center"> 图 7-4 &nbsp; 完美二叉树 </p>
@ -577,7 +577,7 @@ comments: true
如图 7-5 所示,「完全二叉树 complete binary tree」只有最底层的节点未被填满且最底层节点尽量靠左填充。 如图 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 &nbsp; 完全二叉树 </p> <p align="center"> 图 7-5 &nbsp; 完全二叉树 </p>
@ -585,7 +585,7 @@ comments: true
如图 7-6 所示,「完满二叉树 full binary tree」除了叶节点之外其余所有节点都有两个子节点。 如图 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 &nbsp; 完满二叉树 </p> <p align="center"> 图 7-6 &nbsp; 完满二叉树 </p>
@ -593,7 +593,7 @@ comments: true
如图 7-7 所示,「平衡二叉树 balanced binary tree」中任意节点的左子树和右子树的高度之差的绝对值不超过 1 。 如图 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 &nbsp; 平衡二叉树 </p> <p align="center"> 图 7-7 &nbsp; 平衡二叉树 </p>
@ -604,7 +604,7 @@ comments: true
- 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。 - 完美二叉树是理想情况,可以充分发挥二叉树“分治”的优势。
- 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $O(n)$ 。 - 链表则是另一个极端,各项操作都变为线性操作,时间复杂度退化至 $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 &nbsp; 二叉树的最佳与最差结构 </p> <p align="center"> 图 7-8 &nbsp; 二叉树的最佳与最差结构 </p>

View file

@ -14,7 +14,7 @@ comments: true
层序遍历本质上属于「广度优先遍历 breadth-first traversal」它体现了一种“一圈一圈向外扩展”的逐层遍历方式。 层序遍历本质上属于「广度优先遍历 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 &nbsp; 二叉树的层序遍历 </p> <p align="center"> 图 7-9 &nbsp; 二叉树的层序遍历 </p>
@ -335,7 +335,7 @@ comments: true
图 7-10 展示了对二叉树进行深度优先遍历的工作原理。**深度优先遍历就像是绕着整个二叉树的外围“走”一圈**,在每个节点都会遇到三个位置,分别对应前序遍历、中序遍历和后序遍历。 图 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 &nbsp; 二叉搜索树的前、中、后序遍历 </p> <p align="center"> 图 7-10 &nbsp; 二叉搜索树的前、中、后序遍历 </p>
@ -764,37 +764,37 @@ comments: true
2. “归”表示函数返回,代表当前节点已经访问完毕。 2. “归”表示函数返回,代表当前节点已经访问完毕。
=== "<1>" === "<1>"
![前序遍历的递归过程](binary_tree_traversal.assets/preorder_step1.png) ![前序遍历的递归过程](binary_tree_traversal.assets/preorder_step1.png){ class="animation-figure" }
=== "<2>" === "<2>"
![preorder_step2](binary_tree_traversal.assets/preorder_step2.png) ![preorder_step2](binary_tree_traversal.assets/preorder_step2.png){ class="animation-figure" }
=== "<3>" === "<3>"
![preorder_step3](binary_tree_traversal.assets/preorder_step3.png) ![preorder_step3](binary_tree_traversal.assets/preorder_step3.png){ class="animation-figure" }
=== "<4>" === "<4>"
![preorder_step4](binary_tree_traversal.assets/preorder_step4.png) ![preorder_step4](binary_tree_traversal.assets/preorder_step4.png){ class="animation-figure" }
=== "<5>" === "<5>"
![preorder_step5](binary_tree_traversal.assets/preorder_step5.png) ![preorder_step5](binary_tree_traversal.assets/preorder_step5.png){ class="animation-figure" }
=== "<6>" === "<6>"
![preorder_step6](binary_tree_traversal.assets/preorder_step6.png) ![preorder_step6](binary_tree_traversal.assets/preorder_step6.png){ class="animation-figure" }
=== "<7>" === "<7>"
![preorder_step7](binary_tree_traversal.assets/preorder_step7.png) ![preorder_step7](binary_tree_traversal.assets/preorder_step7.png){ class="animation-figure" }
=== "<8>" === "<8>"
![preorder_step8](binary_tree_traversal.assets/preorder_step8.png) ![preorder_step8](binary_tree_traversal.assets/preorder_step8.png){ class="animation-figure" }
=== "<9>" === "<9>"
![preorder_step9](binary_tree_traversal.assets/preorder_step9.png) ![preorder_step9](binary_tree_traversal.assets/preorder_step9.png){ class="animation-figure" }
=== "<10>" === "<10>"
![preorder_step10](binary_tree_traversal.assets/preorder_step10.png) ![preorder_step10](binary_tree_traversal.assets/preorder_step10.png){ class="animation-figure" }
=== "<11>" === "<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 &nbsp; 前序遍历的递归过程 </p> <p align="center"> 图 7-11 &nbsp; 前序遍历的递归过程 </p>

View file

@ -7,7 +7,7 @@ icon: material/graph-outline
<div class="center-table" markdown> <div class="center-table" markdown>
![](../assets/covers/chapter_tree.jpg){ width="600" } ![](../assets/covers/chapter_tree.jpg){ class="cover-image" }
</div> </div>

View file

@ -8,8 +8,8 @@ hide:
<h1 align="center"> </h1> <h1 align="center"> </h1>
<p align="center"> <p align="center">
<img src="index.assets/conceptual_rendering.png" width="250"> <img src="index.assets/conceptual_rendering.png" width="200">
<img src="index.assets/hello_algo_mindmap_tp.png" width="400"> <img src="index.assets/hello_algo_mindmap_tp.png" width="320">
</p> </p>
<h2 align="center"> 《 Hello 算法 》</h2> <h2 align="center"> 《 Hello 算法 》</h2>

View file

@ -25,7 +25,7 @@
--md-default-fg-color: #adbac7; --md-default-fg-color: #adbac7;
--md-default-bg-color: #22272e; --md-default-bg-color: #22272e;
--md-code-bg-color: #1D2126; --md-code-bg-color: #1d2126;
--md-code-fg-color: #adbac7; --md-code-fg-color: #adbac7;
--md-accent-fg-color: #aaa; --md-accent-fg-color: #aaa;
@ -43,6 +43,23 @@
color: var(--md-default-fg-color) !important; 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 Markdown Tables (requires md_in_html extension) */
.center-table { .center-table {
text-align: center; text-align: center;
@ -82,12 +99,6 @@
text-transform: none; text-transform: none;
} }
/* Image align center */
.center {
display: block;
margin: 0 auto;
}
/* font-family setting for Win10 */ /* font-family setting for Win10 */
body { body {
--md-text-font-family: -apple-system, BlinkMacSystemFont, --md-text-font-family: -apple-system, BlinkMacSystemFont,