diff --git a/chapter_backtracking/backtracking_algorithm.md b/chapter_backtracking/backtracking_algorithm.md index 71d2af330..a1539fd32 100644 --- a/chapter_backtracking/backtracking_algorithm.md +++ b/chapter_backtracking/backtracking_algorithm.md @@ -332,7 +332,28 @@ comments: true === "C" ```c title="preorder_traversal_ii_compact.c" - [class]{}-[func]{preOrder} + /* 前序遍历:例题二 */ + void preOrder(TreeNode *root, vector *path, vector *res) { + if (root == NULL) { + return; + } + // 尝试 + vectorPushback(path, root); + if (root->val == 7) { + // 记录解 + vector *newPath = newVector(); + for (int i = 0; i < path->size; i++) { + vectorPushback(newPath, path->data[i]); + } + vectorPushback(res, newPath); + } + + preOrder(root->left, path, res); + preOrder(root->right, path, res); + + // 回退 + vectorPopback(path); + } ``` === "C#" @@ -607,7 +628,30 @@ comments: true === "C" ```c title="preorder_traversal_iii_compact.c" - [class]{}-[func]{preOrder} + /* 前序遍历:例题三 */ + void preOrder(TreeNode *root, vector *path, vector *res) { + // 剪枝 + if (root == NULL || root->val == 3) { + return; + } + // 尝试 + vectorPushback(path, root); + if (root->val == 7) { + // 记录解 + vector *newPath = newVector(); + for (int i = 0; i < path->size; i++) { + vectorPushback(newPath, path->data[i]); + } + vectorPushback(res, newPath); + res->depth++; + } + + preOrder(root->left, path, res); + preOrder(root->right, path, res); + + // 回退 + vectorPopback(path); + } ``` === "C#" @@ -1281,17 +1325,60 @@ comments: true === "C" ```c title="preorder_traversal_iii_template.c" - [class]{}-[func]{isSolution} + /* 判断当前状态是否为解 */ + bool isSolution(vector *state) { + return state->size != 0 && ((TreeNode *)(state->data[state->size - 1]))->val == 7; + } - [class]{}-[func]{recordSolution} + /* 记录解 */ + void recordSolution(vector *state, vector *res) { + vector *newPath = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(newPath, state->data[i]); + } + vectorPushback(res, newPath); + } - [class]{}-[func]{isValid} + /* 判断在当前状态下,该选择是否合法 */ + bool isValid(vector *state, TreeNode *choice) { + return choice != NULL && choice->val != 3; + } - [class]{}-[func]{makeChoice} + /* 更新状态 */ + void makeChoice(vector *state, TreeNode *choice) { + vectorPushback(state, choice); + } - [class]{}-[func]{undoChoice} + /* 恢复状态 */ + void undoChoice(vector *state, TreeNode *choice) { + vectorPopback(state); + } - [class]{}-[func]{backtrack} + /* 前序遍历:例题三 */ + void backtrack(vector *state, vector *choices, vector *res) { + // 检查是否为解 + if (isSolution(state)) { + // 记录解 + recordSolution(state, res); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices->size; i++) { + TreeNode *choice = choices->data[i]; + // 剪枝:检查选择是否合法 + if (isValid(state, choice)) { + // 尝试:做出选择,更新状态 + makeChoice(state, choice); + // 进行下一轮选择 + vector *nextChoices = newVector(); + vectorPushback(nextChoices, choice->left); + vectorPushback(nextChoices, choice->right); + backtrack(state, nextChoices, res); + // 回退:撤销选择,恢复到之前的状态 + undoChoice(state, choice); + } + } + } ``` === "C#" diff --git a/chapter_backtracking/permutations_problem.md b/chapter_backtracking/permutations_problem.md index 6dc9f026a..b919d016c 100644 --- a/chapter_backtracking/permutations_problem.md +++ b/chapter_backtracking/permutations_problem.md @@ -269,9 +269,52 @@ comments: true === "C" ```c title="permutations_i.c" - [class]{}-[func]{backtrack} + /* 回溯算法:全排列 I */ + void backtrack(vector *state, vector *choices, vector *selected, vector *res) { + // 当状态长度等于元素数量时,记录解 + if (state->size == choices->size) { + vector *newState = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(newState, state->data[i]); + } + vectorPushback(res, newState); + return; + } + // 遍历所有选择 + for (int i = 0; i < choices->size; i++) { + int *choice = malloc(sizeof(int)); + *choice = *((int *)(choices->data[i])); + // 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 + bool select = *((bool *)(selected->data[i])); + if (!select) { + // 尝试:做出选择,更新状态 + *((bool *)selected->data[i]) = true; + vectorPushback(state, choice); + // 进行下一轮选择 + backtrack(state, choices, selected, res); + // 回退:撤销选择,恢复到之前的状态 + *((bool *)selected->data[i]) = false; + vectorPopback(state); + } + } + } - [class]{}-[func]{permutationsI} + /* 全排列 I */ + vector *permutationsI(vector *nums) { + vector *iState = newVector(); + + int select[3] = {false, false, false}; + vector *bSelected = newVector(); + for (int i = 0; i < nums->size; i++) { + vectorPushback(bSelected, &select[i]); + } + + vector *res = newVector(); + + // 前序遍历 + backtrack(iState, nums, bSelected, res); + return res; + } ``` === "C#" diff --git a/chapter_backtracking/subset_sum_problem.md b/chapter_backtracking/subset_sum_problem.md index b54b7cf0c..fc9f19787 100644 --- a/chapter_backtracking/subset_sum_problem.md +++ b/chapter_backtracking/subset_sum_problem.md @@ -182,9 +182,40 @@ comments: true === "C" ```c title="subset_sum_i_naive.c" - [class]{}-[func]{backtrack} + /* 回溯算法:子集和 I */ + void backtrack(vector *state, int target, int total, vector *choices, vector *res) { + // 子集和等于 target 时,记录解 + if (total == target) { + vector *tmpVector = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(tmpVector, state->data[i]); + } + vectorPushback(res, tmpVector); + return; + } + // 遍历所有选择 + for (size_t i = 0; i < choices->size; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (total + *(int *)(choices->data[i]) > target) { + continue; + } + // 尝试:做出选择,更新元素和 total + vectorPushback(state, choices->data[i]); + // 进行下一轮选择 + backtrack(state, target, total + *(int *)(choices->data[i]), choices, res); + // 回退:撤销选择,恢复到之前的状态 + vectorPopback(state); + } + } - [class]{}-[func]{subsetSumINaive} + /* 求解子集和 I(包含重复子集) */ + vector *subsetSumINaive(vector *nums, int target) { + vector *state = newVector(); // 状态(子集) + int total = 0; // 子集和 + vector *res = newVector(); // 结果列表(子集列表) + backtrack(state, target, total, nums, res); + return res; + } ``` === "C#" @@ -518,9 +549,42 @@ comments: true === "C" ```c title="subset_sum_i.c" - [class]{}-[func]{backtrack} + /* 回溯算法:子集和 I */ + void backtrack(vector *state, int target, vector *choices, int start, vector *res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + vector *tmpVector = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(tmpVector, state->data[i]); + } + vectorPushback(res, tmpVector); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + for (int i = start; i < choices->size; i++) { + // 剪枝:若子集和超过 target ,则跳过该选择 + if (target - *(int *)(choices->data[i]) < 0) { + continue; + } + // 尝试:做出选择,更新 target, start + vectorPushback(state, choices->data[i]); + // 进行下一轮选择 + backtrack(state, target - *(int *)(choices->data[i]), choices, i, res); + // 回退:撤销选择,恢复到之前的状态 + vectorPopback(state); + } + } - [class]{}-[func]{subsetSumI} + /* 求解子集和 I */ + vector *subsetSumI(vector *nums, int target) { + vector *state = newVector(); // 状态(子集) + qsort(nums->data[0], nums->size, sizeof(int), comp); // 对 nums 进行排序 + int start = 0; // 子集和 + vector *res = newVector(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } ``` === "C#" @@ -869,9 +933,48 @@ comments: true === "C" ```c title="subset_sum_ii.c" - [class]{}-[func]{backtrack} + /* 回溯算法:子集和 II */ + void backtrack(vector *state, int target, vector *choices, int start, vector *res) { + // 子集和等于 target 时,记录解 + if (target == 0) { + vector *tmpVector = newVector(); + for (int i = 0; i < state->size; i++) { + vectorPushback(tmpVector, state->data[i]); + } + vectorPushback(res, tmpVector); + return; + } + // 遍历所有选择 + // 剪枝二:从 start 开始遍历,避免生成重复子集 + // 剪枝三:从 start 开始遍历,避免重复选择同一元素 + for (int i = start; i < choices->size; i++) { + // 剪枝一:若子集和超过 target ,则直接结束循环 + // 这是因为数组已排序,后边元素更大,子集和一定超过 target + if (target - *(int *)(choices->data[i]) < 0) { + continue; + } + // 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 + if (i > start && *(int *)(choices->data[i]) == *(int *)(choices->data[i - 1])) { + continue; + } + // 尝试:做出选择,更新 target, start + vectorPushback(state, choices->data[i]); + // 进行下一轮选择 + backtrack(state, target - *(int *)(choices->data[i]), choices, i + 1, res); + // 回退:撤销选择,恢复到之前的状态 + vectorPopback(state); + } + } - [class]{}-[func]{subsetSumII} + /* 求解子集和 II */ + vector *subsetSumII(vector *nums, int target) { + vector *state = newVector(); // 状态(子集) + qsort(nums->data[0], nums->size, sizeof(int), comp); // 对 nums 进行排序 + int start = 0; // 子集和 + vector *res = newVector(); // 结果列表(子集列表) + backtrack(state, target, nums, start, res); + return res; + } ``` === "C#" diff --git a/chapter_computational_complexity/space_complexity.md b/chapter_computational_complexity/space_complexity.md index 2741952ae..27ca8c891 100755 --- a/chapter_computational_complexity/space_complexity.md +++ b/chapter_computational_complexity/space_complexity.md @@ -1651,7 +1651,7 @@ $$ return 0; int *nums = malloc(sizeof(int) * n); printf("递归 n = %d 中的 nums 长度 = %d\r\n", n, n); - int res = quadraticRecur(n - 1) + int res = quadraticRecur(n - 1); free(nums); return res; } diff --git a/chapter_divide_and_conquer/hanota_problem.md b/chapter_divide_and_conquer/hanota_problem.md index ce9ece326..296189486 100644 --- a/chapter_divide_and_conquer/hanota_problem.md +++ b/chapter_divide_and_conquer/hanota_problem.md @@ -73,7 +73,7 @@ status: new === "<4>" ![hanota_f3_step4](hanota_problem.assets/hanota_f3_step4.png) -本质上看,**我们将问题 $f(3)$ 划分为两个子问题 $f(2)$ 和子问题 $f(1)$** 。按顺序解决这三个子问题之后,原问题随之得到解决。这说明子问题是独立的,且解是可以合并的。 +本质上看,**我们将问题 $f(3)$ 划分为两个子问题 $f(2)$ 和子问题 $f(1)$** 。按顺序解决这三个子问题之后,原问题随之得到解决。这说明子问题是独立的,而且解是可以合并的。 至此,我们可总结出汉诺塔问题的分治策略:将原问题 $f(n)$ 划分为两个子问题 $f(n-1)$ 和一个子问题 $f(1)$ 。子问题的解决顺序为: diff --git a/chapter_dynamic_programming/dp_problem_features.md b/chapter_dynamic_programming/dp_problem_features.md index 70d6b09cf..93d2472a0 100644 --- a/chapter_dynamic_programming/dp_problem_features.md +++ b/chapter_dynamic_programming/dp_problem_features.md @@ -5,10 +5,10 @@ status: new # 14.2.   动态规划问题特性 -在上节中,我们学习了动态规划是如何通过子问题分解来求解问题的。实际上,子问题分解是一种通用的算法思路,在分治、动态规划、回溯中各有特点: +在上节中,我们学习了动态规划是如何通过子问题分解来求解问题的。实际上,子问题分解是一种通用的算法思路,在分治、动态规划、回溯中的侧重点不同: -- 「分治算法」递归地将原问题划分为多个互相独立的子问题,直至最小子问题,并在回溯中合并子问题的解,最终得到原问题的解。 -- 「动态规划」也对问题进行递归分解,但与分治算法的主要区别是,**动态规划中的子问题往往不是相互独立的**,原问题的解依赖于子问题的解,而子问题的解又依赖于更小的子问题的解。 +- 「分治算法」递归地将原问题划分为多个相互独立的子问题,直至最小子问题,并在回溯中合并子问题的解,最终得到原问题的解。 +- 「动态规划」也对问题进行递归分解,但与分治算法的主要区别是,动态规划中的子问题是相互依赖的,在分解过程中会出现许多重叠子问题。 - 「回溯算法」在尝试和回退中穷举所有可能的解,并通过剪枝避免不必要的搜索分支。原问题的解由一系列决策步骤构成,我们可以将每个决策步骤之前的子序列看作为一个子问题。 实际上,动态规划常用来求解最优化问题,它们不仅包含重叠子问题,还具有另外两大特性:最优子结构、无后效性。 diff --git a/chapter_dynamic_programming/intro_to_dynamic_programming.md b/chapter_dynamic_programming/intro_to_dynamic_programming.md index 6491115c1..14207665e 100644 --- a/chapter_dynamic_programming/intro_to_dynamic_programming.md +++ b/chapter_dynamic_programming/intro_to_dynamic_programming.md @@ -356,7 +356,7 @@ $$ dp[i] = dp[i-1] + dp[i-2] $$ -这意味着在爬楼梯问题中,**各个子问题之间不是相互独立的,原问题的解可以从子问题的解构建得来**。 +这意味着在爬楼梯问题中,各个子问题之间存在递推关系,**原问题的解可以由子问题的解构建得来**。 ![方案数量递推关系](intro_to_dynamic_programming.assets/climbing_stairs_state_transfer.png) diff --git a/chapter_preface/about_the_book.md b/chapter_preface/about_the_book.md index 01cfdfe40..bdc0f7f26 100644 --- a/chapter_preface/about_the_book.md +++ b/chapter_preface/about_the_book.md @@ -26,7 +26,7 @@ comments: true 本书主要内容包括: -- **复杂度分析**:数据结构和算法的评价维度,算法效率的评估方法。时间复杂度、空间复杂度的推算方法、常见类型、示例等。 +- **复杂度分析**:数据结构和算法的评价维度与方法。时间复杂度、空间复杂度的推算方法、常见类型、示例等。 - **数据结构**:基本数据类型,数据结构的分类方法。数组、链表、栈、队列、散列表、树、堆、图等数据结构的定义、优缺点、常用操作、常见类型、典型应用、实现方法等。 - **算法**:搜索、排序、分治、回溯、动态规划、贪心等算法的定义、优缺点、效率、应用场景、解题步骤、示例题目等。 diff --git a/chapter_stack_and_queue/queue.md b/chapter_stack_and_queue/queue.md index 71587fb8b..62f2a86bc 100755 --- a/chapter_stack_and_queue/queue.md +++ b/chapter_stack_and_queue/queue.md @@ -1383,7 +1383,7 @@ comments: true if self.__size == self.capacity(): raise IndexError("队列已满") # 计算尾指针,指向队尾索引 + 1 - # 通过取余操作,实现 rear 越过数组尾部后回到头部F + # 通过取余操作,实现 rear 越过数组尾部后回到头部 rear: int = (self.__front + self.__size) % self.capacity() # 将 num 添加至队尾 self.__nums[rear] = num diff --git a/overrides/home.html b/overrides/home.html index 24fbf86a9..3ad794287 100644 --- a/overrides/home.html +++ b/overrides/home.html @@ -1,280 +1,417 @@ - {% extends "main.html" %} + + {% block tabs %} -{{ super() }} + {{ super() }} + + - -
+ +
+
+ +
+
+ +
+ - -
-
-

- - - - UP42 in Python -

-

Use UP42 via Python: order geospatial data, run analytic workflows, and - generate insights.

-
-
-

- - - Python ecosystem -

-

Use UP42 together with your preferred Python libraries.

-
-
-

- - Visualizations -

-

Interactive maps and visualizations. Ideal to use with Jupyter notebooks.

-
-
+
+
+ +
+
+
+

全书动画图解

+

+ 内容清晰易懂、学习曲线平滑
电脑、平板、手机全终端阅读 +

+
+
+
+ +
+
+
+
-
-
+ + + + +
-
- - +
{% endblock %} + + {% block content %}{% endblock %} -{% block footer %}{% endblock %} \ No newline at end of file + + +{% block footer %} + {{ super() }} +{% endblock %} \ No newline at end of file