Update preorder_traversal_iii.

This commit is contained in:
krahets 2023-07-25 16:39:38 +08:00
parent 90af225dae
commit b067016bfa
20 changed files with 48 additions and 56 deletions

View file

@ -37,7 +37,6 @@ void backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<ve
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
return;
}
// 遍历所有选择
for (TreeNode *choice : choices) {

View file

@ -41,7 +41,7 @@ public class preorder_traversal_iii_compact {
res = new List<List<TreeNode>>();
preOrder(root);
Console.WriteLine("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点");
Console.WriteLine("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点");
foreach (List<TreeNode> path in res) {
PrintUtil.PrintList(path.Select(p => p.val).ToList());
}

View file

@ -38,7 +38,6 @@ public class preorder_traversal_iii_template {
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
return;
}
// 遍历所有选择
foreach (TreeNode choice in choices) {

View file

@ -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)

View file

@ -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<TreeNode> path : res) {
List<Integer> vals = new ArrayList<>();
for (TreeNode node : path) {

View file

@ -41,7 +41,6 @@ public class preorder_traversal_iii_template {
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
return;
}
// 遍历所有选择
for (TreeNode choice : choices) {

View file

@ -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));
});

View file

@ -38,7 +38,6 @@ function backtrack(state, choices, res) {
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
return;
}
// 遍历所有选择
for (const choice of choices) {

View file

@ -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])

View file

@ -43,7 +43,6 @@ def backtrack(
if is_solution(state):
# 记录解
record_solution(state, res)
return
# 遍历所有选择
for choice in choices:
# 剪枝:检查选择是否合法

View file

@ -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,

View file

@ -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

View file

@ -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 {

View file

@ -40,7 +40,6 @@ fn backtrack(state: &mut Vec<Rc<RefCell<TreeNode>>>, choices: &mut Vec<Rc<RefCel
if is_solution(state) {
// 记录解
record_solution(state, res);
return;
}
// 遍历所有选择
for choice in choices {

View file

@ -42,7 +42,7 @@ enum PreorderTraversalIIICompact {
res = []
preOrder(root: root)
print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点")
print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点")
for path in res {
var vals: [Int] = []
for node in path {

View file

@ -36,7 +36,6 @@ func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNo
//
if isSolution(state: state) {
recordSolution(state: state, res: &res)
return
}
//
for choice in choices {
@ -65,7 +64,7 @@ enum PreorderTraversalIIITemplate {
var res: [[TreeNode]] = []
backtrack(state: &state, choices: [root].compactMap { $0 }, res: &res)
print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点,仅包含一个值为 7 的节点")
print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点")
for path in res {
var vals: [Int] = []
for node in path {

View file

@ -42,7 +42,7 @@ const path: TreeNode[] = [];
const res: TreeNode[][] = [];
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));
});

View file

@ -43,7 +43,6 @@ function backtrack(
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
return;
}
// 遍历所有选择
for (const choice of choices) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

View file

@ -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
问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。
## 优势与局限性