diff --git a/docs/chapter_array_and_linkedlist/array.md b/docs/chapter_array_and_linkedlist/array.md index 9d492a1a3..454d4489e 100755 --- a/docs/chapter_array_and_linkedlist/array.md +++ b/docs/chapter_array_and_linkedlist/array.md @@ -134,7 +134,7 @@ comments: true ??? pythontutor "可视化运行" -
+ ### 2. 访问元素 @@ -320,7 +320,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 3. 插入元素 @@ -516,7 +516,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 4. 删除元素 @@ -686,7 +686,7 @@ comments: true ??? pythontutor "可视化运行" - + 总的来看,数组的插入与删除操作有以下缺点。 @@ -933,7 +933,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 6. 查找元素 @@ -1115,7 +1115,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 7. 扩容数组 @@ -1345,7 +1345,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 4.1.2 数组的优点与局限性 diff --git a/docs/chapter_array_and_linkedlist/linked_list.md b/docs/chapter_array_and_linkedlist/linked_list.md index d81fd6bbc..79aefd68a 100755 --- a/docs/chapter_array_and_linkedlist/linked_list.md +++ b/docs/chapter_array_and_linkedlist/linked_list.md @@ -421,7 +421,7 @@ comments: true ??? pythontutor "可视化运行" - + 数组整体是一个变量,比如数组 `nums` 包含元素 `nums[0]` 和 `nums[1]` 等,而链表是由多个独立的节点对象组成的。**我们通常将头节点当作链表的代称**,比如以上代码中的链表可记作链表 `n0` 。 @@ -582,7 +582,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 3. 删除节点 @@ -785,7 +785,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 4. 访问节点 @@ -989,7 +989,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 5. 查找节点 @@ -1215,7 +1215,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 4.2.2 数组 vs. 链表 diff --git a/docs/chapter_array_and_linkedlist/list.md b/docs/chapter_array_and_linkedlist/list.md index a1ab8a92b..faf2d2caa 100755 --- a/docs/chapter_array_and_linkedlist/list.md +++ b/docs/chapter_array_and_linkedlist/list.md @@ -153,7 +153,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 访问元素 @@ -285,7 +285,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 3. 插入与删除元素 @@ -540,7 +540,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 4. 遍历列表 @@ -742,7 +742,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 5. 拼接列表 @@ -854,7 +854,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 6. 排序列表 @@ -953,7 +953,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 4.3.2 列表实现 @@ -2297,5 +2297,5 @@ comments: true ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index c7e167192..e74160c58 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -234,7 +234,7 @@ comments: true ??? pythontutor "可视化运行" - + { class="animation-figure" } @@ -545,7 +545,7 @@ comments: true ??? pythontutor "可视化运行" - + 在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。 @@ -899,7 +899,7 @@ comments: true ??? pythontutor "可视化运行" - + “剪枝”是一个非常形象的名词。如图 13-3 所示,在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提高了搜索效率。 @@ -1926,7 +1926,7 @@ comments: true ??? pythontutor "可视化运行" - + 根据题意,我们在找到值为 $7$ 的节点后应该继续搜索,**因此需要将记录解之后的 `return` 语句删除**。图 13-4 对比了保留或删除 `return` 语句的搜索过程。 diff --git a/docs/chapter_backtracking/n_queens_problem.md b/docs/chapter_backtracking/n_queens_problem.md index 8aec7c995..de8acb336 100644 --- a/docs/chapter_backtracking/n_queens_problem.md +++ b/docs/chapter_backtracking/n_queens_problem.md @@ -746,7 +746,7 @@ comments: true ??? pythontutor "可视化运行" - + 逐行放置 $n$ 次,考虑列约束,则从第一行到最后一行分别有 $n$、$n-1$、$\dots$、$2$、$1$ 个选择,使用 $O(n!)$ 时间。当记录解时,需要复制矩阵 `state` 并添加进 `res` ,复制操作使用 $O(n^2)$ 时间。因此,**总体时间复杂度为 $O(n! \cdot n^2)$** 。实际上,根据对角线约束的剪枝也能够大幅缩小搜索空间,因而搜索效率往往优于以上时间复杂度。 diff --git a/docs/chapter_backtracking/permutations_problem.md b/docs/chapter_backtracking/permutations_problem.md index 873340e29..f4cd8065c 100644 --- a/docs/chapter_backtracking/permutations_problem.md +++ b/docs/chapter_backtracking/permutations_problem.md @@ -540,7 +540,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 13.2.2 考虑相等元素的情况 @@ -1087,7 +1087,7 @@ comments: true ??? pythontutor "可视化运行" - + 假设元素两两之间互不相同,则 $n$ 个元素共有 $n!$ 种排列(阶乘);在记录结果时,需要复制长度为 $n$ 的列表,使用 $O(n)$ 时间。**因此时间复杂度为 $O(n!n)$** 。 diff --git a/docs/chapter_backtracking/subset_sum_problem.md b/docs/chapter_backtracking/subset_sum_problem.md index eef470afb..f57fbb330 100644 --- a/docs/chapter_backtracking/subset_sum_problem.md +++ b/docs/chapter_backtracking/subset_sum_problem.md @@ -503,7 +503,7 @@ comments: true ??? pythontutor "可视化运行" - + 向以上代码输入数组 $[3, 4, 5]$ 和目标元素 $9$ ,输出结果为 $[3, 3, 3], [4, 5], [5, 4]$ 。**虽然成功找出了所有和为 $9$ 的子集,但其中存在重复的子集 $[4, 5]$ 和 $[5, 4]$** 。 @@ -1064,7 +1064,7 @@ comments: true ??? pythontutor "可视化运行" - + 图 13-12 所示为将数组 $[3, 4, 5]$ 和目标元素 $9$ 输入以上代码后的整体回溯过程。 @@ -1675,7 +1675,7 @@ comments: true ??? pythontutor "可视化运行" - + 图 13-14 展示了数组 $[4, 4, 5]$ 和目标元素 $9$ 的回溯过程,共包含四种剪枝操作。请你将图示与代码注释相结合,理解整个搜索过程,以及每种剪枝操作是如何工作的。 diff --git a/docs/chapter_computational_complexity/iteration_and_recursion.md b/docs/chapter_computational_complexity/iteration_and_recursion.md index 699f50d80..ec36d9bab 100644 --- a/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -200,7 +200,7 @@ comments: true ??? pythontutor "可视化运行" - + 图 2-1 是该求和函数的流程框图。 @@ -430,7 +430,7 @@ comments: true ??? pythontutor "可视化运行" - + **`while` 循环比 `for` 循环的自由度更高**。在 `while` 循环中,我们可以自由地设计条件变量的初始化和更新步骤。 @@ -675,7 +675,7 @@ comments: true ??? pythontutor "可视化运行" - + 总的来说,**`for` 循环的代码更加紧凑,`while` 循环更加灵活**,两者都可以实现迭代结构。选择使用哪一个应该根据特定问题的需求来决定。 @@ -910,7 +910,7 @@ comments: true ??? pythontutor "可视化运行" - + 图 2-2 是该嵌套循环的流程框图。 @@ -1133,7 +1133,7 @@ comments: true ??? pythontutor "可视化运行" - + 图 2-3 展示了该函数的递归过程。 @@ -1346,7 +1346,7 @@ comments: true ??? pythontutor "可视化运行" - + 尾递归的执行过程如图 2-5 所示。对比普通递归和尾递归,两者的求和操作的执行点是不同的。 @@ -1572,7 +1572,7 @@ comments: true ??? pythontutor "可视化运行" - + 观察以上代码,我们在函数内递归调用了两个函数,**这意味着从一个调用产生了两个调用分支**。如图 2-6 所示,这样不断递归调用下去,最终将产生一棵层数为 $n$ 的递归树(recursion tree)。 @@ -1917,7 +1917,7 @@ comments: true ??? pythontutor "可视化运行" - + 观察以上代码,当递归转化为迭代后,代码变得更加复杂了。尽管迭代和递归在很多情况下可以互相转化,但不一定值得这样做,有以下两点原因。 diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index c0aa74d2d..63d6be7b6 100755 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -1179,7 +1179,7 @@ $$ ??? pythontutor "可视化运行" - + ### 2. 线性阶 $O(n)$ {data-toc-label="2. 线性阶"} @@ -1457,7 +1457,7 @@ $$ ??? pythontutor "可视化运行" - + 如图 2-17 所示,此函数的递归深度为 $n$ ,即同时存在 $n$ 个未返回的 `linear_recur()` 函数,使用 $O(n)$ 大小的栈帧空间: @@ -1617,7 +1617,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } @@ -1850,7 +1850,7 @@ $$ ??? pythontutor "可视化运行" - + 如图 2-18 所示,该函数的递归深度为 $n$ ,在每个递归函数中都初始化了一个数组,长度分别为 $n$、$n-1$、$\dots$、$2$、$1$ ,平均长度为 $n / 2$ ,因此总体占用 $O(n^2)$ 空间: @@ -2031,7 +2031,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } @@ -2225,7 +2225,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index cdf1711ae..342907aec 100755 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -1194,7 +1194,7 @@ $$ ??? pythontutor "可视化运行" - + ### 2. 线性阶 $O(n)$ {data-toc-label="2. 线性阶"} @@ -1360,7 +1360,7 @@ $$ ??? pythontutor "可视化运行" - + 遍历数组和遍历链表等操作的时间复杂度均为 $O(n)$ ,其中 $n$ 为数组或链表的长度: @@ -1549,7 +1549,7 @@ $$ ??? pythontutor "可视化运行" - + 值得注意的是,**输入数据大小 $n$ 需根据输入数据的类型来具体确定**。比如在第一个示例中,变量 $n$ 为输入数据大小;在第二个示例中,数组长度 $n$ 为数据大小。 @@ -1767,7 +1767,7 @@ $$ ??? pythontutor "可视化运行" - + 图 2-10 对比了常数阶、线性阶和平方阶三种时间复杂度。 @@ -2076,7 +2076,7 @@ $$ ??? pythontutor "可视化运行" - + ### 4. 指数阶 $O(2^n)$ {data-toc-label="4. 指数阶"} @@ -2325,7 +2325,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } @@ -2477,7 +2477,7 @@ $$ ??? pythontutor "可视化运行" - + 指数阶增长非常迅速,在穷举法(暴力搜索、回溯等)中比较常见。对于数据规模较大的问题,指数阶是不可接受的,通常需要使用动态规划或贪心算法等来解决。 @@ -2674,7 +2674,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } @@ -2825,7 +2825,7 @@ $$ ??? pythontutor "可视化运行" - + 对数阶常出现于基于分治策略的算法中,体现了“一分为多”和“化繁为简”的算法思想。它增长缓慢,是仅次于常数阶的理想的时间复杂度。 @@ -3039,7 +3039,7 @@ $$ ??? pythontutor "可视化运行" - + 图 2-13 展示了线性对数阶的生成方式。二叉树的每一层的操作总数都为 $n$ ,树共有 $\log_2 n + 1$ 层,因此时间复杂度为 $O(n \log n)$ 。 @@ -3265,7 +3265,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } @@ -3659,7 +3659,7 @@ $$ ??? pythontutor "可视化运行" - + 值得说明的是,我们在实际中很少使用最佳时间复杂度,因为通常只有在很小概率下才能达到,可能会带来一定的误导性。**而最差时间复杂度更为实用,因为它给出了一个效率安全值**,让我们可以放心地使用算法。 diff --git a/docs/chapter_data_structure/basic_data_types.md b/docs/chapter_data_structure/basic_data_types.md index 1db5460c6..0dd6e7a02 100644 --- a/docs/chapter_data_structure/basic_data_types.md +++ b/docs/chapter_data_structure/basic_data_types.md @@ -180,5 +180,5 @@ comments: true ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_divide_and_conquer/binary_search_recur.md b/docs/chapter_divide_and_conquer/binary_search_recur.md index f7ce7677d..2c19c4c4b 100644 --- a/docs/chapter_divide_and_conquer/binary_search_recur.md +++ b/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -452,5 +452,5 @@ comments: true ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md index b167f7e39..d5ba06039 100644 --- a/docs/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -514,7 +514,7 @@ comments: true ??? pythontutor "可视化运行" - + 图 12-8 展示了构建二叉树的递归过程,各个节点是在向下“递”的过程中建立的,而各条边(引用)是在向上“归”的过程中建立的。 diff --git a/docs/chapter_divide_and_conquer/hanota_problem.md b/docs/chapter_divide_and_conquer/hanota_problem.md index e796cfd8a..92bafdec3 100644 --- a/docs/chapter_divide_and_conquer/hanota_problem.md +++ b/docs/chapter_divide_and_conquer/hanota_problem.md @@ -544,7 +544,7 @@ comments: true ??? pythontutor "可视化运行" - + 如图 12-15 所示,汉诺塔问题形成一棵高度为 $n$ 的递归树,每个节点代表一个子问题,对应一个开启的 `dfs()` 函数,**因此时间复杂度为 $O(2^n)$ ,空间复杂度为 $O(n)$** 。 diff --git a/docs/chapter_dynamic_programming/dp_problem_features.md b/docs/chapter_dynamic_programming/dp_problem_features.md index db17d2f6a..10c4ff14f 100644 --- a/docs/chapter_dynamic_programming/dp_problem_features.md +++ b/docs/chapter_dynamic_programming/dp_problem_features.md @@ -320,7 +320,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-7 展示了以上代码的动态规划过程。 @@ -583,7 +583,7 @@ $$ ??? pythontutor "可视化运行" - + ## 14.2.2 无后效性 @@ -944,7 +944,7 @@ $$ ??? pythontutor "可视化运行" - + 在上面的案例中,由于仅需多考虑前面一个状态,因此我们仍然可以通过扩展状态定义,使得问题重新满足无后效性。然而,某些问题具有非常严重的“有后效性”。 diff --git a/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/docs/chapter_dynamic_programming/dp_solution_pipeline.md index e1644d7d8..d6441376a 100644 --- a/docs/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -385,7 +385,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-14 给出了以 $dp[2, 1]$ 为根节点的递归树,其中包含一些重叠子问题,其数量会随着网格 `grid` 的尺寸变大而急剧增多。 @@ -744,7 +744,7 @@ $$ ??? pythontutor "可视化运行" - + 如图 14-15 所示,在引入记忆化后,所有子问题的解只需计算一次,因此时间复杂度取决于状态总数,即网格尺寸 $O(nm)$ 。 @@ -1119,7 +1119,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-16 展示了最小路径和的状态转移过程,其遍历了整个网格,**因此时间复杂度为 $O(nm)$** 。 @@ -1507,5 +1507,5 @@ $$ ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_dynamic_programming/edit_distance_problem.md b/docs/chapter_dynamic_programming/edit_distance_problem.md index c670a590d..57bf0dd9c 100644 --- a/docs/chapter_dynamic_programming/edit_distance_problem.md +++ b/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -479,7 +479,7 @@ $$ ??? pythontutor "可视化运行" - + 如图 14-30 所示,编辑距离问题的状态转移过程与背包问题非常类似,都可以看作填写一个二维网格的过程。 @@ -968,5 +968,5 @@ $$ ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md index 6ae1e0455..ec62eed2f 100644 --- a/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -422,7 +422,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 14.1.1 方法一:暴力搜索 @@ -697,7 +697,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-3 展示了暴力搜索形成的递归树。对于问题 $dp[n]$ ,其递归树的深度为 $n$ ,时间复杂度为 $O(2^n)$ 。指数阶属于爆炸式增长,如果我们输入一个比较大的 $n$ ,则会陷入漫长的等待之中。 @@ -1064,7 +1064,7 @@ $$ ??? pythontutor "可视化运行" - + 观察图 14-4 ,**经过记忆化处理后,所有重叠子问题都只需计算一次,时间复杂度优化至 $O(n)$** ,这是一个巨大的飞跃。 @@ -1340,7 +1340,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-5 模拟了以上代码的执行过程。 @@ -1577,7 +1577,7 @@ $$ ??? pythontutor "可视化运行" - + 观察以上代码,由于省去了数组 `dp` 占用的空间,因此空间复杂度从 $O(n)$ 降至 $O(1)$ 。 diff --git a/docs/chapter_dynamic_programming/knapsack_problem.md b/docs/chapter_dynamic_programming/knapsack_problem.md index c3c4b7b8f..05a16ef0c 100644 --- a/docs/chapter_dynamic_programming/knapsack_problem.md +++ b/docs/chapter_dynamic_programming/knapsack_problem.md @@ -340,7 +340,7 @@ $$ ??? pythontutor "可视化运行" - + 如图 14-18 所示,由于每个物品都会产生不选和选两条搜索分支,因此时间复杂度为 $O(2^n)$ 。 @@ -708,7 +708,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-19 展示了在记忆化搜索中被剪掉的搜索分支。 @@ -1059,7 +1059,7 @@ $$ ??? pythontutor "可视化运行" - + 如图 14-20 所示,时间复杂度和空间复杂度都由数组 `dp` 大小决定,即 $O(n \times cap)$ 。 @@ -1441,5 +1441,5 @@ $$ ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md index e1caa811f..5c90a8272 100644 --- a/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -373,7 +373,7 @@ $$ ??? pythontutor "可视化运行" - + ### 3. 空间优化 @@ -747,7 +747,7 @@ $$ ??? pythontutor "可视化运行" - + ## 14.5.2 零钱兑换问题 @@ -1194,7 +1194,7 @@ $$ ??? pythontutor "可视化运行" - + 图 14-25 展示了零钱兑换的动态规划过程,和完全背包问题非常相似。 @@ -1609,7 +1609,7 @@ $$ ??? pythontutor "可视化运行" - + ## 14.5.3 零钱兑换问题 II @@ -2011,7 +2011,7 @@ $$ ??? pythontutor "可视化运行" - + ### 3. 空间优化 @@ -2347,5 +2347,5 @@ $$ ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_graph/graph_operations.md b/docs/chapter_graph/graph_operations.md index 694ee0840..5419793f0 100644 --- a/docs/chapter_graph/graph_operations.md +++ b/docs/chapter_graph/graph_operations.md @@ -1208,7 +1208,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 9.2.2 基于邻接表的实现 @@ -2366,7 +2366,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 9.2.3 效率对比 diff --git a/docs/chapter_graph/graph_traversal.md b/docs/chapter_graph/graph_traversal.md index 8b90550e3..9a67da79e 100644 --- a/docs/chapter_graph/graph_traversal.md +++ b/docs/chapter_graph/graph_traversal.md @@ -477,7 +477,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先 ??? pythontutor "可视化运行" - + 代码相对抽象,建议对照图 9-10 来加深理解。 @@ -937,7 +937,7 @@ BFS 通常借助队列来实现,代码如下所示。队列具有“先入先 ??? pythontutor "可视化运行" - + 深度优先遍历的算法流程如图 9-12 所示。 diff --git a/docs/chapter_greedy/fractional_knapsack_problem.md b/docs/chapter_greedy/fractional_knapsack_problem.md index 62d03e498..ce4a580db 100644 --- a/docs/chapter_greedy/fractional_knapsack_problem.md +++ b/docs/chapter_greedy/fractional_knapsack_problem.md @@ -533,7 +533,7 @@ comments: true ??? pythontutor "可视化运行" - + 内置排序算法的时间复杂度通常为 $O(n \log n)$ ,空间复杂度通常为 $O(\log n)$ 或 $O(n)$ ,取决于编程语言的具体实现。 diff --git a/docs/chapter_greedy/greedy_algorithm.md b/docs/chapter_greedy/greedy_algorithm.md index 78de24e60..d2a201cf6 100644 --- a/docs/chapter_greedy/greedy_algorithm.md +++ b/docs/chapter_greedy/greedy_algorithm.md @@ -332,7 +332,7 @@ comments: true ??? pythontutor "可视化运行" - + 你可能会不由地发出感叹:So clean !贪心算法仅用约十行代码就解决了零钱兑换问题。 diff --git a/docs/chapter_greedy/max_capacity_problem.md b/docs/chapter_greedy/max_capacity_problem.md index 07f37414e..67dad0560 100644 --- a/docs/chapter_greedy/max_capacity_problem.md +++ b/docs/chapter_greedy/max_capacity_problem.md @@ -423,7 +423,7 @@ $$ ??? pythontutor "可视化运行" - + ### 3. 正确性证明 diff --git a/docs/chapter_greedy/max_product_cutting_problem.md b/docs/chapter_greedy/max_product_cutting_problem.md index c3599112d..d37f2bad0 100644 --- a/docs/chapter_greedy/max_product_cutting_problem.md +++ b/docs/chapter_greedy/max_product_cutting_problem.md @@ -388,7 +388,7 @@ $$ ??? pythontutor "可视化运行" - + { class="animation-figure" } diff --git a/docs/chapter_hashing/hash_algorithm.md b/docs/chapter_hashing/hash_algorithm.md index b41085712..68045ed1a 100644 --- a/docs/chapter_hashing/hash_algorithm.md +++ b/docs/chapter_hashing/hash_algorithm.md @@ -644,7 +644,7 @@ index = hash(key) % capacity ??? pythontutor "可视化运行" - + 观察发现,每种哈希算法的最后一步都是对大质数 $1000000007$ 取模,以确保哈希值在合适的范围内。值得思考的是,为什么要强调对质数取模,或者说对合数取模的弊端是什么?这是一个有趣的问题。 @@ -1002,7 +1002,7 @@ $$ ??? pythontutor "可视化运行" - + 在许多编程语言中,**只有不可变对象才可作为哈希表的 `key`** 。假如我们将列表(动态数组)作为 `key` ,当列表的内容发生变化时,它的哈希值也随之改变,我们就无法在哈希表中查询到原先的 `value` 了。 diff --git a/docs/chapter_hashing/hash_collision.md b/docs/chapter_hashing/hash_collision.md index 7373b137c..b95693962 100644 --- a/docs/chapter_hashing/hash_collision.md +++ b/docs/chapter_hashing/hash_collision.md @@ -1519,7 +1519,7 @@ comments: true ??? pythontutor "可视化运行" - + 值得注意的是,当链表很长时,查询效率 $O(n)$ 很差。**此时可以将链表转换为“AVL 树”或“红黑树”**,从而将查询操作的时间复杂度优化至 $O(\log n)$ 。 diff --git a/docs/chapter_hashing/hash_map.md b/docs/chapter_hashing/hash_map.md index 9b60dbfc0..10f630918 100755 --- a/docs/chapter_hashing/hash_map.md +++ b/docs/chapter_hashing/hash_map.md @@ -325,7 +325,7 @@ comments: true ??? pythontutor "可视化运行" - + 哈希表有三种常用的遍历方式:遍历键值对、遍历键和遍历值。示例代码如下: @@ -547,7 +547,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 6.1.2 哈希表简单实现 @@ -1755,7 +1755,7 @@ index = hash(key) % capacity ??? pythontutor "可视化运行" - + ## 6.1.3 哈希冲突与扩容 diff --git a/docs/chapter_heap/build_heap.md b/docs/chapter_heap/build_heap.md index a1f8c10c3..e12c48ba9 100644 --- a/docs/chapter_heap/build_heap.md +++ b/docs/chapter_heap/build_heap.md @@ -324,7 +324,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 8.2.3 复杂度分析 diff --git a/docs/chapter_heap/heap.md b/docs/chapter_heap/heap.md index f14335e48..361c00b8f 100644 --- a/docs/chapter_heap/heap.md +++ b/docs/chapter_heap/heap.md @@ -420,7 +420,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 8.1.2 堆的实现 @@ -808,7 +808,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 3. 元素入堆 @@ -1213,7 +1213,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 4. 堆顶元素出堆 @@ -1770,7 +1770,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 8.1.3 堆的常见应用 diff --git a/docs/chapter_heap/top_k.md b/docs/chapter_heap/top_k.md index 834ce5d8a..f20fc9d64 100644 --- a/docs/chapter_heap/top_k.md +++ b/docs/chapter_heap/top_k.md @@ -463,7 +463,7 @@ comments: true ??? pythontutor "可视化运行" - + 总共执行了 $n$ 轮入堆和出堆,堆的最大长度为 $k$ ,因此时间复杂度为 $O(n \log k)$ 。该方法的效率很高,当 $k$ 较小时,时间复杂度趋向 $O(n)$ ;当 $k$ 较大时,时间复杂度不会超过 $O(n \log n)$ 。 diff --git a/docs/chapter_searching/binary_search.md b/docs/chapter_searching/binary_search.md index 8b6e64657..f4cdf1b3f 100755 --- a/docs/chapter_searching/binary_search.md +++ b/docs/chapter_searching/binary_search.md @@ -364,7 +364,7 @@ comments: true ??? pythontutor "可视化运行" - + **时间复杂度为 $O(\log n)$** :在二分循环中,区间每轮缩小一半,因此循环次数为 $\log_2 n$ 。 @@ -688,7 +688,7 @@ comments: true ??? pythontutor "可视化运行" - + 如图 10-3 所示,在两种区间表示下,二分查找算法的初始化、循环条件和缩小区间操作皆有所不同。 diff --git a/docs/chapter_searching/binary_search_edge.md b/docs/chapter_searching/binary_search_edge.md index 27076dcb0..ee8161648 100644 --- a/docs/chapter_searching/binary_search_edge.md +++ b/docs/chapter_searching/binary_search_edge.md @@ -226,7 +226,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 10.3.2 查找右边界 @@ -481,7 +481,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 转化为查找元素 diff --git a/docs/chapter_searching/binary_search_insertion.md b/docs/chapter_searching/binary_search_insertion.md index aaa016f2f..da6ee83dc 100644 --- a/docs/chapter_searching/binary_search_insertion.md +++ b/docs/chapter_searching/binary_search_insertion.md @@ -317,7 +317,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 10.2.2 存在重复元素的情况 @@ -662,7 +662,7 @@ comments: true ??? pythontutor "可视化运行" - + !!! tip diff --git a/docs/chapter_searching/replace_linear_by_hashing.md b/docs/chapter_searching/replace_linear_by_hashing.md index 6225bc7d2..e250d7ac6 100755 --- a/docs/chapter_searching/replace_linear_by_hashing.md +++ b/docs/chapter_searching/replace_linear_by_hashing.md @@ -243,7 +243,7 @@ comments: true ??? pythontutor "可视化运行" - + 此方法的时间复杂度为 $O(n^2)$ ,空间复杂度为 $O(1)$ ,在大数据量下非常耗时。 @@ -538,7 +538,7 @@ comments: true ??? pythontutor "可视化运行" - + 此方法通过哈希查找将时间复杂度从 $O(n^2)$ 降至 $O(n)$ ,大幅提升运行效率。 diff --git a/docs/chapter_sorting/bubble_sort.md b/docs/chapter_sorting/bubble_sort.md index 5888bbd7f..8f74cc2a1 100755 --- a/docs/chapter_sorting/bubble_sort.md +++ b/docs/chapter_sorting/bubble_sort.md @@ -292,7 +292,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.3.2 效率优化 @@ -597,7 +597,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.3.3 算法特性 diff --git a/docs/chapter_sorting/bucket_sort.md b/docs/chapter_sorting/bucket_sort.md index cad768885..2d30f9e50 100644 --- a/docs/chapter_sorting/bucket_sort.md +++ b/docs/chapter_sorting/bucket_sort.md @@ -439,7 +439,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.8.2 算法特性 diff --git a/docs/chapter_sorting/counting_sort.md b/docs/chapter_sorting/counting_sort.md index eebe7e919..caf21c34a 100644 --- a/docs/chapter_sorting/counting_sort.md +++ b/docs/chapter_sorting/counting_sort.md @@ -363,7 +363,7 @@ comments: true ??? pythontutor "可视化运行" - + !!! note "计数排序与桶排序的联系" @@ -879,7 +879,7 @@ $$ ??? pythontutor "可视化运行" - + ## 11.9.3 算法特性 diff --git a/docs/chapter_sorting/heap_sort.md b/docs/chapter_sorting/heap_sort.md index 274fe3310..2aff3324f 100644 --- a/docs/chapter_sorting/heap_sort.md +++ b/docs/chapter_sorting/heap_sort.md @@ -614,7 +614,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.7.2 算法特性 diff --git a/docs/chapter_sorting/insertion_sort.md b/docs/chapter_sorting/insertion_sort.md index 8444051ef..9e6cb6d64 100755 --- a/docs/chapter_sorting/insertion_sort.md +++ b/docs/chapter_sorting/insertion_sort.md @@ -272,7 +272,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.4.2 算法特性 diff --git a/docs/chapter_sorting/merge_sort.md b/docs/chapter_sorting/merge_sort.md index 2c15f8c18..20c5b6143 100755 --- a/docs/chapter_sorting/merge_sort.md +++ b/docs/chapter_sorting/merge_sort.md @@ -681,7 +681,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.6.2 算法特性 diff --git a/docs/chapter_sorting/quick_sort.md b/docs/chapter_sorting/quick_sort.md index 0d6c486c4..038016381 100755 --- a/docs/chapter_sorting/quick_sort.md +++ b/docs/chapter_sorting/quick_sort.md @@ -368,7 +368,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.5.1 算法流程 @@ -595,7 +595,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.5.2 算法特性 @@ -1084,7 +1084,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.5.5 递归深度优化 @@ -1373,5 +1373,5 @@ comments: true ??? pythontutor "可视化运行" - + diff --git a/docs/chapter_sorting/radix_sort.md b/docs/chapter_sorting/radix_sort.md index e6fe97061..b60e993ef 100644 --- a/docs/chapter_sorting/radix_sort.md +++ b/docs/chapter_sorting/radix_sort.md @@ -718,7 +718,7 @@ $$ ??? pythontutor "可视化运行" - + !!! question "为什么从最低位开始排序?" diff --git a/docs/chapter_sorting/selection_sort.md b/docs/chapter_sorting/selection_sort.md index 6fc485712..7b3fe9767 100644 --- a/docs/chapter_sorting/selection_sort.md +++ b/docs/chapter_sorting/selection_sort.md @@ -326,7 +326,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 11.2.1 算法特性 diff --git a/docs/chapter_stack_and_queue/deque.md b/docs/chapter_stack_and_queue/deque.md index 34c075f35..2f85fac0d 100644 --- a/docs/chapter_stack_and_queue/deque.md +++ b/docs/chapter_stack_and_queue/deque.md @@ -395,7 +395,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 5.3.2 双向队列实现 * diff --git a/docs/chapter_stack_and_queue/queue.md b/docs/chapter_stack_and_queue/queue.md index 256d2d1cf..25eafba2c 100755 --- a/docs/chapter_stack_and_queue/queue.md +++ b/docs/chapter_stack_and_queue/queue.md @@ -368,7 +368,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 5.2.2 队列实现 @@ -1313,7 +1313,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 基于数组的实现 @@ -2288,7 +2288,7 @@ comments: true ??? pythontutor "可视化运行" - + 以上实现的队列仍然具有局限性:其长度不可变。然而,这个问题不难解决,我们可以将数组替换为动态数组,从而引入扩容机制。有兴趣的读者可以尝试自行实现。 diff --git a/docs/chapter_stack_and_queue/stack.md b/docs/chapter_stack_and_queue/stack.md index c1566eda9..900f97d01 100755 --- a/docs/chapter_stack_and_queue/stack.md +++ b/docs/chapter_stack_and_queue/stack.md @@ -361,7 +361,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 5.1.2 栈的实现 @@ -1162,7 +1162,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 基于数组的实现 @@ -1804,7 +1804,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 5.1.3 两种实现对比 diff --git a/docs/chapter_tree/array_representation_of_tree.md b/docs/chapter_tree/array_representation_of_tree.md index fb9366c2e..04302eee8 100644 --- a/docs/chapter_tree/array_representation_of_tree.md +++ b/docs/chapter_tree/array_representation_of_tree.md @@ -1330,7 +1330,7 @@ comments: true ??? pythontutor "可视化运行" - + ## 7.3.3 优点与局限性 diff --git a/docs/chapter_tree/binary_search_tree.md b/docs/chapter_tree/binary_search_tree.md index 9efa24876..3401248c2 100755 --- a/docs/chapter_tree/binary_search_tree.md +++ b/docs/chapter_tree/binary_search_tree.md @@ -340,7 +340,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 插入节点 @@ -804,7 +804,7 @@ comments: true ??? pythontutor "可视化运行" - + 与查找节点相同,插入节点使用 $O(\log n)$ 时间。 @@ -1590,7 +1590,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 4. 中序遍历有序 diff --git a/docs/chapter_tree/binary_tree.md b/docs/chapter_tree/binary_tree.md index c563fe99a..7434da2fc 100644 --- a/docs/chapter_tree/binary_tree.md +++ b/docs/chapter_tree/binary_tree.md @@ -459,7 +459,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 插入与删除节点 @@ -628,7 +628,7 @@ comments: true ??? pythontutor "可视化运行" - + !!! tip diff --git a/docs/chapter_tree/binary_tree_traversal.md b/docs/chapter_tree/binary_tree_traversal.md index 9dbbc550a..40a8b8752 100755 --- a/docs/chapter_tree/binary_tree_traversal.md +++ b/docs/chapter_tree/binary_tree_traversal.md @@ -336,7 +336,7 @@ comments: true ??? pythontutor "可视化运行" - + ### 2. 复杂度分析 @@ -821,7 +821,7 @@ comments: true ??? pythontutor "可视化运行" - + !!! tip diff --git a/en/docs/chapter_array_and_linkedlist/array.md b/en/docs/chapter_array_and_linkedlist/array.md index 118496c00..5cb2b7452 100755 --- a/en/docs/chapter_array_and_linkedlist/array.md +++ b/en/docs/chapter_array_and_linkedlist/array.md @@ -134,7 +134,7 @@ We can choose between two array initialization methods based on our needs: with ??? pythontutor "Code Visualization" - + ### 2. Accessing Elements diff --git a/en/docs/chapter_array_and_linkedlist/linked_list.md b/en/docs/chapter_array_and_linkedlist/linked_list.md index fc442b41f..6b8465313 100755 --- a/en/docs/chapter_array_and_linkedlist/linked_list.md +++ b/en/docs/chapter_array_and_linkedlist/linked_list.md @@ -421,7 +421,7 @@ Building a linked list involves two steps: first, initializing each node object; ??? pythontutor "Code Visualization" - + An array is a single variable; for example, an array `nums` contains elements `nums[0]`, `nums[1]`, and so on. A linked list, by contrast, is composed of multiple independent node objects. **We usually use the head node as a stand-in for the entire linked list**; for example, the linked list in the above code can be referred to as linked list `n0`. diff --git a/en/docs/chapter_array_and_linkedlist/list.md b/en/docs/chapter_array_and_linkedlist/list.md index 34b8b6125..96bed8f47 100755 --- a/en/docs/chapter_array_and_linkedlist/list.md +++ b/en/docs/chapter_array_and_linkedlist/list.md @@ -153,7 +153,7 @@ We typically initialize a list in one of two ways: empty or with predefined valu ??? pythontutor "Code Visualization" - + ### 2. Access Elements @@ -285,7 +285,7 @@ Since a list is essentially an array, we can access and update elements in $O(1) ??? pythontutor "Code Visualization" - + ### 3. Insert and Delete Elements @@ -540,7 +540,7 @@ Compared to arrays, lists can freely add and delete elements. Adding an element ??? pythontutor "Code Visualization" - + ### 4. Traverse a List @@ -742,7 +742,7 @@ Like arrays, lists can be traversed by index or by directly iterating through el ??? pythontutor "Code Visualization" - + ### 5. Concatenate Lists @@ -854,7 +854,7 @@ Given a new list `nums1`, we can concatenate it to the end of the original list. ??? pythontutor "Code Visualization" - + ### 6. Sort a List @@ -953,7 +953,7 @@ After sorting a list, we can use "binary search" and "two-pointer" algorithms, w ??? pythontutor "Code Visualization" - + ## 4.3.2 List Implementation diff --git a/en/docs/chapter_data_structure/basic_data_types.md b/en/docs/chapter_data_structure/basic_data_types.md index e22730fcc..7faca2a3e 100644 --- a/en/docs/chapter_data_structure/basic_data_types.md +++ b/en/docs/chapter_data_structure/basic_data_types.md @@ -180,5 +180,5 @@ In other words, **basic data types provide the "content type" of data, while dat ??? pythontutor "Visualized Execution" - + diff --git a/en/docs/chapter_hashing/hash_algorithm.md b/en/docs/chapter_hashing/hash_algorithm.md index cca51bef7..6479011d7 100644 --- a/en/docs/chapter_hashing/hash_algorithm.md +++ b/en/docs/chapter_hashing/hash_algorithm.md @@ -997,7 +997,7 @@ We know that hash table keys can be integers, floating-point numbers, strings, a ??? pythontutor "Visualized Execution" - + In many programming languages, **only immutable objects can serve as the `key` in a hash table**. If we use a list (dynamic array) as a `key`, when the contents of the list change, its hash value also changes, and we would no longer be able to find the original `value` in the hash table. diff --git a/en/docs/chapter_hashing/hash_map.md b/en/docs/chapter_hashing/hash_map.md index f718c554b..046ae5279 100755 --- a/en/docs/chapter_hashing/hash_map.md +++ b/en/docs/chapter_hashing/hash_map.md @@ -325,7 +325,7 @@ Common operations on hash tables include: initialization, query operations, addi ??? pythontutor "Visualized Execution" - + There are three common ways to traverse a hash table: traversing key-value pairs, traversing keys, and traversing values. Example code is as follows: @@ -547,7 +547,7 @@ There are three common ways to traverse a hash table: traversing key-value pairs ??? pythontutor "Visualized Execution" - + ## 6.1.2 Simple Hash Table Implementation diff --git a/en/docs/chapter_heap/heap.md b/en/docs/chapter_heap/heap.md index f1faff2be..14aee70bd 100644 --- a/en/docs/chapter_heap/heap.md +++ b/en/docs/chapter_heap/heap.md @@ -420,7 +420,7 @@ Similar to "ascending order" and "descending order" in sorting algorithms, we ca ??? pythontutor "Code Visualization" - + ## 8.1.2 Implementation of the Heap diff --git a/en/docs/chapter_stack_and_queue/deque.md b/en/docs/chapter_stack_and_queue/deque.md index 3a7353e5d..631d4d01c 100644 --- a/en/docs/chapter_stack_and_queue/deque.md +++ b/en/docs/chapter_stack_and_queue/deque.md @@ -395,7 +395,7 @@ Similarly, we can directly use the deque classes provided by the programming lan ??? pythontutor "Code Visualization" - + ## 5.3.2 Deque Implementation * diff --git a/en/docs/chapter_stack_and_queue/queue.md b/en/docs/chapter_stack_and_queue/queue.md index 7f4a06907..2f6c54a2f 100755 --- a/en/docs/chapter_stack_and_queue/queue.md +++ b/en/docs/chapter_stack_and_queue/queue.md @@ -368,7 +368,7 @@ We can directly use the queue classes provided by the programming language: ??? pythontutor "Code Visualization" - + ## 5.2.2 Queue Implementation diff --git a/en/docs/chapter_stack_and_queue/stack.md b/en/docs/chapter_stack_and_queue/stack.md index 48f0e61bf..84ec2c01f 100755 --- a/en/docs/chapter_stack_and_queue/stack.md +++ b/en/docs/chapter_stack_and_queue/stack.md @@ -361,7 +361,7 @@ Typically, we can directly use the built-in stack class provided by the programm ??? pythontutor "Code Visualization" - + ## 5.1.2 Stack Implementation diff --git a/en/docs/chapter_tree/binary_tree.md b/en/docs/chapter_tree/binary_tree.md index 9ab02878a..920c11c65 100644 --- a/en/docs/chapter_tree/binary_tree.md +++ b/en/docs/chapter_tree/binary_tree.md @@ -459,7 +459,7 @@ Similar to a linked list, the initialization of a binary tree involves first cre ??? pythontutor "Code Visualization" - + ### 2. Inserting and Removing Nodes @@ -624,7 +624,7 @@ Similar to a linked list, inserting and removing nodes in a binary tree can be a ??? pythontutor "Code Visualization" - + !!! tip diff --git a/ja/docs/chapter_array_and_linkedlist/array.md b/ja/docs/chapter_array_and_linkedlist/array.md index 673d7b0a3..bf0c98f09 100644 --- a/ja/docs/chapter_array_and_linkedlist/array.md +++ b/ja/docs/chapter_array_and_linkedlist/array.md @@ -134,7 +134,7 @@ comments: true ??? pythontutor "実行の可視化" - + ### 2. 要素へのアクセス @@ -320,7 +320,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 3. 要素の挿入 @@ -516,7 +516,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 4. 要素の削除 @@ -686,7 +686,7 @@ comments: true ??? pythontutor "コードの可視化" - + 全体として見ると、配列の挿入と削除には次の欠点があります。 @@ -933,7 +933,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 6. 要素の検索 @@ -1115,7 +1115,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 7. 配列の拡張 @@ -1345,7 +1345,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 4.1.2 配列の利点と限界 diff --git a/ja/docs/chapter_array_and_linkedlist/linked_list.md b/ja/docs/chapter_array_and_linkedlist/linked_list.md index f7b7d119d..26aa0e9bd 100644 --- a/ja/docs/chapter_array_and_linkedlist/linked_list.md +++ b/ja/docs/chapter_array_and_linkedlist/linked_list.md @@ -421,7 +421,7 @@ comments: true ??? pythontutor "可視化実行" - + 配列全体は 1 つの変数であり、たとえば配列 `nums` には `nums[0]` や `nums[1]` などの要素が含まれます。一方、連結リストは複数の独立したノードオブジェクトで構成されます。**通常、先頭ノードを連結リストの代名詞として扱います**。たとえば上記のコードの連結リストは `n0` と表せます。 @@ -582,7 +582,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 3. ノードの削除 @@ -785,7 +785,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 4. ノードへのアクセス @@ -989,7 +989,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 5. ノードの探索 @@ -1215,7 +1215,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 4.2.2 配列 vs. 連結リスト diff --git a/ja/docs/chapter_array_and_linkedlist/list.md b/ja/docs/chapter_array_and_linkedlist/list.md index 788ebaade..2c0f42554 100644 --- a/ja/docs/chapter_array_and_linkedlist/list.md +++ b/ja/docs/chapter_array_and_linkedlist/list.md @@ -153,7 +153,7 @@ comments: true ??? pythontutor "可視化実行" - + ### 2. 要素へのアクセス @@ -285,7 +285,7 @@ comments: true ??? pythontutor "可視化実行" - + ### 3. 要素の挿入と削除 @@ -540,7 +540,7 @@ comments: true ??? pythontutor "可視化実行" - + ### 4. リストの走査 @@ -742,7 +742,7 @@ comments: true ??? pythontutor "可視化実行" - + ### 5. リストの連結 @@ -854,7 +854,7 @@ comments: true ??? pythontutor "可視化実行" - + ### 6. リストをソート @@ -953,7 +953,7 @@ comments: true ??? pythontutor "可視化実行" - + ## 4.3.2 リストの実装 @@ -2297,5 +2297,5 @@ comments: true ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_backtracking/backtracking_algorithm.md b/ja/docs/chapter_backtracking/backtracking_algorithm.md index 20b4740d2..edf2ef40d 100644 --- a/ja/docs/chapter_backtracking/backtracking_algorithm.md +++ b/ja/docs/chapter_backtracking/backtracking_algorithm.md @@ -234,7 +234,7 @@ comments: true ??? pythontutor "コードの可視化" - + { class="animation-figure" } @@ -545,7 +545,7 @@ comments: true ??? pythontutor "コードの可視化" - + 各「試行」で現在のノードを `path` に追加して経路を記録し、「戻る」前にはそのノードを `path` から取り除き、**今回の試行前の状態を復元する**必要があります。 @@ -899,7 +899,7 @@ comments: true ??? pythontutor "コードの可視化" - + 「枝刈り」は非常にイメージしやすい名称です。次の図のように、探索中に**制約条件を満たさない探索分岐を切り落とす**ことで、多くの無意味な試行を避け、探索効率を高められます。 @@ -1926,7 +1926,7 @@ comments: true ??? pythontutor "コードの可視化" - + 問題の条件より、値が $7$ のノードを見つけた後も探索を続ける必要があります。**そのため、解を記録した後の `return` 文は削除しなければなりません**。次の図は、`return` 文を残す場合と削除する場合の探索過程を比較したものです。 diff --git a/ja/docs/chapter_backtracking/n_queens_problem.md b/ja/docs/chapter_backtracking/n_queens_problem.md index e55327cc1..b26617bb7 100644 --- a/ja/docs/chapter_backtracking/n_queens_problem.md +++ b/ja/docs/chapter_backtracking/n_queens_problem.md @@ -746,7 +746,7 @@ comments: true ??? pythontutor "コードの可視化" - + 行ごとに $n$ 回配置し、列制約を考慮すると、1 行目から最終行までの選択肢はそれぞれ $n$、$n-1$、$\dots$、$2$、$1$ 個となるため、時間計算量は $O(n!)$ です。解を記録する際には、行列 `state` をコピーして `res` に追加する必要があり、このコピー操作には $O(n^2)$ 時間を要します。したがって、**全体の時間計算量は $O(n! \cdot n^2)$** です。実際には、対角線制約による枝刈りも探索空間を大きく縮小できるため、探索効率はしばしば上記の時間計算量より良くなります。 diff --git a/ja/docs/chapter_backtracking/permutations_problem.md b/ja/docs/chapter_backtracking/permutations_problem.md index 8c3ea74a3..05b611b84 100644 --- a/ja/docs/chapter_backtracking/permutations_problem.md +++ b/ja/docs/chapter_backtracking/permutations_problem.md @@ -540,7 +540,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 13.2.2 等しい要素を考慮する場合 @@ -1087,7 +1087,7 @@ comments: true ??? pythontutor "コードの可視化" - + 要素どうしがすべて互いに異なると仮定すると、$n$ 個の要素には全部で $n!$ 通りの順列(階乗)があります。結果を記録する際には、長さ $n$ のリストをコピーする必要があり、これに $O(n)$ 時間を要します。**したがって時間計算量は $O(n!n)$** です。 diff --git a/ja/docs/chapter_backtracking/subset_sum_problem.md b/ja/docs/chapter_backtracking/subset_sum_problem.md index 9747f7052..832d7f373 100644 --- a/ja/docs/chapter_backtracking/subset_sum_problem.md +++ b/ja/docs/chapter_backtracking/subset_sum_problem.md @@ -524,7 +524,7 @@ comments: true ??? pythontutor "コードの可視化" - + 上のコードに配列 $[3, 4, 5]$ と目標値 $9$ を入力すると、出力は $[3, 3, 3], [4, 5], [5, 4]$ となります。**和が $9$ となる部分集合はすべて見つかっていますが、重複する部分集合 $[4, 5]$ と $[5, 4]$ が含まれています**。 @@ -1085,7 +1085,7 @@ comments: true ??? pythontutor "コードの可視化" - + 次の図は、配列 $[3, 4, 5]$ と目標値 $9$ を上のコードに入力したときの、全体のバックトラッキング過程を示しています。 @@ -1696,7 +1696,7 @@ comments: true ??? pythontutor "コードの可視化" - + 次の図は、配列 $[4, 4, 5]$ と目標値 $9$ に対するバックトラッキング過程を示しており、全部で 4 種類の枝刈り操作が含まれています。図とコードコメントを対応させながら、探索全体の流れと、各枝刈り操作がどのように機能するかを理解してください。 diff --git a/ja/docs/chapter_computational_complexity/iteration_and_recursion.md b/ja/docs/chapter_computational_complexity/iteration_and_recursion.md index 4296b14a6..8b80793d1 100644 --- a/ja/docs/chapter_computational_complexity/iteration_and_recursion.md +++ b/ja/docs/chapter_computational_complexity/iteration_and_recursion.md @@ -200,7 +200,7 @@ comments: true ??? pythontutor "コードの可視化" - + 次の図は、この総和関数のフローチャートです。 @@ -430,7 +430,7 @@ comments: true ??? pythontutor "コードの可視化" - + **`while` ループは `for` ループより自由度が高い**です。`while` ループでは、条件変数の初期化や更新手順を柔軟に設計できます。 @@ -689,7 +689,7 @@ comments: true ??? pythontutor "コードの可視化" - + 総じて、**`for` ループのコードはより簡潔で、`while` ループはより柔軟**です。どちらも反復構造を実現できますが、どちらを使うかは問題ごとの要件に応じて決めるべきです。 @@ -924,7 +924,7 @@ comments: true ??? pythontutor "コードの可視化" - + 次の図は、このネストしたループのフローチャートです。 @@ -1147,7 +1147,7 @@ comments: true ??? pythontutor "コードの可視化" - + 次の図は、この関数の再帰過程を示しています。 @@ -1360,7 +1360,7 @@ $1 + 2 + \dots + n$ の計算を例にすると、結果変数 `res` を関数 ??? pythontutor "コードの可視化" - + 末尾再帰の実行過程を次の図に示します。通常の再帰と末尾再帰を比べると、加算処理が実行されるタイミングが異なります。 @@ -1586,7 +1586,7 @@ $1 + 2 + \dots + n$ の計算を例にすると、結果変数 `res` を関数 ??? pythontutor "コードの可視化" - + 上のコードを見ると、関数内で 2 回の再帰呼び出しを行っています。**これは 1 回の呼び出しから 2 つの呼び出し分岐が生じることを意味します**。次の図のように、この再帰呼び出しを繰り返していくと、最終的に深さ $n$ の再帰木(recursion tree)が生成されます。 @@ -1931,7 +1931,7 @@ $1 + 2 + \dots + n$ の計算を例にすると、結果変数 `res` を関数 ??? pythontutor "コードの可視化" - + 上のコードを見ると、再帰を反復へ変換すると、コードはより複雑になります。反復と再帰は多くの場合に相互変換できますが、常にそうする価値があるとは限りません。理由は次の 2 点です。 diff --git a/ja/docs/chapter_computational_complexity/space_complexity.md b/ja/docs/chapter_computational_complexity/space_complexity.md index eecfc13b9..53786914c 100644 --- a/ja/docs/chapter_computational_complexity/space_complexity.md +++ b/ja/docs/chapter_computational_complexity/space_complexity.md @@ -1179,7 +1179,7 @@ $$ ??? pythontutor "コードの可視化" - + ### 2. 線形階 $O(n)$ {data-toc-label="2. 線形階"} @@ -1457,7 +1457,7 @@ $$ ??? pythontutor "コードの可視化" - + 以下の図に示すように、この関数の再帰の深さは $n$ であり、同時に $n$ 個の未返却 `linear_recur()` 関数が存在するため、$O(n)$ のスタックフレーム空間を使用します。 @@ -1629,7 +1629,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } @@ -1862,7 +1862,7 @@ $$ ??? pythontutor "コードの可視化" - + 以下の図に示すように、この関数の再帰の深さは $n$ であり、各再帰関数の中で長さがそれぞれ $n$、$n-1$、$\dots$、$2$、$1$ の配列を初期化しています。平均長は $n / 2$ なので、全体では $O(n^2)$ の空間を占有します。 @@ -2049,7 +2049,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } @@ -2258,7 +2258,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } diff --git a/ja/docs/chapter_computational_complexity/time_complexity.md b/ja/docs/chapter_computational_complexity/time_complexity.md index 56463f24c..256413861 100644 --- a/ja/docs/chapter_computational_complexity/time_complexity.md +++ b/ja/docs/chapter_computational_complexity/time_complexity.md @@ -1194,7 +1194,7 @@ $$ ??? pythontutor "コードの可視化" - + ### 2. 線形階 $O(n)$ {data-toc-label="2. 線形階"} @@ -1360,7 +1360,7 @@ $$ ??? pythontutor "コードの可視化" - + 配列走査や連結リスト走査などの操作の時間計算量はいずれも $O(n)$ であり、ここでの $n$ は配列または連結リストの長さです: @@ -1556,7 +1556,7 @@ $$ ??? pythontutor "コードの可視化" - + 注意すべきなのは、**入力データサイズ $n$ は入力データの型に応じて具体的に定める必要がある**ということです。例えば 1 つ目の例では変数 $n$ が入力データサイズであり、2 つ目の例では配列長 $n$ がデータサイズです。 @@ -1774,7 +1774,7 @@ $$ ??? pythontutor "コードの可視化" - + 以下の図は、定数階・線形階・平方階の 3 種類の時間計算量を比較したものです。 @@ -2097,7 +2097,7 @@ $$ ??? pythontutor "コードの可視化" - + ### 4. 指数階 $O(2^n)$ {data-toc-label="4. 指数階"} @@ -2381,7 +2381,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } @@ -2582,7 +2582,7 @@ $$ ??? pythontutor "コードの可視化" - + 指数階の増加は非常に速く、全探索法(ブルートフォース、バックトラッキングなど)によく見られます。データ規模が大きい問題では、指数階は受け入れられず、通常は動的計画法や貪欲法などを使って解く必要があります。 @@ -2834,7 +2834,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } @@ -3052,7 +3052,7 @@ $$ ??? pythontutor "コードの可視化" - + 対数階は分割統治に基づくアルゴリズムによく現れ、「1 つを複数に分ける」「複雑なものを単純化する」という考え方を体現しています。増加は緩やかで、定数階に次いで理想的な時間計算量です。 @@ -3266,7 +3266,7 @@ $$ ??? pythontutor "コードの可視化" - + 下図は線形対数階がどのように生じるかを示しています。二分木の各層の操作総数はすべて $n$ であり、木全体は $\log_2 n + 1$ 層あるため、時間計算量は $O(n \log n)$ です。 @@ -3502,7 +3502,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } @@ -3896,7 +3896,7 @@ $$ ??? pythontutor "コードの可視化" - + 実際には、最良時間計算量を使うことはあまりありません。通常それが実現する確率はごく低く、誤解を招く可能性があるからです。**一方で最悪時間計算量はより実用的で、効率の安全側の目安を与えてくれる**ため、安心してアルゴリズムを使えます。 diff --git a/ja/docs/chapter_data_structure/basic_data_types.md b/ja/docs/chapter_data_structure/basic_data_types.md index 436640e81..772e8d257 100644 --- a/ja/docs/chapter_data_structure/basic_data_types.md +++ b/ja/docs/chapter_data_structure/basic_data_types.md @@ -180,5 +180,5 @@ comments: true ??? pythontutor "実行の可視化" - + diff --git a/ja/docs/chapter_divide_and_conquer/binary_search_recur.md b/ja/docs/chapter_divide_and_conquer/binary_search_recur.md index 491d30c3d..98a48acae 100644 --- a/ja/docs/chapter_divide_and_conquer/binary_search_recur.md +++ b/ja/docs/chapter_divide_and_conquer/binary_search_recur.md @@ -452,5 +452,5 @@ comments: true ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md b/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md index e8030e586..ed58a1017 100644 --- a/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md +++ b/ja/docs/chapter_divide_and_conquer/build_binary_tree_problem.md @@ -514,7 +514,7 @@ $m$ の検索効率を高めるために、ハッシュテーブル `hmap` を ??? pythontutor "コードの可視化" - + 下図は二分木を構築する再帰過程を示しています。各ノードは下向きに「再帰していく」過程で生成され、各辺(参照)は上向きに「戻る」過程で張られます。 diff --git a/ja/docs/chapter_divide_and_conquer/hanota_problem.md b/ja/docs/chapter_divide_and_conquer/hanota_problem.md index 3e3976694..5c8b372d2 100644 --- a/ja/docs/chapter_divide_and_conquer/hanota_problem.md +++ b/ja/docs/chapter_divide_and_conquer/hanota_problem.md @@ -544,7 +544,7 @@ $f(1)$ と $f(2)$ の解が既知なので、分割統治の観点から、**`A` ??? pythontutor "コードの可視化" - + 以下の図に示すように、ハノイの塔の問題は高さ $n$ の再帰木を形成し、各ノードは 1 つの部分問題、すなわち 1 つ起動された `dfs()` 関数に対応します。**したがって時間計算量は $O(2^n)$ 、空間計算量は $O(n)$** です。 diff --git a/ja/docs/chapter_dynamic_programming/dp_problem_features.md b/ja/docs/chapter_dynamic_programming/dp_problem_features.md index 781ffaa40..0a610294c 100644 --- a/ja/docs/chapter_dynamic_programming/dp_problem_features.md +++ b/ja/docs/chapter_dynamic_programming/dp_problem_features.md @@ -320,7 +320,7 @@ $$ ??? pythontutor "コードの可視化" - + 下図は上記コードの動的計画法の過程を示しています。 @@ -583,7 +583,7 @@ $$ ??? pythontutor "コードの可視化" - + ## 14.2.2 無後効性 @@ -944,7 +944,7 @@ $$ ??? pythontutor "コードの可視化" - + 上の例では、追加で考慮すべきなのは一つ前の状態だけであるため、状態定義を拡張することで問題を再び無後効性に適合させることができます。しかし、問題によっては非常に強い「後効性」があります。 diff --git a/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md b/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md index 85574b6ae..0664fc75c 100644 --- a/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md +++ b/ja/docs/chapter_dynamic_programming/dp_solution_pipeline.md @@ -385,7 +385,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図は、$dp[2, 1]$ を根ノードとする再帰木を示しています。この中にはいくつかの重複部分問題が含まれており、その数はグリッド `grid` のサイズが大きくなるにつれて急激に増加します。 @@ -744,7 +744,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図に示すように、メモ化を導入すると、すべての部分問題の解は 1 回だけ計算すればよくなります。したがって時間計算量は状態総数、すなわちグリッドサイズの $O(nm)$ に依存します。 @@ -1119,7 +1119,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図は最小経路和の状態遷移の過程を示しています。グリッド全体を走査するため、**時間計算量は $O(nm)$** です。 @@ -1507,5 +1507,5 @@ $$ ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_dynamic_programming/edit_distance_problem.md b/ja/docs/chapter_dynamic_programming/edit_distance_problem.md index 24112489e..a9ec94687 100644 --- a/ja/docs/chapter_dynamic_programming/edit_distance_problem.md +++ b/ja/docs/chapter_dynamic_programming/edit_distance_problem.md @@ -479,7 +479,7 @@ $$ ??? pythontutor "コードの可視化" - + 下図に示すように、編集距離問題の状態遷移の過程はナップサック問題と非常によく似ており、どちらも2次元グリッドを埋めていく過程とみなせます。 @@ -968,5 +968,5 @@ $dp[i,j]$ は上の $dp[i-1, j]$、左の $dp[i, j-1]$、左上の $dp[i-1, j-1] ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md b/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md index 72fbf6d02..d1588f47b 100644 --- a/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/ja/docs/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -422,7 +422,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 14.1.1 方法 1:総当たり探索 @@ -697,7 +697,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図は総当たり探索によって形成される再帰木を示しています。問題 $dp[n]$ に対して、その再帰木の深さは $n$、時間計算量は $O(2^n)$ です。指数オーダーは爆発的に増加するため、比較的大きな $n$ を入力すると長時間待たされることになります。 @@ -1064,7 +1064,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図を見ると、**メモ化を行うことで、すべての重複部分問題は 1 回だけ計算すればよくなり、時間計算量は $O(n)$ まで改善されます**。これは大きな飛躍です。 @@ -1340,7 +1340,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図は、以上のコードの実行過程をシミュレートしたものです。 @@ -1577,7 +1577,7 @@ $$ ??? pythontutor "コードの可視化" - + 上のコードを見ると、配列 `dp` が占めていた領域を省けるため、空間計算量は $O(n)$ から $O(1)$ へと下がります。 diff --git a/ja/docs/chapter_dynamic_programming/knapsack_problem.md b/ja/docs/chapter_dynamic_programming/knapsack_problem.md index f2b31016d..49cf7c88a 100644 --- a/ja/docs/chapter_dynamic_programming/knapsack_problem.md +++ b/ja/docs/chapter_dynamic_programming/knapsack_problem.md @@ -340,7 +340,7 @@ $$ ??? pythontutor "コードの可視化" - + 以下の図のように、各品物ごとに「選ばない」「選ぶ」の2つの探索分岐が生じるため、時間計算量は $O(2^n)$ です。 @@ -708,7 +708,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図は、メモ化探索で剪定された探索分岐を示しています。 @@ -1059,7 +1059,7 @@ $$ ??? pythontutor "コードの可視化" - + 以下の図のように、時間計算量と空間計算量はいずれも配列 `dp` のサイズによって決まり、$O(n \times cap)$ です。 @@ -1441,5 +1441,5 @@ $$ ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md b/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md index 52ae74f56..794886b09 100644 --- a/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md +++ b/ja/docs/chapter_dynamic_programming/unbounded_knapsack_problem.md @@ -373,7 +373,7 @@ $$ ??? pythontutor "コードの可視化" - + ### 3. 空間最適化 @@ -747,7 +747,7 @@ $$ ??? pythontutor "コードの可視化" - + ## 14.5.2 硬貨交換問題 @@ -1194,7 +1194,7 @@ $$ ??? pythontutor "コードの可視化" - + 次の図は硬貨交換の動的計画法の過程を示しており、完全ナップサック問題と非常によく似ています。 @@ -1609,7 +1609,7 @@ $$ ??? pythontutor "コードの可視化" - + ## 14.5.3 硬貨交換問題 II @@ -2011,7 +2011,7 @@ $$ ??? pythontutor "コードの可視化" - + ### 3. 空間最適化 @@ -2347,5 +2347,5 @@ $$ ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_graph/graph_operations.md b/ja/docs/chapter_graph/graph_operations.md index 64abc24f2..efc4191cf 100644 --- a/ja/docs/chapter_graph/graph_operations.md +++ b/ja/docs/chapter_graph/graph_operations.md @@ -1208,7 +1208,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 9.2.2 隣接リストに基づく実装 @@ -2366,7 +2366,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 9.2.3 効率の比較 diff --git a/ja/docs/chapter_graph/graph_traversal.md b/ja/docs/chapter_graph/graph_traversal.md index 149d6710d..200e35b06 100644 --- a/ja/docs/chapter_graph/graph_traversal.md +++ b/ja/docs/chapter_graph/graph_traversal.md @@ -477,7 +477,7 @@ BFS は通常キューを用いて実装され、コードは以下のとおり ??? pythontutor "コードの可視化" - + コードはやや抽象的なので、以下の図と照らし合わせて理解を深めることを勧めます。 @@ -937,7 +937,7 @@ BFS は通常キューを用いて実装され、コードは以下のとおり ??? pythontutor "コードの可視化" - + 深さ優先走査のアルゴリズムの流れは以下の図のとおりです。 diff --git a/ja/docs/chapter_greedy/fractional_knapsack_problem.md b/ja/docs/chapter_greedy/fractional_knapsack_problem.md index fe53f2a4a..ac6083a88 100644 --- a/ja/docs/chapter_greedy/fractional_knapsack_problem.md +++ b/ja/docs/chapter_greedy/fractional_knapsack_problem.md @@ -533,7 +533,7 @@ comments: true ??? pythontutor "コードの可視化" - + 組み込みのソートアルゴリズムの時間計算量は通常 $O(n \log n)$、空間計算量は通常 $O(\log n)$ または $O(n)$ であり、具体的な値はプログラミング言語の実装に依存する。 diff --git a/ja/docs/chapter_greedy/greedy_algorithm.md b/ja/docs/chapter_greedy/greedy_algorithm.md index 22ff9eaa1..7260ed3b1 100644 --- a/ja/docs/chapter_greedy/greedy_algorithm.md +++ b/ja/docs/chapter_greedy/greedy_algorithm.md @@ -332,7 +332,7 @@ comments: true ??? pythontutor "コードの可視化" - + 思わずこう言いたくなるかもしれません。So clean!貪欲法はわずか十行ほどのコードでコイン両替問題を解いてしまいます。 diff --git a/ja/docs/chapter_greedy/max_capacity_problem.md b/ja/docs/chapter_greedy/max_capacity_problem.md index a3f067991..3dddb7bcf 100644 --- a/ja/docs/chapter_greedy/max_capacity_problem.md +++ b/ja/docs/chapter_greedy/max_capacity_problem.md @@ -423,7 +423,7 @@ $$ ??? pythontutor "コードの可視化" - + ### 3. 正しさの証明 diff --git a/ja/docs/chapter_greedy/max_product_cutting_problem.md b/ja/docs/chapter_greedy/max_product_cutting_problem.md index 76ef45ae6..763557148 100644 --- a/ja/docs/chapter_greedy/max_product_cutting_problem.md +++ b/ja/docs/chapter_greedy/max_product_cutting_problem.md @@ -388,7 +388,7 @@ $$ ??? pythontutor "コードの可視化" - + { class="animation-figure" } diff --git a/ja/docs/chapter_hashing/hash_algorithm.md b/ja/docs/chapter_hashing/hash_algorithm.md index d528bc54e..8b7684866 100644 --- a/ja/docs/chapter_hashing/hash_algorithm.md +++ b/ja/docs/chapter_hashing/hash_algorithm.md @@ -644,7 +644,7 @@ index = hash(key) % capacity ??? pythontutor "コードの可視化" - + 見て分かるように、各ハッシュアルゴリズムの最後のステップでは、大きな素数 $1000000007$ で剰余を取り、ハッシュ値が適切な範囲に収まるようにしています。ここで考えてみる価値があるのは、なぜ素数での剰余を強調するのか、あるいは合成数で剰余を取ることにどんな欠点があるのか、という点です。これは興味深い問題です。 @@ -1002,7 +1002,7 @@ $$ ??? pythontutor "可視化実行" - + 多くのプログラミング言語では、**不変オブジェクトだけがハッシュテーブルの `key` として使えます**。仮にリスト(動的配列)を `key` とすると、その内容が変化したときにハッシュ値も変わってしまうため、もとの `value` をハッシュテーブルから検索できなくなります。 diff --git a/ja/docs/chapter_hashing/hash_collision.md b/ja/docs/chapter_hashing/hash_collision.md index dbca6767c..6682d2e6c 100644 --- a/ja/docs/chapter_hashing/hash_collision.md +++ b/ja/docs/chapter_hashing/hash_collision.md @@ -1519,7 +1519,7 @@ comments: true ??? pythontutor "コードの可視化" - + 注意すべきなのは、連結リストが長い場合、検索効率 $O(n)$ は非常に低いことです。**このとき、連結リストを「AVL 木」または「赤黒木」に変換することで**、検索操作の時間計算量を $O(\log n)$ に最適化できます。 diff --git a/ja/docs/chapter_hashing/hash_map.md b/ja/docs/chapter_hashing/hash_map.md index 763a65ba2..2da72cce1 100644 --- a/ja/docs/chapter_hashing/hash_map.md +++ b/ja/docs/chapter_hashing/hash_map.md @@ -325,7 +325,7 @@ comments: true ??? pythontutor "可視化実行" - + ハッシュテーブルには、キーと値のペア、キー、値を走査する 3 つの一般的な方法があります。コード例は以下のとおりです: @@ -547,7 +547,7 @@ comments: true ??? pythontutor "可視化実行" - + ## 6.1.2 ハッシュテーブルの簡単な実装 @@ -1755,7 +1755,7 @@ index = hash(key) % capacity ??? pythontutor "コードの可視化" - + ## 6.1.3 ハッシュ衝突と拡張 diff --git a/ja/docs/chapter_heap/build_heap.md b/ja/docs/chapter_heap/build_heap.md index e0a14037d..094072524 100644 --- a/ja/docs/chapter_heap/build_heap.md +++ b/ja/docs/chapter_heap/build_heap.md @@ -324,7 +324,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 8.2.3 計算量の分析 diff --git a/ja/docs/chapter_heap/heap.md b/ja/docs/chapter_heap/heap.md index 63deb2e23..4a82e4b9a 100644 --- a/ja/docs/chapter_heap/heap.md +++ b/ja/docs/chapter_heap/heap.md @@ -420,7 +420,7 @@ comments: true ??? pythontutor "実行を可視化" - + ## 8.1.2 ヒープの実装 @@ -808,7 +808,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 3. 要素をヒープに追加 @@ -1213,7 +1213,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 4. ヒープ頂点の要素を取り出す @@ -1770,7 +1770,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 8.1.3 ヒープの代表的な応用 diff --git a/ja/docs/chapter_heap/top_k.md b/ja/docs/chapter_heap/top_k.md index 7c4e8d75d..2042cffcd 100644 --- a/ja/docs/chapter_heap/top_k.md +++ b/ja/docs/chapter_heap/top_k.md @@ -463,7 +463,7 @@ comments: true ??? pythontutor "コードの可視化" - + 合計で $n$ 回のヒープ挿入と取り出しを行い、ヒープの最大長は $k$ であるため、時間計算量は $O(n \log k)$ です。この方法は非常に効率が高く、$k$ が小さいときは時間計算量が $O(n)$ に近づき、$k$ が大きいときでも $O(n \log n)$ を超えることはありません。 diff --git a/ja/docs/chapter_searching/binary_search.md b/ja/docs/chapter_searching/binary_search.md index 7cc2f7aa2..2bd41b5ce 100644 --- a/ja/docs/chapter_searching/binary_search.md +++ b/ja/docs/chapter_searching/binary_search.md @@ -364,7 +364,7 @@ comments: true ??? pythontutor "コードの可視化" - + **時間計算量は $O(\log n)$** :二分探索のループでは各ラウンドで区間が半分になるため、ループ回数は $\log_2 n$ です。 @@ -688,7 +688,7 @@ comments: true ??? pythontutor "コードの可視化" - + 次の図に示すように、2 種類の区間表現では、二分探索アルゴリズムの初期化、ループ条件、区間の縮小操作がそれぞれ異なります。 diff --git a/ja/docs/chapter_searching/binary_search_edge.md b/ja/docs/chapter_searching/binary_search_edge.md index 36daed647..81c8484b7 100644 --- a/ja/docs/chapter_searching/binary_search_edge.md +++ b/ja/docs/chapter_searching/binary_search_edge.md @@ -226,7 +226,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 10.3.2 右端境界を探す @@ -481,7 +481,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 2. 要素探索に変換する diff --git a/ja/docs/chapter_searching/binary_search_insertion.md b/ja/docs/chapter_searching/binary_search_insertion.md index 06ead69f2..197d2f7be 100644 --- a/ja/docs/chapter_searching/binary_search_insertion.md +++ b/ja/docs/chapter_searching/binary_search_insertion.md @@ -317,7 +317,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 10.2.2 重複要素がある場合 @@ -662,7 +662,7 @@ comments: true ??? pythontutor "コードの可視化" - + !!! tip diff --git a/ja/docs/chapter_searching/replace_linear_by_hashing.md b/ja/docs/chapter_searching/replace_linear_by_hashing.md index 19ef07bd6..fa3423ef4 100644 --- a/ja/docs/chapter_searching/replace_linear_by_hashing.md +++ b/ja/docs/chapter_searching/replace_linear_by_hashing.md @@ -243,7 +243,7 @@ comments: true ??? pythontutor "コードの可視化" - + この方法の時間計算量は $O(n^2)$ ,空間計算量は $O(1)$ であり,大規模データでは非常に時間がかかります。 @@ -538,7 +538,7 @@ comments: true ??? pythontutor "コードの可視化" - + この方法ではハッシュ探索によって時間計算量を $O(n^2)$ から $O(n)$ に下げ,実行効率を大幅に向上させます。 diff --git a/ja/docs/chapter_sorting/bubble_sort.md b/ja/docs/chapter_sorting/bubble_sort.md index b8ca4b6e1..4d612619c 100644 --- a/ja/docs/chapter_sorting/bubble_sort.md +++ b/ja/docs/chapter_sorting/bubble_sort.md @@ -292,7 +292,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.3.2 効率の最適化 @@ -612,7 +612,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.3.3 アルゴリズムの特徴 diff --git a/ja/docs/chapter_sorting/bucket_sort.md b/ja/docs/chapter_sorting/bucket_sort.md index bb874ba3a..279fa585c 100644 --- a/ja/docs/chapter_sorting/bucket_sort.md +++ b/ja/docs/chapter_sorting/bucket_sort.md @@ -439,7 +439,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.8.2 アルゴリズムの特性 diff --git a/ja/docs/chapter_sorting/counting_sort.md b/ja/docs/chapter_sorting/counting_sort.md index 365fb86d6..846c330f9 100644 --- a/ja/docs/chapter_sorting/counting_sort.md +++ b/ja/docs/chapter_sorting/counting_sort.md @@ -363,7 +363,7 @@ comments: true ??? pythontutor "コードの可視化" - + !!! note "計数ソートとバケットソートの関係" @@ -879,7 +879,7 @@ $$ ??? pythontutor "コードの可視化" - + ## 11.9.3 アルゴリズムの特性 diff --git a/ja/docs/chapter_sorting/heap_sort.md b/ja/docs/chapter_sorting/heap_sort.md index 8e14f7f9c..ceadc0e8b 100644 --- a/ja/docs/chapter_sorting/heap_sort.md +++ b/ja/docs/chapter_sorting/heap_sort.md @@ -614,7 +614,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.7.2 アルゴリズムの特性 diff --git a/ja/docs/chapter_sorting/insertion_sort.md b/ja/docs/chapter_sorting/insertion_sort.md index 95bcd59bf..d49cc0180 100644 --- a/ja/docs/chapter_sorting/insertion_sort.md +++ b/ja/docs/chapter_sorting/insertion_sort.md @@ -272,7 +272,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.4.2 アルゴリズムの特徴 diff --git a/ja/docs/chapter_sorting/merge_sort.md b/ja/docs/chapter_sorting/merge_sort.md index 804c45d96..a5d095153 100644 --- a/ja/docs/chapter_sorting/merge_sort.md +++ b/ja/docs/chapter_sorting/merge_sort.md @@ -681,7 +681,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.6.2 アルゴリズムの特性 diff --git a/ja/docs/chapter_sorting/quick_sort.md b/ja/docs/chapter_sorting/quick_sort.md index b0a9b2ffc..0ae1adffc 100644 --- a/ja/docs/chapter_sorting/quick_sort.md +++ b/ja/docs/chapter_sorting/quick_sort.md @@ -368,7 +368,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.5.1 アルゴリズムの流れ @@ -595,7 +595,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.5.2 アルゴリズムの特性 @@ -1095,7 +1095,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.5.5 再帰の深さの最適化 @@ -1404,5 +1404,5 @@ comments: true ??? pythontutor "コードの可視化" - + diff --git a/ja/docs/chapter_sorting/radix_sort.md b/ja/docs/chapter_sorting/radix_sort.md index dfe66b3d1..8825cc731 100644 --- a/ja/docs/chapter_sorting/radix_sort.md +++ b/ja/docs/chapter_sorting/radix_sort.md @@ -724,7 +724,7 @@ $$ ??? pythontutor "コードの可視化" - + !!! question "なぜ最下位桁からソートするのですか?" diff --git a/ja/docs/chapter_sorting/selection_sort.md b/ja/docs/chapter_sorting/selection_sort.md index 51e2070e4..eb2ece3b6 100644 --- a/ja/docs/chapter_sorting/selection_sort.md +++ b/ja/docs/chapter_sorting/selection_sort.md @@ -326,7 +326,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 11.2.1 アルゴリズムの特徴 diff --git a/ja/docs/chapter_stack_and_queue/deque.md b/ja/docs/chapter_stack_and_queue/deque.md index d0cf7fdc8..dd03e2f9e 100644 --- a/ja/docs/chapter_stack_and_queue/deque.md +++ b/ja/docs/chapter_stack_and_queue/deque.md @@ -395,7 +395,7 @@ comments: true ??? pythontutor "実行の可視化" - + ## 5.3.2 両端キューの実装 * diff --git a/ja/docs/chapter_stack_and_queue/queue.md b/ja/docs/chapter_stack_and_queue/queue.md index 20883f1a0..ddf11c135 100644 --- a/ja/docs/chapter_stack_and_queue/queue.md +++ b/ja/docs/chapter_stack_and_queue/queue.md @@ -368,7 +368,7 @@ comments: true ??? pythontutor "可視化実行" - + ## 5.2.2 キューの実装 @@ -1313,7 +1313,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 2. 配列に基づく実装 @@ -2288,7 +2288,7 @@ comments: true ??? pythontutor "コードの可視化" - + 上記の実装によるキューにも制約があり、長さを可変にできません。しかし、この問題の解決は難しくなく、配列を動的配列に置き換えれば容量拡張の仕組みを導入できます。興味があれば自分で実装してみてください。 diff --git a/ja/docs/chapter_stack_and_queue/stack.md b/ja/docs/chapter_stack_and_queue/stack.md index 06ff5bcb5..7f09a5073 100644 --- a/ja/docs/chapter_stack_and_queue/stack.md +++ b/ja/docs/chapter_stack_and_queue/stack.md @@ -361,7 +361,7 @@ comments: true ??? pythontutor "実行の可視化" - + ## 5.1.2 スタックの実装 @@ -1162,7 +1162,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 2. 配列による実装 @@ -1804,7 +1804,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 5.1.3 2つの実装の比較 diff --git a/ja/docs/chapter_tree/array_representation_of_tree.md b/ja/docs/chapter_tree/array_representation_of_tree.md index 0282a156c..48e618e90 100644 --- a/ja/docs/chapter_tree/array_representation_of_tree.md +++ b/ja/docs/chapter_tree/array_representation_of_tree.md @@ -1330,7 +1330,7 @@ comments: true ??? pythontutor "コードの可視化" - + ## 7.3.3 利点と制約 diff --git a/ja/docs/chapter_tree/binary_search_tree.md b/ja/docs/chapter_tree/binary_search_tree.md index 8a8a7542b..98cede59d 100644 --- a/ja/docs/chapter_tree/binary_search_tree.md +++ b/ja/docs/chapter_tree/binary_search_tree.md @@ -340,7 +340,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 2. ノードの挿入 @@ -804,7 +804,7 @@ comments: true ??? pythontutor "コードの可視化" - + ノード探索と同様に、ノード挿入には $O(\log n)$ 時間を要します。 @@ -1590,7 +1590,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 4. 中順走査は昇順 diff --git a/ja/docs/chapter_tree/binary_tree.md b/ja/docs/chapter_tree/binary_tree.md index 0dcf74953..04831a39e 100644 --- a/ja/docs/chapter_tree/binary_tree.md +++ b/ja/docs/chapter_tree/binary_tree.md @@ -459,7 +459,7 @@ comments: true ??? pythontutor "実行の可視化" - + ### 2. ノードの挿入と削除 @@ -628,7 +628,7 @@ comments: true ??? pythontutor "実行の可視化" - + !!! tip diff --git a/ja/docs/chapter_tree/binary_tree_traversal.md b/ja/docs/chapter_tree/binary_tree_traversal.md index 291c08af2..96bb24fb6 100644 --- a/ja/docs/chapter_tree/binary_tree_traversal.md +++ b/ja/docs/chapter_tree/binary_tree_traversal.md @@ -336,7 +336,7 @@ comments: true ??? pythontutor "コードの可視化" - + ### 2. 計算量 @@ -821,7 +821,7 @@ comments: true ??? pythontutor "コードの可視化" - + !!! tip diff --git a/ko/docs/chapter_appendix/contribution.md b/ko/docs/chapter_appendix/contribution.md new file mode 100644 index 000000000..d6e81af64 --- /dev/null +++ b/ko/docs/chapter_appendix/contribution.md @@ -0,0 +1,53 @@ +--- +comments: true +--- + +# 16.2 함께 만드는 책 + +저자의 역량이 부족하여 책에 누락이나 오류가 있을 수 있다는 점을 양해 부탁드린다. 오탈자, 끊어진 링크, 빠진 내용, 모호한 표현, 불분명한 설명, 부자연스러운 글의 구성 등을 발견했다면 수정에 힘을 보태 주기 바란다. 독자에게 더 좋은 학습 자료를 제공하는 데 큰 도움이 된다. + +모든 [집필 참여자](https://github.com/krahets/hello-algo/graphs/contributors)의 GitHub ID는 저장소와 웹 버전의 첫 페이지에 표시된다. 이는 오픈 소스 커뮤니티에 아낌없이 기여한 분들에게 감사를 전하기 위한 것이다. + +!!! success "오픈 소스의 매력" + + 종이책은 인쇄와 다음 인쇄 사이의 간격이 길어 내용을 갱신하기가 매우 불편하다. + + 반면 이 오픈 소스 도서에서는 내용을 갱신하는 데 걸리는 시간이 며칠, 때로는 몇 시간으로 줄어든다. + +### 1. 간단한 내용 수정 + +아래 그림(16-3)과 같이 각 페이지의 오른쪽 위에는 ‘편집 아이콘’이 있다. 다음 순서로 글이나 코드를 수정할 수 있다. + +1. ‘편집 아이콘’을 누른다. 저장소를 포크해야 한다는 안내가 나오면 동의한다. +2. Markdown 소스 파일을 수정하고 내용이 올바른지 확인한다. 서식은 가능한 한 일관되게 유지한다. +3. 페이지 아래쪽에 수정 내용을 설명하고 ‘Propose file change’ 버튼을 누른다. 이동한 페이지에서 ‘Create pull request’ 버튼을 누르면 풀 리퀘스트를 만들 수 있다. + +{ class="animation-figure" } + +그림 16-3 페이지 편집 버튼
+ +그림은 직접 수정할 수 없으므로 [Issue](https://github.com/krahets/hello-algo/issues)를 만들거나 댓글로 문제를 설명해 주기 바란다. 확인 후 최대한 빨리 그림을 다시 제작하여 교체하겠다. + +### 2. 내용 작성 + +코드를 다른 프로그래밍 언어로 옮기거나 글의 내용을 확장하는 등 이 오픈 소스 프로젝트에 참여하고 싶다면 다음 풀 리퀘스트 절차를 따르면 된다. + +1. GitHub에 로그인한 뒤 이 책의 [코드 저장소](https://github.com/krahets/hello-algo)를 개인 계정으로 포크한다. +2. 포크한 저장소의 웹페이지로 이동하여 `git clone` 명령으로 저장소를 로컬에 복제한다. +3. 로컬에서 내용을 작성하고 충분히 테스트하여 코드가 올바른지 검증한다. +4. 로컬 변경 사항을 커밋한 뒤 원격 저장소로 푸시한다. +5. 저장소 웹페이지를 새로 고침하고 ‘Create pull request’ 버튼을 눌러 풀 리퀘스트를 만든다. + +### 3. Docker 배포 + +`hello-algo` 루트 디렉터리에서 다음 Docker 명령을 실행하면 `http://localhost:8000/ko/`에서 한국어판을 열 수 있다. + +```shell +docker-compose up -d +``` + +다음 명령으로 배포를 제거할 수 있다. + +```shell +docker-compose down +``` diff --git a/ko/docs/chapter_appendix/index.md b/ko/docs/chapter_appendix/index.md new file mode 100644 index 000000000..a47b4d49c --- /dev/null +++ b/ko/docs/chapter_appendix/index.md @@ -0,0 +1,14 @@ +--- +comments: true +icon: material/help-circle-outline +--- + +# 제 16 장 부록 + +{ class="cover-image" } + +## 이 장의 내용 + +- [16.1 프로그래밍 환경 설치](installation.md) +- [16.2 함께 만드는 책](contribution.md) +- [16.3 용어집](terminology.md) diff --git a/ko/docs/chapter_appendix/installation.md b/ko/docs/chapter_appendix/installation.md new file mode 100644 index 000000000..fb3d0b849 --- /dev/null +++ b/ko/docs/chapter_appendix/installation.md @@ -0,0 +1,76 @@ +--- +comments: true +--- + +# 16.1 프로그래밍 환경 설치 + +## 16.1.1 IDE 설치 + +로컬 통합 개발 환경(IDE)으로는 가벼운 오픈 소스 도구인 VS Code를 권장한다. [VS Code 공식 웹사이트](https://code.visualstudio.com/)에서 운영체제에 맞는 버전을 내려받아 설치한다. + +{ class="animation-figure" } + +그림 16-1 공식 웹사이트에서 VS Code 내려받기
+ +VS Code는 풍부한 확장 생태계를 갖추고 있어 대부분의 프로그래밍 언어를 실행하고 디버깅할 수 있다. Python을 예로 들면, ‘Python Extension Pack’을 설치한 뒤 Python 코드를 디버깅할 수 있다. 설치 과정은 아래 그림(16-2)과 같다. + +{ class="animation-figure" } + +그림 16-2 VS Code 확장 설치
+ +## 16.1.2 언어 실행 환경 설치 + +### 1. Python 환경 + +1. [Miniconda3](https://docs.conda.io/en/latest/miniconda.html)를 내려받아 설치한다. Python 3.10 이상이 필요하다. +2. VS Code 확장 마켓플레이스에서 `python`을 검색하여 Python Extension Pack을 설치한다. +3. (선택 사항) 명령줄에 `pip install black`을 입력하여 코드 포매터를 설치한다. + +### 2. C/C++ 환경 + +1. Windows에서는 [MinGW](https://sourceforge.net/projects/mingw-w64/files/)를 설치해야 한다([설정 안내](https://blog.csdn.net/qq_33698226/article/details/129031241)). macOS에는 Clang이 기본으로 포함되어 있어 별도로 설치할 필요가 없다. +2. VS Code 확장 마켓플레이스에서 `c++`를 검색하여 C/C++ Extension Pack을 설치한다. +3. (선택 사항) Settings 페이지에서 코드 서식 옵션인 `Clang_format_fallback Style`을 검색하고 `{ BasedOnStyle: Microsoft, BreakBeforeBraces: Attach }`로 설정한다. + +### 3. Java 환경 + +1. [OpenJDK](https://jdk.java.net/18/)를 내려받아 설치한다(JDK 9보다 높은 버전이어야 한다). +2. VS Code 확장 마켓플레이스에서 `java`를 검색하여 Extension Pack for Java를 설치한다. + +### 4. C# 환경 + +1. [.NET 8.0](https://dotnet.microsoft.com/ko-kr/download)을 내려받아 설치한다. +2. VS Code 확장 마켓플레이스에서 `C# Dev Kit`을 검색하여 C# Dev Kit을 설치한다([설정 안내](https://code.visualstudio.com/docs/csharp/get-started)). +3. Visual Studio를 사용해도 된다([설치 안내](https://learn.microsoft.com/ko-kr/visualstudio/install/install-visual-studio?view=vs-2022)). + +### 5. Go 환경 + +1. [go](https://go.dev/dl/)를 내려받아 설치한다. +2. VS Code 확장 마켓플레이스에서 `go`를 검색하여 Go를 설치한다. +3. `Ctrl + Shift + P`를 눌러 명령 팔레트를 열고 go를 입력한다. `Go: Install/Update Tools`를 선택한 뒤 모든 항목을 체크하여 설치한다. + +### 6. Swift 환경 + +1. [Swift](https://www.swift.org/download/)를 내려받아 설치한다. +2. VS Code 확장 마켓플레이스에서 `swift`를 검색하여 [Swift for Visual Studio Code](https://marketplace.visualstudio.com/items?itemName=sswg.swift-lang)를 설치한다. + +### 7. JavaScript 환경 + +1. [Node.js](https://nodejs.org/ko/)를 내려받아 설치한다. +2. (선택 사항) VS Code 확장 마켓플레이스에서 `Prettier`를 검색하여 코드 포매터를 설치한다. + +### 8. TypeScript 환경 + +1. JavaScript 환경과 같은 순서로 설치한다. +2. [TypeScript Execute (tsx)](https://github.com/privatenumber/tsx?tab=readme-ov-file#global-installation)를 설치한다. +3. VS Code 확장 마켓플레이스에서 `typescript`를 검색하여 [Pretty TypeScript Errors](https://marketplace.visualstudio.com/items?itemName=yoavbls.pretty-ts-errors)를 설치한다. + +### 9. Dart 환경 + +1. [Dart](https://dart.dev/get-dart)를 내려받아 설치한다. +2. VS Code 확장 마켓플레이스에서 `dart`를 검색하여 [Dart](https://marketplace.visualstudio.com/items?itemName=Dart-Code.dart-code)를 설치한다. + +### 10. Rust 환경 + +1. [Rust](https://www.rust-lang.org/tools/install)를 내려받아 설치한다. +2. VS Code 확장 마켓플레이스에서 `rust`를 검색하여 [rust-analyzer](https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer)를 설치한다. diff --git a/ko/docs/chapter_appendix/terminology.md b/ko/docs/chapter_appendix/terminology.md new file mode 100644 index 000000000..04c1ddc6f --- /dev/null +++ b/ko/docs/chapter_appendix/terminology.md @@ -0,0 +1,144 @@ +--- +comments: true +--- + +# 16.3 용어집 + +아래 표(16-1)에는 이 책에 나오는 주요 용어를 정리했다. 영어 문헌을 읽을 때 도움이 되므로 각 용어의 영어 명칭을 기억해 두기를 권장한다. + +표 16-1 자료 구조와 알고리즘의 주요 용어
+ +그림 4-1 배열의 정의와 저장 방식
+ +## 4.1.1 배열의 기본 연산 + +### 1. 배열 초기화 + +필요에 따라 초깃값을 지정하지 않거나 지정하는 두 가지 초기화 방식을 사용할 수 있다. 초깃값을 지정하지 않으면 대부분의 프로그래밍 언어는 배열 원소를 $0$으로 초기화한다. + +=== "Python" + + ```python title="array.py" + # 배열 초기화 + arr: list[int] = [0] * 5 # [ 0, 0, 0, 0, 0 ] + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* 배열 초기화 */ + // 스택에 저장 + int arr[5] = {}; + int nums[5] = { 1, 3, 2, 5, 4 }; + // 힙에 저장(공간을 직접 해제해야 함) + int* arr1 = new int[5] {}; + int* nums1 = new int[5] { 1, 3, 2, 5, 4 }; + ``` + +=== "Java" + + ```java title="array.java" + /* 배열 초기화 */ + int[] arr = new int[5]; // { 0, 0, 0, 0, 0 } + int[] nums = { 1, 3, 2, 5, 4 }; + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 배열 초기화 */ + int[] arr = new int[5]; // [ 0, 0, 0, 0, 0 ] + int[] nums = [1, 3, 2, 5, 4]; + ``` + +=== "Go" + + ```go title="array.go" + /* 배열 초기화 */ + var arr [5]int + // Go에서 길이를 지정하면([5]int) 배열이고, 지정하지 않으면([]int) 슬라이스다 + // Go 배열의 길이는 컴파일 시 결정되도록 설계되었으므로 상수로만 길이를 지정할 수 있다 + // 용량 확장 메서드 extend()를 쉽게 구현하기 위해 아래에서는 슬라이스(Slice)를 배열(Array)로 간주한다 + nums := []int{1, 3, 2, 5, 4} + ``` + +=== "Swift" + + ```swift title="array.swift" + /* 배열 초기화 */ + let arr = Array(repeating: 0, count: 5) // [0, 0, 0, 0, 0] + let nums = [1, 3, 2, 5, 4] + ``` + +=== "JS" + + ```javascript title="array.js" + /* 배열 초기화 */ + var arr = new Array(5).fill(0); + var nums = [1, 3, 2, 5, 4]; + ``` + +=== "TS" + + ```typescript title="array.ts" + /* 배열 초기화 */ + let arr: number[] = new Array(5).fill(0); + let nums: number[] = [1, 3, 2, 5, 4]; + ``` + +=== "Dart" + + ```dart title="array.dart" + /* 배열 초기화 */ + List그림 4-2 배열 원소의 메모리 주소 계산
+ +위 그림(4-2)에서 배열의 첫 원소 인덱스는 $0$이다. $1$부터 세는 편이 더 자연스러워 보이므로 직관에 어긋날 수 있다. 그러나 주소 계산식의 관점에서 **인덱스는 본질적으로 메모리 주소의 오프셋이다**. 첫 원소의 주소 오프셋은 $0$이므로 인덱스가 $0$인 것은 합리적이다. + +배열의 원소 접근은 매우 효율적이며, 어떤 원소든 $O(1)$ 시간에 임의 접근할 수 있다. + +=== "Python" + + ```python title="array.py" + def random_access(nums: list[int]) -> int: + """원소에 임의 접근""" + # 구간 [0, len(nums)-1]에서 숫자 하나를 무작위로 뽑는다 + random_index = random.randint(0, len(nums) - 1) + # 무작위 원소를 가져와 반환한다 + random_num = nums[random_index] + return random_num + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* 원소에 임의 접근 */ + int randomAccess(int *nums, int size) { + // 구간 [0, size)에서 무작위로 수 하나 선택 + int randomIndex = rand() % size; + // 무작위 원소를 가져와 반환한다 + int randomNum = nums[randomIndex]; + return randomNum; + } + ``` + +=== "Java" + + ```java title="array.java" + /* 원소에 임의 접근 */ + int randomAccess(int[] nums) { + // 구간 [0, nums.length)에서 숫자 하나를 무작위로 뽑는다 + int randomIndex = ThreadLocalRandom.current().nextInt(0, nums.length); + // 무작위 원소를 가져와 반환한다 + int randomNum = nums[randomIndex]; + return randomNum; + } + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 원소에 임의 접근 */ + int RandomAccess(int[] nums) { + Random random = new(); + // 구간 [0, nums.Length)에서 무작위로 수 하나 선택 + int randomIndex = random.Next(nums.Length); + // 무작위 원소를 가져와 반환한다 + int randomNum = nums[randomIndex]; + return randomNum; + } + ``` + +=== "Go" + + ```go title="array.go" + /* 원소에 임의 접근 */ + func randomAccess(nums []int) (randomNum int) { + // 구간 [0, nums.length)에서 숫자 하나를 무작위로 뽑는다 + randomIndex := rand.Intn(len(nums)) + // 무작위 원소를 가져와 반환한다 + randomNum = nums[randomIndex] + return + } + ``` + +=== "Swift" + + ```swift title="array.swift" + /* 원소에 임의 접근 */ + func randomAccess(nums: [Int]) -> Int { + // 구간 [0, nums.count)에서 숫자 하나를 무작위로 뽑는다 + let randomIndex = nums.indices.randomElement()! + // 무작위 원소를 가져와 반환한다 + let randomNum = nums[randomIndex] + return randomNum + } + ``` + +=== "JS" + + ```javascript title="array.js" + /* 원소에 임의 접근 */ + function randomAccess(nums) { + // 구간 [0, nums.length)에서 숫자 하나를 무작위로 뽑는다 + const random_index = Math.floor(Math.random() * nums.length); + // 무작위 원소를 가져와 반환한다 + const random_num = nums[random_index]; + return random_num; + } + ``` + +=== "TS" + + ```typescript title="array.ts" + /* 원소에 임의 접근 */ + function randomAccess(nums: number[]): number { + // 구간 [0, nums.length)에서 숫자 하나를 무작위로 뽑는다 + const random_index = Math.floor(Math.random() * nums.length); + // 무작위 원소를 가져와 반환한다 + const random_num = nums[random_index]; + return random_num; + } + ``` + +=== "Dart" + + ```dart title="array.dart" + /* 원소에 임의 접근 */ + int randomAccess(List그림 4-3 배열에 원소를 삽입하는 예
+ +배열의 길이는 고정되어 있으므로 원소를 하나 삽입하면 반드시 마지막 원소가 ‘사라진다’는 점에 주의하자. 이 문제의 해결책은 ‘리스트’ 절에서 다룬다. + +=== "Python" + + ```python title="array.py" + def insert(nums: list[int], num: int, index: int): + """배열의 인덱스 index에 원소 num 삽입""" + # 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for i in range(len(nums) - 1, index, -1): + nums[i] = nums[i - 1] + # index 위치의 원소에 num을 대입한다 + nums[index] = num + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* 배열의 인덱스 index에 원소 num 삽입 */ + void insert(int *nums, int size, int num, int index) { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for (int i = size - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num; + } + ``` + +=== "Java" + + ```java title="array.java" + /* 배열의 인덱스 index에 원소 num 삽입 */ + void insert(int[] nums, int num, int index) { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for (int i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num; + } + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 배열의 인덱스 index에 원소 num 삽입 */ + void Insert(int[] nums, int num, int index) { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for (int i = nums.Length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num; + } + ``` + +=== "Go" + + ```go title="array.go" + /* 배열의 인덱스 index에 원소 num 삽입 */ + func insert(nums []int, num int, index int) { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for i := len(nums) - 1; i > index; i-- { + nums[i] = nums[i-1] + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num + } + ``` + +=== "Swift" + + ```swift title="array.swift" + /* 배열의 인덱스 index에 원소 num 삽입 */ + func insert(nums: inout [Int], num: Int, index: Int) { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for i in stride(from: nums.count - 1, to: index, by: -1) { + nums[i] = nums[i - 1] + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num + } + ``` + +=== "JS" + + ```javascript title="array.js" + /* 배열의 인덱스 index에 원소 num 삽입 */ + function insert(nums, num, index) { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num; + } + ``` + +=== "TS" + + ```typescript title="array.ts" + /* 배열의 인덱스 index에 원소 num 삽입 */ + function insert(nums: number[], num: number, index: number): void { + // 인덱스 index와 그 뒤에 있는 모든 원소를 한 칸씩 뒤로 이동한다 + for (let i = nums.length - 1; i > index; i--) { + nums[i] = nums[i - 1]; + } + // index 위치의 원소에 num을 대입한다 + nums[index] = num; + } + ``` + +=== "Dart" + + ```dart title="array.dart" + /* 배열의 인덱스 index에 원소 _num 삽입 */ + void insert(List그림 4-4 배열에서 원소를 삭제하는 예
+ +삭제가 끝난 뒤 원래의 마지막 원소는 ‘의미 없는’ 값이 되므로 따로 수정할 필요가 없다. + +=== "Python" + + ```python title="array.py" + def remove(nums: list[int], index: int): + """인덱스 index에 있는 원소 삭제""" + # 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for i in range(index, len(nums) - 1): + nums[i] = nums[i + 1] + ``` + +=== "C++" + + ```cpp title="array.cpp" + /* 인덱스 index에 있는 원소 삭제 */ + void remove(int *nums, int size, int index) { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for (int i = index; i < size - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "Java" + + ```java title="array.java" + /* 인덱스 index에 있는 원소 삭제 */ + void remove(int[] nums, int index) { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for (int i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "C#" + + ```csharp title="array.cs" + /* 인덱스 index에 있는 원소 삭제 */ + void Remove(int[] nums, int index) { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for (int i = index; i < nums.Length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "Go" + + ```go title="array.go" + /* 인덱스 index에 있는 원소 삭제 */ + func remove(nums []int, index int) { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for i := index; i < len(nums)-1; i++ { + nums[i] = nums[i+1] + } + } + ``` + +=== "Swift" + + ```swift title="array.swift" + /* 인덱스 index에 있는 원소 삭제 */ + func remove(nums: inout [Int], index: Int) { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for i in nums.indices.dropFirst(index).dropLast() { + nums[i] = nums[i + 1] + } + } + ``` + +=== "JS" + + ```javascript title="array.js" + /* 인덱스 index에 있는 원소 삭제 */ + function remove(nums, index) { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "TS" + + ```typescript title="array.ts" + /* 인덱스 index에 있는 원소 삭제 */ + function remove(nums: number[], index: number): void { + // 인덱스 index 뒤에 있는 모든 원소를 한 칸씩 앞으로 이동한다 + for (let i = index; i < nums.length - 1; i++) { + nums[i] = nums[i + 1]; + } + } + ``` + +=== "Dart" + + ```dart title="array.dart" + /* 인덱스 index에 있는 원소 삭제 */ + void remove(List그림 4-5 연결 리스트의 정의와 저장 방식
+ +위 그림(4-5)에서 연결 리스트를 구성하는 단위는 노드(node) 객체다. 각 노드는 노드의 ‘값’과 다음 노드를 가리키는 ‘참조’라는 두 데이터를 포함한다. + +- 연결 리스트의 첫 노드를 ‘머리 노드’, 마지막 노드를 ‘꼬리 노드’라고 한다. +- 꼬리 노드는 ‘없음’을 가리키며, Java, C++, Python에서는 이를 각각 `null`, `nullptr`, `None`으로 표기한다. +- C, C++, Go, Rust처럼 포인터를 지원하는 언어에서는 위의 ‘참조’를 ‘포인터’로 바꾸어 이해하면 된다. + +다음 코드와 같이 연결 리스트 노드 `ListNode`는 값 외에도 참조(포인터)를 하나 더 저장해야 한다. 따라서 같은 양의 데이터를 저장할 때 **연결 리스트는 배열보다 메모리 공간을 더 많이 차지한다**. + +=== "Python" + + ```python title="" + class ListNode: + """연결 리스트 노드 클래스""" + def __init__(self, val: int): + self.val: int = val # 노드 값 + self.next: ListNode | None = None # 다음 노드를 가리키는 참조 + ``` + +=== "C++" + + ```cpp title="" + /* 연결 리스트 노드 구조체 */ + struct ListNode { + int val; // 노드 값 + ListNode *next; // 다음 노드를 가리키는 포인터 + ListNode(int x) : val(x), next(nullptr) {} // 생성자 + }; + ``` + +=== "Java" + + ```java title="" + /* 연결 리스트 노드 클래스 */ + class ListNode { + int val; // 노드 값 + ListNode next; // 다음 노드를 가리키는 참조 + ListNode(int x) { val = x; } // 생성자 + } + ``` + +=== "C#" + + ```csharp title="" + /* 연결 리스트 노드 클래스 */ + class ListNode(int x) { //생성자 + int val = x; // 노드 값 + ListNode? next; // 다음 노드를 가리키는 참조 + } + ``` + +=== "Go" + + ```go title="" + /* 연결 리스트 노드 구조체 */ + type ListNode struct { + Val int // 노드 값 + Next *ListNode // 다음 노드를 가리키는 포인터 + } + + // NewListNode 생성자: 새 연결 리스트 생성 + func NewListNode(val int) *ListNode { + return &ListNode{ + Val: val, + Next: nil, + } + } + ``` + +=== "Swift" + + ```swift title="" + /* 연결 리스트 노드 클래스 */ + class ListNode { + var val: Int // 노드 값 + var next: ListNode? // 다음 노드를 가리키는 참조 + + init(x: Int) { // 생성자 + val = x + } + } + ``` + +=== "JS" + + ```javascript title="" + /* 연결 리스트 노드 클래스 */ + class ListNode { + constructor(val, next) { + this.val = (val === undefined ? 0 : val); // 노드 값 + this.next = (next === undefined ? null : next); // 다음 노드를 가리키는 참조 + } + } + ``` + +=== "TS" + + ```typescript title="" + /* 연결 리스트 노드 클래스 */ + class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; // 노드 값 + this.next = next === undefined ? null : next; // 다음 노드를 가리키는 참조 + } + } + ``` + +=== "Dart" + + ```dart title="" + /* 연결 리스트 노드 클래스 */ + class ListNode { + int val; // 노드 값 + ListNode? next; // 다음 노드를 가리키는 참조 + ListNode(this.val, [this.next]); // 생성자 + } + ``` + +=== "Rust" + + ```rust title="" + use std::rc::Rc; + use std::cell::RefCell; + /* 연결 리스트 노드 클래스 */ + #[derive(Debug)] + struct ListNode { + val: i32, // 노드 값 + next: Option그림 4-6 연결 리스트에 노드를 삽입하는 예
+ +=== "Python" + + ```python title="linked_list.py" + def insert(n0: ListNode, P: ListNode): + """연결 리스트의 노드 n0 뒤에 노드 P 삽입""" + n1 = n0.next + P.next = n1 + n0.next = P + ``` + +=== "C++" + + ```cpp title="linked_list.cpp" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + void insert(ListNode *n0, ListNode *P) { + ListNode *n1 = n0->next; + P->next = n1; + n0->next = P; + } + ``` + +=== "Java" + + ```java title="linked_list.java" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + void insert(ListNode n0, ListNode P) { + ListNode n1 = n0.next; + P.next = n1; + n0.next = P; + } + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + void Insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } + ``` + +=== "Go" + + ```go title="linked_list.go" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + func insertNode(n0 *ListNode, P *ListNode) { + n1 := n0.Next + P.Next = n1 + n0.Next = P + } + ``` + +=== "Swift" + + ```swift title="linked_list.swift" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + func insert(n0: ListNode, P: ListNode) { + let n1 = n0.next + P.next = n1 + n0.next = P + } + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + function insert(n0, P) { + const n1 = n0.next; + P.next = n1; + n0.next = P; + } + ``` + +=== "TS" + + ```typescript title="linked_list.ts" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + function insert(n0: ListNode, P: ListNode): void { + const n1 = n0.next; + P.next = n1; + n0.next = P; + } + ``` + +=== "Dart" + + ```dart title="linked_list.dart" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + void insert(ListNode n0, ListNode P) { + ListNode? n1 = n0.next; + P.next = n1; + n0.next = P; + } + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* 연결 리스트의 노드 n0 뒤에 노드 P 삽입 */ + #[allow(non_snake_case)] + pub fn insert그림 4-7 연결 리스트의 노드 삭제
+ +=== "Python" + + ```python title="linked_list.py" + def remove(n0: ListNode): + """연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제""" + if not n0.next: + return + # n0 -> P -> n1 + P = n0.next + n1 = P.next + n0.next = n1 + ``` + +=== "C++" + + ```cpp title="linked_list.cpp" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + void remove(ListNode *n0) { + if (n0->next == nullptr) + return; + // n0 -> P -> n1 + ListNode *P = n0->next; + ListNode *n1 = P->next; + n0->next = n1; + // 메모리 해제 + delete P; + } + ``` + +=== "Java" + + ```java title="linked_list.java" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + void remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode n1 = P.next; + n0.next = n1; + } + ``` + +=== "C#" + + ```csharp title="linked_list.cs" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + void Remove(ListNode n0) { + if (n0.next == null) + return; + // n0 -> P -> n1 + ListNode P = n0.next; + ListNode? n1 = P.next; + n0.next = n1; + } + ``` + +=== "Go" + + ```go title="linked_list.go" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + func removeItem(n0 *ListNode) { + if n0.Next == nil { + return + } + // n0 -> P -> n1 + P := n0.Next + n1 := P.Next + n0.Next = n1 + } + ``` + +=== "Swift" + + ```swift title="linked_list.swift" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + func remove(n0: ListNode) { + if n0.next == nil { + return + } + // n0 -> P -> n1 + let P = n0.next + let n1 = P?.next + n0.next = n1 + } + ``` + +=== "JS" + + ```javascript title="linked_list.js" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + function remove(n0) { + if (!n0.next) return; + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; + } + ``` + +=== "TS" + + ```typescript title="linked_list.ts" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + function remove(n0: ListNode): void { + if (!n0.next) { + return; + } + // n0 -> P -> n1 + const P = n0.next; + const n1 = P.next; + n0.next = n1; + } + ``` + +=== "Dart" + + ```dart title="linked_list.dart" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + void remove(ListNode n0) { + if (n0.next == null) return; + // n0 -> P -> n1 + ListNode P = n0.next!; + ListNode? n1 = P.next; + n0.next = n1; + } + ``` + +=== "Rust" + + ```rust title="linked_list.rs" + /* 연결 리스트의 노드 n0 바로 뒤에 있는 노드 삭제 */ + #[allow(non_snake_case)] + pub fn remove표 4-1 배열과 연결 리스트의 효율 비교
+ +그림 4-8 대표적인 연결 리스트 종류
+ +## 4.2.4 연결 리스트의 대표적인 활용 + +단일 연결 리스트는 보통 스택, 큐, 해시 테이블, 그래프 등의 자료 구조를 구현하는 데 사용된다. + +- **스택과 큐**: 삽입과 삭제를 모두 연결 리스트의 같은 끝에서 수행하면 후입선출 특성이 나타나 스택에 해당한다. 한쪽 끝에서 삽입하고 반대쪽 끝에서 삭제하면 선입선출 특성이 나타나 큐에 해당한다. +- **해시 테이블**: 체이닝은 해시 충돌을 해결하는 대표적인 방법 중 하나로, 충돌하는 모든 원소를 하나의 연결 리스트에 넣는다. +- **그래프**: 인접 리스트는 그래프를 표현하는 흔한 방법이다. 각 정점에 연결 리스트 하나를 연결하고, 그 리스트의 각 원소로 해당 정점과 연결된 다른 정점을 나타낸다. + +이중 연결 리스트는 이전 원소와 다음 원소를 빠르게 찾아야 하는 상황에 자주 사용된다. + +- **고급 자료 구조**: 레드-블랙 트리나 B 트리에서는 노드의 부모 노드에 접근해야 한다. 이중 연결 리스트처럼 노드에 부모를 가리키는 참조를 저장하여 구현할 수 있다. +- **브라우저 방문 기록**: 사용자가 웹 브라우저의 앞으로 또는 뒤로 버튼을 누르면 이전이나 다음에 방문했던 페이지를 알아야 한다. 이중 연결 리스트를 사용하면 이러한 연산이 간단해진다. +- **LRU 알고리즘**: 캐시 교체 알고리즘인 LRU에서는 가장 오랫동안 사용하지 않은 데이터를 빠르게 찾고 노드도 빠르게 추가하고 삭제해야 한다. 이때 이중 연결 리스트가 매우 적합하다. + +원형 연결 리스트는 운영체제의 자원 스케줄링처럼 주기적인 연산이 필요한 상황에 자주 사용된다. + +- **라운드 로빈 스케줄링 알고리즘**: 운영체제의 대표적인 CPU 스케줄링 알고리즘으로, 여러 프로세스를 순환한다. 각 프로세스에 타임 슬라이스를 할당하고, 시간이 다 되면 CPU가 다음 프로세스로 전환한다. 이러한 순환은 원형 연결 리스트로 구현할 수 있다. +- **데이터 버퍼**: 일부 데이터 버퍼도 원형 연결 리스트로 구현할 수 있다. 예를 들어 오디오나 동영상 플레이어에서 데이터 스트림을 여러 버퍼 블록으로 나누어 원형 연결 리스트에 넣으면 끊김 없이 재생할 수 있다. diff --git a/ko/docs/chapter_array_and_linkedlist/list.md b/ko/docs/chapter_array_and_linkedlist/list.md new file mode 100644 index 000000000..aaf8589cd --- /dev/null +++ b/ko/docs/chapter_array_and_linkedlist/list.md @@ -0,0 +1,2301 @@ +--- +comments: true +--- + +# 4.3 리스트 + +리스트(list)는 순서가 있는 원소의 모음을 나타내는 추상적인 자료 구조 개념이다. 원소 접근, 수정, 추가, 삭제, 순회 등의 연산을 지원하며, 사용자가 용량 제한을 신경 쓰지 않아도 된다. 리스트는 연결 리스트나 배열로 구현할 수 있다. + +- 연결 리스트는 원소의 삽입, 삭제, 탐색, 갱신을 지원하고 유연하게 용량을 늘릴 수 있으므로 자연스럽게 리스트로 볼 수 있다. +- 배열도 원소의 삽입, 삭제, 탐색, 갱신을 지원하지만 길이를 바꿀 수 없으므로 길이가 제한된 리스트로 볼 수 있다. + +배열로 리스트를 구현하면 **길이를 바꿀 수 없다는 특성 때문에 실용성이 낮아진다**. 보통 저장할 데이터 양을 미리 알 수 없어 적절한 길이를 정하기 어렵기 때문이다. 길이가 너무 짧으면 필요를 충족하지 못하고, 너무 길면 메모리 공간을 낭비한다. + +이 문제는 동적 배열(dynamic array)로 리스트를 구현하여 해결할 수 있다. 동적 배열은 배열의 장점을 이어받으면서도 프로그램 실행 중에 용량을 동적으로 늘릴 수 있다. + +실제로 **많은 프로그래밍 언어의 표준 라이브러리가 제공하는 리스트는 동적 배열로 구현된다**. Python의 `list`, Java의 `ArrayList`, C++의 `vector`, C#의 `List` 등이 그 예다. 이후 설명에서는 ‘리스트’와 ‘동적 배열’을 같은 개념으로 다룬다. + +## 4.3.1 리스트의 기본 연산 + +### 1. 리스트 초기화 + +보통 ‘초깃값 없음’과 ‘초깃값 지정’의 두 가지 초기화 방식을 사용한다. + +=== "Python" + + ```python title="list.py" + # 리스트 초기화 + # 초깃값 없음 + nums1: list[int] = [] + # 초깃값 지정 + nums: list[int] = [1, 3, 2, 5, 4] + ``` + +=== "C++" + + ```cpp title="list.cpp" + /* 리스트 초기화 */ + // C++의 vector가 이 글에서 설명하는 nums에 해당한다 + // 초깃값 없음 + vector표 4-2 컴퓨터의 저장 장치
+ +그림 4-9 컴퓨터 저장 시스템
+ +!!! tip "참고" + + 컴퓨터의 저장 계층 구조는 속도, 용량, 비용 사이의 정교한 균형을 보여 준다. 이러한 절충은 모든 산업 분야에 널리 존재하며, 서로 다른 장점과 제약 사이에서 최적의 균형을 찾아야 한다. + +전체적으로 **하드 디스크는 많은 데이터를 장기간 저장하고, 메모리는 실행 중인 프로그램이 처리하는 데이터를 임시 저장하며, 캐시는 자주 접근하는 데이터와 명령어를 저장하여** 실행 효율을 높인다. 세 장치는 서로 협력하여 컴퓨터 시스템이 효율적으로 작동하게 한다. + +아래 그림(4-10)과 같이 프로그램이 실행되면 하드 디스크의 데이터를 메모리로 읽어 와 CPU가 계산에 사용한다. 캐시는 CPU의 일부로 볼 수 있으며, **메모리에서 데이터를 지능적으로 불러와** CPU에 빠른 데이터 읽기를 제공한다. 이를 통해 실행 효율을 크게 높이고 더 느린 메모리에 대한 의존을 줄인다. + +{ class="animation-figure" } + +그림 4-10 하드 디스크, 메모리, 캐시 사이의 데이터 흐름
+ +## 4.4.2 자료 구조의 메모리 효율 + +메모리 공간 활용 측면에서 배열과 연결 리스트에는 각각 장점과 한계가 있다. + +한편으로 **메모리는 한정되어 있으며 같은 메모리 영역을 여러 프로그램이 공유할 수 없으므로**, 자료 구조가 공간을 최대한 효율적으로 사용하기를 바란다. 배열 원소는 빈틈없이 붙어 있어 연결 리스트 노드 사이의 참조(포인터)를 저장할 추가 공간이 필요 없으므로 공간 효율이 더 높다. 하지만 배열은 충분한 연속 메모리를 한꺼번에 할당해야 하므로 공간 낭비가 발생할 수 있으며, 용량 확장에도 시간과 공간이 추가로 든다. 반면 연결 리스트는 ‘노드’ 단위로 메모리를 동적으로 할당하고 회수하므로 더 유연하다. + +다른 한편으로 프로그램이 실행되면서 **메모리 할당과 해제가 반복되면 빈 메모리의 단편화가 점점 심해져** 메모리 활용 효율이 낮아진다. 배열은 연속 저장 방식을 사용하므로 상대적으로 단편화를 덜 일으킨다. 반면 연결 리스트의 원소는 분산되어 있으므로 삽입과 삭제가 잦으면 단편화가 더 쉽게 발생한다. + +## 4.4.3 자료 구조의 캐시 효율 + +캐시는 메모리보다 용량은 훨씬 작지만 속도는 훨씬 빨라 프로그램 실행 속도에 매우 중요한 역할을 한다. 용량이 제한되어 있어 자주 사용하는 데이터 일부만 저장할 수 있으므로, CPU가 접근하려는 데이터가 캐시에 없으면 캐시 미스(cache miss)가 발생한다. 이때 CPU는 더 느린 메모리에서 필요한 데이터를 불러와야 한다. + +따라서 **‘캐시 미스’가 적을수록 CPU의 데이터 읽기 및 쓰기 효율이 높아지고**, 프로그램 성능도 좋아진다. CPU가 캐시에서 데이터를 성공적으로 가져오는 비율을 캐시 적중률(cache hit rate)이라고 하며, 캐시 효율을 측정하는 지표로 자주 사용한다. + +캐시는 효율을 최대한 높이기 위해 다음과 같은 데이터 로딩 방식을 사용한다. + +- **캐시 라인**: 캐시는 데이터를 한 바이트씩 저장하고 불러오는 대신 캐시 라인 단위로 처리한다. 캐시 라인 단위 전송은 바이트 단위 전송보다 효율적이다. +- **프리페치**: 프로세서는 순차 접근이나 고정 간격 접근 같은 데이터 접근 패턴을 예측하고, 그 패턴에 따라 데이터를 미리 캐시에 불러와 적중률을 높인다. +- **공간 지역성**: 어떤 데이터에 접근하면 가까운 시일 안에 주변 데이터에도 접근할 가능성이 있다. 따라서 캐시는 데이터를 불러올 때 주변 데이터도 함께 불러와 적중률을 높인다. +- **시간 지역성**: 어떤 데이터에 접근하면 머지않아 다시 접근할 가능성이 높다. 캐시는 이 원리를 이용해 최근 접근한 데이터를 유지함으로써 적중률을 높인다. + +실제로 **배열과 연결 리스트의 캐시 활용 효율은 서로 다르며**, 주로 다음과 같은 차이가 있다. + +- **공간 사용량**: 연결 리스트 원소는 배열 원소보다 더 많은 공간을 차지하므로 캐시에 담을 수 있는 유효 데이터의 양이 적다. +- **캐시 라인**: 연결 리스트의 데이터는 메모리 곳곳에 흩어져 있지만 캐시는 ‘라인 단위’로 불러오므로, 불필요한 데이터를 가져오는 비율이 높다. +- **프리페치**: 배열의 데이터 접근 패턴은 연결 리스트보다 ‘예측 가능성’이 높다. 즉, 시스템이 다음에 불러올 데이터를 더 쉽게 예상할 수 있다. +- **공간 지역성**: 배열은 한곳에 모인 메모리 공간에 저장되므로, 불러온 데이터 주변의 데이터에도 곧 접근할 가능성이 더 높다. + +대체로 **배열은 캐시 적중률이 더 높아 연산 효율에서도 연결 리스트보다 유리한 경우가 많다**. 이 때문에 알고리즘 문제를 풀 때는 배열 기반 자료 구조를 더 선호하는 경우가 많다. + +다만 **캐시 효율이 높다고 해서 모든 상황에서 배열이 연결 리스트보다 좋은 것은 아니다**. 실제로 어떤 자료 구조를 쓸지는 구체적인 요구에 따라 결정해야 한다. 예를 들어 배열과 연결 리스트 모두 ‘스택’을 구현할 수 있지만(다음 장에서 자세히 설명한다), 적합한 상황은 다르다. + +- 알고리즘 문제를 풀 때는 배열 기반 스택을 선호한다. 배열에 일정한 메모리 공간을 미리 할당하기만 하면, 높은 연산 효율과 임의 접근 기능을 얻을 수 있기 때문이다. +- 데이터가 매우 많고 변화가 잦으며 스택의 예상 크기를 가늠하기 어렵다면 연결 리스트 기반 스택이 더 적합하다. 연결 리스트는 많은 데이터를 메모리 여러 곳에 분산 저장할 수 있으며, 배열 용량 확장에 따른 추가 비용도 피할 수 있다. diff --git a/ko/docs/chapter_array_and_linkedlist/summary.md b/ko/docs/chapter_array_and_linkedlist/summary.md new file mode 100644 index 000000000..b8039325c --- /dev/null +++ b/ko/docs/chapter_array_and_linkedlist/summary.md @@ -0,0 +1,90 @@ +--- +comments: true +--- + +# 4.5 요약 + +### 1. 핵심 정리 + +- 배열과 연결 리스트는 두 가지 기본 자료 구조로, 컴퓨터 메모리에 데이터를 저장하는 연속 공간 저장과 분산 공간 저장을 각각 대표한다. 둘의 특징은 서로 보완적이다. +- 배열은 임의 접근을 지원하고 메모리 사용량이 적다. 그러나 원소 삽입과 삭제의 효율이 낮으며 초기화한 뒤 길이를 바꿀 수 없다. +- 연결 리스트는 참조(포인터)를 바꾸어 노드를 효율적으로 삽입하고 삭제하며, 길이도 유연하게 조절할 수 있다. 하지만 노드 접근 효율이 낮고 메모리를 더 많이 사용한다. 대표적인 종류에는 단일 연결 리스트, 원형 연결 리스트, 이중 연결 리스트가 있다. +- 리스트는 삽입, 삭제, 탐색, 갱신을 지원하는 순서 있는 원소의 모음으로, 보통 동적 배열로 구현한다. 배열의 장점을 유지하면서 길이를 유연하게 조절할 수 있다. +- 리스트의 등장으로 배열의 실용성이 크게 높아졌지만, 메모리 공간 일부가 낭비될 수 있다. +- 프로그램이 실행될 때 데이터는 주로 메모리에 저장된다. 배열은 메모리 공간 효율이 더 높고, 연결 리스트는 메모리를 더 유연하게 사용한다. +- 캐시는 캐시 라인, 프리페치, 공간 지역성, 시간 지역성 등의 로딩 방식을 통해 CPU에 빠른 데이터 접근을 제공하여 프로그램 실행 효율을 크게 높인다. +- 배열은 캐시 적중률이 더 높으므로 보통 연결 리스트보다 효율적이다. 자료 구조를 선택할 때는 구체적인 요구와 상황에 맞춰야 한다. + +### 2. Q & A + +**Q**: 배열을 스택에 저장하는지 힙에 저장하는지에 따라 시간 효율과 공간 효율이 달라지는가? + +스택에 저장한 배열도 힙에 저장한 배열도 연속된 메모리 공간에 있으므로, 데이터 연산 효율은 대체로 같다. 다만 스택과 힙의 특성 때문에 다음과 같은 차이가 생긴다. + +1. 할당 및 해제 효율: 스택은 비교적 작은 메모리 영역으로 컴파일러가 자동으로 할당한다. 힙은 더 크며 코드에서 동적으로 할당할 수 있고 단편화도 더 쉽게 발생한다. 따라서 힙의 할당 및 해제 연산은 보통 스택보다 느리다. +2. 크기 제한: 스택 메모리는 비교적 작고 힙의 크기는 대개 사용 가능한 메모리에 의해 제한된다. 따라서 큰 배열을 저장하기에는 힙이 더 적합하다. +3. 유연성: 스택에 저장하는 배열의 크기는 컴파일 시 정해야 하지만, 힙에 저장하는 배열의 크기는 실행 중에 동적으로 정할 수 있다. + +**Q**: 배열은 원소의 자료형이 같아야 한다고 하는데, 연결 리스트에서는 왜 이를 강조하지 않는가? + +연결 리스트는 참조(포인터)로 연결된 노드들로 이루어진다. 각 노드에는 `int`, `double`, `string`, `object` 등 서로 다른 자료형의 데이터를 저장할 수 있다. + +반면 배열 원소는 자료형이 같아야 오프셋을 계산하여 해당 원소의 위치를 구할 수 있다. 예를 들어 배열에 `int`와 `long`이 함께 있으면 원소 하나가 각각 4바이트와 8바이트를 차지한다. ‘원소 길이’가 두 가지이므로 다음 식으로 오프셋을 계산할 수 없다. + +```shell +# 원소 메모리 주소 = 배열 메모리 주소(첫 원소의 메모리 주소) + 원소 길이 * 원소 인덱스 +``` + +**Q**: 노드 `P`를 삭제한 뒤 `P.next`를 `None`으로 설정해야 하는가? + +`P.next`를 바꾸지 않아도 된다. 해당 연결 리스트에서는 머리 노드부터 꼬리 노드까지 순회해도 더 이상 `P`를 만나지 않는다. 즉, `P`는 이미 연결 리스트에서 삭제되었으므로 어디를 가리키든 이 연결 리스트에 영향을 주지 않는다. + +자료 구조와 알고리즘 문제를 푸는 관점에서는 프로그램 논리가 올바르기만 하면 연결을 끊지 않아도 된다. 표준 라이브러리 관점에서는 연결을 끊는 편이 더 안전하고 논리도 명확하다. 연결을 끊지 않은 상태에서 삭제한 노드가 정상적으로 회수되지 않으면, 뒤따르는 노드의 메모리 회수에도 영향을 줄 수 있다. + +**Q**: 연결 리스트의 삽입과 삭제는 시간 복잡도가 $O(1)$이라고 한다. 하지만 먼저 원소를 찾는 데 $O(n)$이 필요한데, 전체 시간 복잡도는 왜 $O(n)$이 아닌가? + +원소를 먼저 찾고 삭제한다면 시간 복잡도는 실제로 $O(n)$이다. 다만 연결 리스트의 $O(1)$ 삽입 및 삭제는 다른 응용에서 장점을 발휘한다. 예를 들어 덱은 연결 리스트로 구현하기에 적합하다. 머리 노드와 꼬리 노드를 항상 가리키는 포인터 변수를 유지하면 매번 삽입과 삭제를 $O(1)$에 수행할 수 있다. + +**Q**: ‘연결 리스트의 정의와 저장 방식’ 그림에서 노드 포인터를 저장하는 연한 파란색 부분은 하나의 메모리 주소를 차지하는가? 아니면 노드 값과 반씩 차지하는가? + +그 그림은 정성적인 설명이다. 정확한 크기는 구체적인 상황에 따라 분석해야 한다. + +- 노드 값이 차지하는 공간은 자료형에 따라 다르다. 예를 들어 `int`, `long`, `double`, 인스턴스 객체 등이 있다. +- 포인터 변수가 차지하는 메모리 크기는 운영체제와 컴파일 환경에 따라 정해지며, 대개 8바이트 또는 4바이트다. + +**Q**: 리스트 끝에 원소를 추가하는 연산은 항상 $O(1)$인가? + +원소를 추가할 때 리스트의 길이를 넘으면 먼저 용량을 늘려야 한다. 시스템이 새 메모리 영역을 요청하고 기존 원소 전체를 옮기므로, 이때의 시간 복잡도는 $O(n)$이다. + +**Q**: ‘리스트는 배열의 실용성을 크게 높였지만 메모리 공간 일부를 낭비할 수 있다’는 설명에서, 공간 낭비는 용량, 길이, 확장 배수 같은 추가 변수의 메모리를 뜻하는가? + +여기서 공간 낭비는 주로 두 가지를 뜻한다. 하나는 리스트에 초기 길이를 설정하지만 그만큼 필요하지 않을 수 있다는 것이다. 다른 하나는 잦은 확장을 피하려고 보통 $\times 1.5$처럼 일정한 계수를 곱해 용량을 늘린다는 것이다. 이때도 빈자리가 많이 생기며, 보통 이를 전부 채우지는 못한다. + +**Q**: Python에서 `n = [1, 2, 3]`을 초기화하면 세 원소의 주소가 이어져 있다. 하지만 `m = [2, 1, 3]`을 초기화하면 원소들의 id가 연속적이지 않고 `n`의 같은 값과 각각 일치한다. 주소가 연속적이지 않아도 `m`은 배열인가? + +리스트 원소를 연결 리스트 노드로 바꾸어 `n = [n1, n2, n3, n4, n5]`라고 하면, 이 다섯 노드 객체도 보통 메모리 곳곳에 흩어져 있다. 그래도 리스트 인덱스가 주어지면 $O(1)$ 시간에 노드의 메모리 주소를 얻어 해당 노드에 접근할 수 있다. 배열에 저장된 것은 노드 자체가 아니라 노드의 참조이기 때문이다. + +많은 언어와 달리 Python에서는 숫자도 객체로 감싸져 있으며, 리스트에는 숫자 자체가 아닌 숫자의 참조가 저장된다. 따라서 두 배열의 같은 숫자가 같은 id를 가지며, 숫자들의 메모리 주소는 연속적일 필요가 없다. + +**Q**: C++ STL의 `std::list`는 이중 연결 리스트를 구현하는데, 일부 알고리즘 책에서는 거의 직접 쓰지 않는 것 같다. 어떤 한계가 있기 때문인가? + +한편으로는 보통 배열로 알고리즘을 구현하는 것을 선호하며, 꼭 필요할 때만 연결 리스트를 사용한다. 주된 이유는 두 가지다. + +- 공간 비용: 원소마다 이전 원소와 다음 원소를 위한 포인터가 두 개 더 필요하므로 `std::list`는 보통 `std::vector`보다 공간을 많이 차지한다. +- 캐시에 불리함: 데이터가 연속적으로 저장되지 않아 `std::list`의 캐시 활용률이 낮다. 일반적으로는 `std::vector`의 성능이 더 좋다. + +다른 한편으로 연결 리스트가 필요한 경우는 주로 이진 트리와 그래프다. 스택과 큐는 연결 리스트 대신 프로그래밍 언어가 제공하는 `stack`과 `queue`를 쓰는 경우가 많다. + +**Q**: `res = [[0]] * n`으로 만든 이차원 리스트에서 각 `[0]`은 서로 독립적인가? + +독립적이지 않다. 이 이차원 리스트의 모든 `[0]`은 실제로 같은 객체를 참조한다. 한 원소를 수정하면 대응하는 원소가 모두 함께 바뀐다. + +각 `[0]`을 독립적으로 만들려면 `res = [[0] for _ in range(n)]`을 사용하면 된다. 이 방식은 서로 독립적인 `[0]` 리스트 객체 $n$개를 초기화한다. + +**Q**: `res = [0] * n`으로 만든 리스트에서 각 정수 0은 서로 독립적인가? + +이 리스트의 모든 정수 0은 같은 객체를 참조한다. Python은 객체 재사용을 극대화하고 성능을 높이기 위해 작은 정수(보통 -5부터 256까지)를 캐시 풀로 관리하기 때문이다. + +모두 같은 객체를 가리켜도 리스트의 각 원소는 독립적으로 바꿀 수 있다. Python의 정수는 ‘불변 객체’이기 때문이다. 원소를 바꾸면 기존 객체 자체가 바뀌는 것이 아니라 다른 객체를 참조하게 된다. + +그러나 리스트 원소가 리스트, 딕셔너리, 클래스 인스턴스 같은 ‘가변 객체’라면, 원소의 내용을 수정할 때 객체 자체가 바뀌므로 이를 참조하는 모든 원소에서 같은 변화가 나타난다. diff --git a/ko/docs/chapter_backtracking/backtracking_algorithm.md b/ko/docs/chapter_backtracking/backtracking_algorithm.md new file mode 100644 index 000000000..25abd2bbe --- /dev/null +++ b/ko/docs/chapter_backtracking/backtracking_algorithm.md @@ -0,0 +1,2003 @@ +--- +comments: true +--- + +# 13.1 백트래킹 알고리즘 + +백트래킹 알고리즘(backtracking algorithm)은 모든 경우를 열거하여 문제를 푸는 방법이다. 초기 상태에서 출발하여 가능한 모든 해결 방법을 완전 탐색하고, 올바른 해를 만나면 기록한다. 해를 찾거나 가능한 선택을 모두 시도해도 찾을 수 없을 때까지 진행하는 것이 핵심이다. + +백트래킹은 보통 '깊이 우선 탐색'으로 해 공간을 순회한다. '이진 트리' 장에서 전위, 중위, 후위 순회가 모두 깊이 우선 탐색이라고 설명했다. 이제 전위 순회를 이용한 백트래킹 문제를 만들어 작동 원리를 차근차근 알아보자. + +!!! question "예제 1" + + 이진 트리가 주어졌을 때 값이 $7$인 모든 노드를 찾아 기록하고, 노드 리스트를 반환하라. + +이 문제에서는 트리를 전위 순회하며 현재 노드의 값이 $7$인지 확인하고, 맞으면 해당 노드의 값을 결과 리스트 `res`에 추가한다. 과정은 아래 그림(13-1)과 코드에 나타나 있다. + +=== "Python" + + ```python title="preorder_traversal_i_compact.py" + def pre_order(root: TreeNode): + """전위 순회: 예제 1""" + if root is None: + return + if root.val == 7: + # 해를 기록한다 + res.append(root) + pre_order(root.left) + pre_order(root.right) + ``` + +=== "C++" + + ```cpp title="preorder_traversal_i_compact.cpp" + /* 전위 순회: 예제 1 */ + void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + if (root->val == 7) { + // 해를 기록한다 + res.push_back(root); + } + preOrder(root->left); + preOrder(root->right); + } + ``` + +=== "Java" + + ```java title="preorder_traversal_i_compact.java" + /* 전위 순회: 예제 1 */ + void preOrder(TreeNode root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 해를 기록한다 + res.add(root); + } + preOrder(root.left); + preOrder(root.right); + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_i_compact.cs" + /* 전위 순회: 예제 1 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + if (root.val == 7) { + // 해를 기록한다 + res.Add(root); + } + PreOrder(root.left); + PreOrder(root.right); + } + ``` + +=== "Go" + + ```go title="preorder_traversal_i_compact.go" + /* 전위 순회: 예제 1 */ + func preOrderI(root *TreeNode, res *[]*TreeNode) { + if root == nil { + return + } + if (root.Val).(int) == 7 { + // 해를 기록한다 + *res = append(*res, root) + } + preOrderI(root.Left, res) + preOrderI(root.Right, res) + } + ``` + +=== "Swift" + + ```swift title="preorder_traversal_i_compact.swift" + /* 전위 순회: 예제 1 */ + func preOrder(root: TreeNode?) { + guard let root = root else { + return + } + if root.val == 7 { + // 해를 기록한다 + res.append(root) + } + preOrder(root: root.left) + preOrder(root: root.right) + } + ``` + +=== "JS" + + ```javascript title="preorder_traversal_i_compact.js" + /* 전위 순회: 예제 1 */ + function preOrder(root, res) { + if (root === null) { + return; + } + if (root.val === 7) { + // 해를 기록한다 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); + } + ``` + +=== "TS" + + ```typescript title="preorder_traversal_i_compact.ts" + /* 전위 순회: 예제 1 */ + function preOrder(root: TreeNode | null, res: TreeNode[]): void { + if (root === null) { + return; + } + if (root.val === 7) { + // 해를 기록한다 + res.push(root); + } + preOrder(root.left, res); + preOrder(root.right, res); + } + ``` + +=== "Dart" + + ```dart title="preorder_traversal_i_compact.dart" + /* 전위 순회: 예제 1 */ + void preOrder(TreeNode? root, List그림 13-1 전위 순회로 노드 탐색
+ +## 13.1.1 시도와 되돌리기 + +**백트래킹이라고 부르는 이유는 해 공간을 탐색할 때 '시도'와 '되돌리기' 전략을 사용하기 때문이다**. 탐색 중 더 진행할 수 없거나 조건을 만족하는 해를 얻을 수 없는 상태를 만나면, 직전 선택을 취소하고 이전 상태로 돌아가 다른 선택을 시도한다. + +예제 1에서는 각 노드의 방문이 한 번의 '시도'이며, 리프 노드를 지나거나 부모 노드로 돌아가는 `return`이 '되돌리기'에 해당한다. + +**되돌리기에는 함수 반환만 포함되는 것이 아니다**. 이를 설명하기 위해 예제 1을 조금 확장해 보자. + +!!! question "예제 2" + + 이진 트리에서 값이 $7$인 모든 노드를 찾아 **루트 노드에서 각 노드까지의 경로를 반환하라**. + +예제 1의 코드에 리스트 `path`를 추가하여 방문한 노드의 경로를 기록해야 한다. 값이 $7$인 노드를 방문하면 `path`를 복사하여 결과 리스트 `res`에 추가한다. 순회를 마치면 `res`에 모든 해가 저장된다. 코드는 다음과 같다. + +=== "Python" + + ```python title="preorder_traversal_ii_compact.py" + def pre_order(root: TreeNode): + """전위 순회: 예제 2""" + if root is None: + return + # 시도 + path.append(root) + if root.val == 7: + # 해를 기록한다 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 되돌리기 + path.pop() + ``` + +=== "C++" + + ```cpp title="preorder_traversal_ii_compact.cpp" + /* 전위 순회: 예제 2 */ + void preOrder(TreeNode *root) { + if (root == nullptr) { + return; + } + // 시도 + path.push_back(root); + if (root->val == 7) { + // 해를 기록한다 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 되돌리기 + path.pop_back(); + } + ``` + +=== "Java" + + ```java title="preorder_traversal_ii_compact.java" + /* 전위 순회: 예제 2 */ + void preOrder(TreeNode root) { + if (root == null) { + return; + } + // 시도 + path.add(root); + if (root.val == 7) { + // 해를 기록한다 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 되돌리기 + path.remove(path.size() - 1); + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_ii_compact.cs" + /* 전위 순회: 예제 2 */ + void PreOrder(TreeNode? root) { + if (root == null) { + return; + } + // 시도 + path.Add(root); + if (root.val == 7) { + // 해를 기록한다 + res.Add(new List그림 13-2 시도와 되돌리기
+ +## 13.1.2 가지치기 + +복잡한 백트래킹 문제에는 보통 하나 이상의 제약 조건이 있으며, **이 제약 조건을 '가지치기'에 활용할 수 있다**. + +!!! question "예제 3" + + 이진 트리에서 값이 $7$인 모든 노드를 찾아 루트 노드에서 각 노드까지의 경로를 반환하라. **단, 경로에 값이 $3$인 노드가 포함되면 안 된다**. + +이 제약을 만족하려면 **가지치기 연산을 추가해야 한다**. 탐색 중 값이 $3$인 노드를 만나면 더 탐색하지 않고 조기에 반환한다. 코드는 다음과 같다. + +=== "Python" + + ```python title="preorder_traversal_iii_compact.py" + def pre_order(root: TreeNode): + """전위 순회: 예제 3""" + # 가지치기 + if root is None or root.val == 3: + return + # 시도 + path.append(root) + if root.val == 7: + # 해를 기록한다 + res.append(list(path)) + pre_order(root.left) + pre_order(root.right) + # 되돌리기 + path.pop() + ``` + +=== "C++" + + ```cpp title="preorder_traversal_iii_compact.cpp" + /* 전위 순회: 예제 3 */ + void preOrder(TreeNode *root) { + // 가지치기 + if (root == nullptr || root->val == 3) { + return; + } + // 시도 + path.push_back(root); + if (root->val == 7) { + // 해를 기록한다 + res.push_back(path); + } + preOrder(root->left); + preOrder(root->right); + // 되돌리기 + path.pop_back(); + } + ``` + +=== "Java" + + ```java title="preorder_traversal_iii_compact.java" + /* 전위 순회: 예제 3 */ + void preOrder(TreeNode root) { + // 가지치기 + if (root == null || root.val == 3) { + return; + } + // 시도 + path.add(root); + if (root.val == 7) { + // 해를 기록한다 + res.add(new ArrayList<>(path)); + } + preOrder(root.left); + preOrder(root.right); + // 되돌리기 + path.remove(path.size() - 1); + } + ``` + +=== "C#" + + ```csharp title="preorder_traversal_iii_compact.cs" + /* 전위 순회: 예제 3 */ + void PreOrder(TreeNode? root) { + // 가지치기 + if (root == null || root.val == 3) { + return; + } + // 시도 + path.Add(root); + if (root.val == 7) { + // 해를 기록한다 + res.Add(new List그림 13-3 제약 조건에 따른 가지치기
+ +## 13.1.3 기본 틀 코드 + +이제 백트래킹의 '시도, 되돌리기, 가지치기'라는 기본 틀을 추출하여 코드의 범용성을 높여 보자. + +아래 기본 틀 코드에서 `state`는 문제의 현재 상태를, `choices`는 그 상태에서 가능한 선택들을 나타낸다. + +=== "Python" + + ```python title="" + def backtrack(state: State, choices: list[choice], res: list[state]): + """백트래킹 알고리즘의 기본 틀""" + # 해인지 확인 + if is_solution(state): + # 해 기록 + record_solution(state, res) + # 더 이상 탐색하지 않는다 + return + # 모든 선택지 순회 + for choice in choices: + # 가지치기: 선택이 유효한지 확인 + if is_valid(state, choice): + # 시도: 선택하고 상태 갱신 + make_choice(state, choice) + backtrack(state, choices, res) + # 되돌리기: 선택을 취소하고 이전 상태 복원 + undo_choice(state, choice) + ``` + +=== "C++" + + ```cpp title="" + /* 백트래킹 알고리즘의 기본 틀 */ + void backtrack(State *state, vector