diff --git a/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp b/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp index 80c45dc1a..8f6da57b2 100644 --- a/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp +++ b/codes/cpp/chapter_backtracking/preorder_traversal_iii_template.cpp @@ -37,7 +37,6 @@ void backtrack(vector &state, vector &choices, vector>(); preOrder(root); - Console.WriteLine("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点"); + Console.WriteLine("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); foreach (List path in res) { PrintUtil.PrintList(path.Select(p => p.val).ToList()); } diff --git a/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs b/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs index 8b850cf89..06d876e26 100644 --- a/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs +++ b/codes/csharp/chapter_backtracking/preorder_traversal_iii_template.cs @@ -38,7 +38,6 @@ public class preorder_traversal_iii_template { if (isSolution(state)) { // 记录解 recordSolution(state, res); - return; } // 遍历所有选择 foreach (TreeNode choice in choices) { diff --git a/codes/go/chapter_backtracking/preorder_traversal_test.go b/codes/go/chapter_backtracking/preorder_traversal_test.go index 295539552..69f569380 100644 --- a/codes/go/chapter_backtracking/preorder_traversal_test.go +++ b/codes/go/chapter_backtracking/preorder_traversal_test.go @@ -59,7 +59,7 @@ func TestPreorderTraversalIIICompact(t *testing.T) { res := make([][]*TreeNode, 0) preOrderIII(root, &res, &path) - fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点") + fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) @@ -81,7 +81,7 @@ func TestPreorderTraversalIIITemplate(t *testing.T) { choices = append(choices, root) backtrackIII(&state, &choices, &res) - fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点") + fmt.Println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for _, path := range res { for _, node := range path { fmt.Printf("%v ", node.Val) diff --git a/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java b/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java index 469c6dfa9..9ab3bb8d9 100644 --- a/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java +++ b/codes/java/chapter_backtracking/preorder_traversal_iii_compact.java @@ -43,7 +43,7 @@ public class preorder_traversal_iii_compact { res = new ArrayList<>(); preOrder(root); - System.out.println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点"); + System.out.println("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); for (List path : res) { List vals = new ArrayList<>(); for (TreeNode node : path) { diff --git a/codes/java/chapter_backtracking/preorder_traversal_iii_template.java b/codes/java/chapter_backtracking/preorder_traversal_iii_template.java index 61779165b..86fa8a971 100644 --- a/codes/java/chapter_backtracking/preorder_traversal_iii_template.java +++ b/codes/java/chapter_backtracking/preorder_traversal_iii_template.java @@ -41,7 +41,6 @@ public class preorder_traversal_iii_template { if (isSolution(state)) { // 记录解 recordSolution(state, res); - return; } // 遍历所有选择 for (TreeNode choice : choices) { diff --git a/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js b/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js index 73cfcac85..2c2cbb93d 100644 --- a/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js +++ b/codes/javascript/chapter_backtracking/preorder_traversal_iii_compact.js @@ -37,7 +37,7 @@ const path = []; const res = []; preOrder(root, path, res); -console.log('\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点'); +console.log('\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点'); res.forEach((path) => { console.log(path.map((node) => node.val)); }); diff --git a/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js b/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js index 8a9825f11..a8d911271 100644 --- a/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js +++ b/codes/javascript/chapter_backtracking/preorder_traversal_iii_template.js @@ -38,7 +38,6 @@ function backtrack(state, choices, res) { if (isSolution(state)) { // 记录解 recordSolution(state, res); - return; } // 遍历所有选择 for (const choice of choices) { diff --git a/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py b/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py index 4ee4d77c6..f5af42fc4 100644 --- a/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py +++ b/codes/python/chapter_backtracking/preorder_traversal_iii_compact.py @@ -39,6 +39,6 @@ if __name__ == "__main__": res = list[list[TreeNode]]() pre_order(root) - print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点") + print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") for path in res: print([node.val for node in path]) diff --git a/codes/python/chapter_backtracking/preorder_traversal_iii_template.py b/codes/python/chapter_backtracking/preorder_traversal_iii_template.py index 6fc3b4fef..ef5466a44 100644 --- a/codes/python/chapter_backtracking/preorder_traversal_iii_template.py +++ b/codes/python/chapter_backtracking/preorder_traversal_iii_template.py @@ -43,7 +43,6 @@ def backtrack( if is_solution(state): # 记录解 record_solution(state, res) - return # 遍历所有选择 for choice in choices: # 剪枝:检查选择是否合法 diff --git a/codes/python/modules/__init__.py b/codes/python/modules/__init__.py index 96a04f2cb..28175051e 100644 --- a/codes/python/modules/__init__.py +++ b/codes/python/modules/__init__.py @@ -9,7 +9,7 @@ from .list_node import ( linked_list_to_list, get_list_node, ) -from .tree_node import TreeNode, list_to_tree, tree_to_list, get_tree_node +from .tree_node import TreeNode, list_to_tree, tree_to_list from .vertex import Vertex, vals_to_vets, vets_to_vals from .print_util import ( print_matrix, diff --git a/codes/python/modules/tree_node.py b/codes/python/modules/tree_node.py index 45707ace5..ead12eede 100644 --- a/codes/python/modules/tree_node.py +++ b/codes/python/modules/tree_node.py @@ -67,14 +67,3 @@ def tree_to_list(root: TreeNode | None) -> list[int]: res = [] tree_to_list_dfs(root, 0, res) return res - - -def get_tree_node(root: TreeNode | None, val: int) -> TreeNode | None: - """Get a tree node with specific value in a binary tree""" - if not root: - return - if root.val == val: - return root - left: TreeNode | None = get_tree_node(root.left, val) - right: TreeNode | None = get_tree_node(root.right, val) - return left if left else right diff --git a/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs b/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs index 54755ca43..ae28d3f88 100644 --- a/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs +++ b/codes/rust/chapter_backtracking/preorder_traversal_iii_compact.rs @@ -42,7 +42,7 @@ pub fn main() { let mut res = Vec::new(); pre_order(&mut res, &mut path, root); - println!("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点"); + println!("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点"); for path in res { let mut vals = Vec::new(); for node in path { diff --git a/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs b/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs index 15dfff50e..53271ef9a 100644 --- a/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs +++ b/codes/rust/chapter_backtracking/preorder_traversal_iii_template.rs @@ -40,7 +40,6 @@ fn backtrack(state: &mut Vec>>, choices: &mut Vec { console.log(path.map((node) => node.val)); }); diff --git a/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts b/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts index ab17b6bdf..c2b1ac8de 100644 --- a/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts +++ b/codes/typescript/chapter_backtracking/preorder_traversal_iii_template.ts @@ -43,7 +43,6 @@ function backtrack( if (isSolution(state)) { // 记录解 recordSolution(state, res); - return; } // 遍历所有选择 for (const choice of choices) { diff --git a/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png b/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png new file mode 100644 index 000000000..1689d91c8 Binary files /dev/null and b/docs/chapter_backtracking/backtracking_algorithm.assets/backtrack_remove_return_or_not.png differ diff --git a/docs/chapter_backtracking/backtracking_algorithm.md b/docs/chapter_backtracking/backtracking_algorithm.md index 405939b0d..f28e941d0 100644 --- a/docs/chapter_backtracking/backtracking_algorithm.md +++ b/docs/chapter_backtracking/backtracking_algorithm.md @@ -201,12 +201,9 @@ !!! question "例题三" - 在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**要求路径中只存在一个值为 $7$ 的节点,并且不允许有值为 $3$ 的节点**。 + 在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**并要求路径中不包含值为 $3$ 的节点**。 -在例题二的基础上添加剪枝操作,包括: - -- 当遇到值为 $7$ 的节点时,记录解并返回,停止搜索。 -- 当遇到值为 $3$ 的节点时,则直接返回,停止搜索。 +为了满足以上约束条件,**我们需要添加剪枝操作**:在搜索过程中,若遇到值为 $3$ 的节点,则提前返回,停止继续搜索。 === "Java" @@ -274,27 +271,10 @@ [class]{}-[func]{preOrder} ``` -剪枝是一个非常形象的名词。在搜索过程中,**我们利用约束条件“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而提升搜索效率。 +剪枝是一个非常形象的名词。在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而实现搜索效率的提高。 ![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png) -## 常用术语 - -为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例。 - -| 名词 | 定义 | 例题三 | -| ------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| 解 Solution | 解是满足问题特定条件的答案,可能有一个或多个 | 根节点到节点 $7$ 的满足约束条件的所有路径 | -| 约束条件 Constraint | 约束条件是问题中限制解的可行性的条件,通常用于剪枝 | 路径中不包含节点 $3$ ,只包含一个节点 $7$ | -| 状态 State | 状态表示问题在某一时刻的情况,包括已经做出的选择 | 当前已访问的节点路径,即 `path` 节点列表 | -| 尝试 Attempt | 尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解 | 递归访问左(右)子节点,将节点添加进 `path` ,判断节点的值是否为 $7$ | -| 回退 Backtracking | 回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态 | 当越过叶结点、结束结点访问、遇到值为 $3$ 的节点时终止搜索,函数返回 | -| 剪枝 Pruning | 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 | 当遇到值为 $3$ 的节点时,则终止继续搜索 | - -!!! tip - - 问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。 - ## 框架代码 接下来,我们尝试将回溯的“尝试、回退、剪枝”的主体框架提炼出来,提升代码的通用性。 @@ -310,6 +290,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res); + // 停止继续搜索 return; } // 遍历所有选择 @@ -335,6 +316,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res); + // 停止继续搜索 return; } // 遍历所有选择 @@ -354,12 +336,13 @@ === "Python" ```python title="" - def backtrack(state: State, choices: list[choice], res: list[state]) -> None: + def backtrack(state: State, choices: list[choice], res: list[state]): """回溯算法框架""" # 判断是否为解 if is_solution(state): # 记录解 record_solution(state, res) + # 停止继续搜索 return # 遍历所有选择 for choice in choices: @@ -381,6 +364,7 @@ if isSolution(state) { // 记录解 recordSolution(state, res) + // 停止继续搜索 return } // 遍历所有选择 @@ -406,6 +390,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res); + // 停止继续搜索 return; } // 遍历所有选择 @@ -431,6 +416,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res); + // 停止继续搜索 return; } // 遍历所有选择 @@ -456,6 +442,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res, numRes); + // 停止继续搜索 return; } // 遍历所有选择 @@ -481,6 +468,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res); + // 停止继续搜索 return; } // 遍历所有选择 @@ -506,6 +494,7 @@ if isSolution(state: state) { // 记录解 recordSolution(state: state, res: &res) + // 停止继续搜索 return } // 遍历所有选择 @@ -537,6 +526,7 @@ if (isSolution(state)) { // 记录解 recordSolution(state, res); + // 停止继续搜索 return; } // 遍历所有选择 @@ -553,7 +543,7 @@ } ``` -下面,我们基于框架代码来解决例题三:状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表。 +接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表。 === "Java" @@ -731,7 +721,28 @@ [class]{}-[func]{backtrack} ``` -相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰嗦,但通用性更好。实际上,**许多回溯问题都可以在该框架下解决**。我们只需根据具体问题来定义 `state` 和 `choices` ,并实现框架中的各个方法。 +根据题意,当找到值为 7 的节点后应该继续搜索,**因此我们需要将记录解之后的 `return` 语句删除**。下图对比了保留或删除 `return` 语句的搜索过程。 + +![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png) + +相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰嗦,但通用性更好。实际上,**许多回溯问题都可以在该框架下解决**。我们只需根据具体问题来定义 `state` 和 `choices` ,并实现框架中的各个方法即可。 + +## 常用术语 + +为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例。 + +| 名词 | 定义 | 例题三 | +| ------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| 解 Solution | 解是满足问题特定条件的答案,可能有一个或多个 | 根节点到节点 $7$ 的满足约束条件的所有路径 | +| 约束条件 Constraint | 约束条件是问题中限制解的可行性的条件,通常用于剪枝 | 路径中不包含节点 $3$ ,只包含一个节点 $7$ | +| 状态 State | 状态表示问题在某一时刻的情况,包括已经做出的选择 | 当前已访问的节点路径,即 `path` 节点列表 | +| 尝试 Attempt | 尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解 | 递归访问左(右)子节点,将节点添加进 `path` ,判断节点的值是否为 $7$ | +| 回退 Backtracking | 回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态 | 当越过叶结点、结束结点访问、遇到值为 $3$ 的节点时终止搜索,函数返回 | +| 剪枝 Pruning | 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 | 当遇到值为 $3$ 的节点时,则终止继续搜索 | + +!!! tip + + 问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。 ## 优势与局限性