You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
hello-algo/chapter_backtracking/backtracking_algorithm.md

1420 lines
44 KiB

2 years ago
---
comments: true
---
1 year ago
# 13.1.   回溯算法
2 years ago
「回溯算法 Backtracking Algorithm」是一种通过穷举来解决问题的方法它的核心思想是从一个初始状态出发暴力搜索所有可能的解决方案当遇到正确的解则将其记录直到找到解或者尝试了所有可能的选择都无法找到解为止。
1 year ago
回溯算法通常采用「深度优先搜索」来遍历解空间。在二叉树章节中,我们提到前序、中序和后序遍历都属于深度优先搜索。接下来,我们利用前序遍历构造一个回溯问题,逐步了解回溯算法的工作原理。
2 years ago
2 years ago
!!! question "例题一"
1 year ago
给定一个二叉树,搜索并记录所有值为 $7$ 的节点,请返回节点列表。
2 years ago
1 year ago
对于此题,我们前序遍历这颗树,并判断当前节点的值是否为 $7$ ,若是则将该节点的值加入到结果列表 `res` 之中。
2 years ago
=== "Java"
2 years ago
```java title="preorder_traversal_i_compact.java"
/* 前序遍历:例题一 */
2 years ago
void preOrder(TreeNode root) {
if (root == null) {
return;
}
if (root.val == 7) {
// 记录解
res.add(root);
}
preOrder(root.left);
preOrder(root.right);
}
```
=== "C++"
2 years ago
```cpp title="preorder_traversal_i_compact.cpp"
/* 前序遍历:例题一 */
2 years ago
void preOrder(TreeNode *root) {
if (root == nullptr) {
return;
}
if (root->val == 7) {
// 记录解
res.push_back(root);
}
preOrder(root->left);
preOrder(root->right);
}
2 years ago
```
=== "Python"
2 years ago
```python title="preorder_traversal_i_compact.py"
1 year ago
def pre_order(root: TreeNode):
2 years ago
"""前序遍历:例题一"""
2 years ago
if root is None:
return
if root.val == 7:
# 记录解
res.append(root)
pre_order(root.left)
pre_order(root.right)
```
=== "Go"
2 years ago
```go title="preorder_traversal_i_compact.go"
2 years ago
/* 前序遍历:例题一 */
func preOrderI(root *TreeNode, res *[]*TreeNode) {
if root == nil {
return
}
if int(root.Val) == 7 {
// 记录解
*res = append(*res, root)
}
preOrderI(root.Left, res)
preOrderI(root.Right, res)
}
2 years ago
```
=== "JavaScript"
2 years ago
```javascript title="preorder_traversal_i_compact.js"
2 years ago
/* 前序遍历:例题一 */
function preOrder(root, res) {
if (root === null) {
return;
}
if (root.val === 7) {
// 记录解
res.push(root);
}
preOrder(root.left, res);
preOrder(root.right, res);
}
2 years ago
```
=== "TypeScript"
2 years ago
```typescript title="preorder_traversal_i_compact.ts"
2 years ago
/* 前序遍历:例题一 */
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);
}
2 years ago
```
=== "C"
2 years ago
```c title="preorder_traversal_i_compact.c"
2 years ago
[class]{}-[func]{preOrder}
```
=== "C#"
2 years ago
```csharp title="preorder_traversal_i_compact.cs"
/* 前序遍历:例题一 */
2 years ago
void preOrder(TreeNode root) {
if (root == null) {
2 years ago
return;
}
2 years ago
if (root.val == 7) {
2 years ago
// 记录解
res.Add(root);
}
preOrder(root.left);
preOrder(root.right);
}
2 years ago
```
=== "Swift"
2 years ago
```swift title="preorder_traversal_i_compact.swift"
2 years ago
/* 前序遍历:例题一 */
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)
}
2 years ago
```
=== "Zig"
2 years ago
```zig title="preorder_traversal_i_compact.zig"
2 years ago
[class]{}-[func]{preOrder}
```
1 year ago
=== "Dart"
```dart title="preorder_traversal_i_compact.dart"
[class]{}-[func]{preOrder}
```
2 years ago
![在前序遍历中搜索节点](backtracking_algorithm.assets/preorder_find_nodes.png)
<p align="center"> Fig. 在前序遍历中搜索节点 </p>
1 year ago
## 13.1.1. &nbsp; 尝试与回退
2 years ago
**之所以称之为回溯算法,是因为该算法在搜索解空间时会采用“尝试”与“回退”的策略**。当算法在搜索过程中遇到某个状态无法继续前进或无法得到满足条件的解时,它会撤销上一步的选择,退回到之前的状态,并尝试其他可能的选择。
对于例题一,访问每个节点都代表一次“尝试”,而越过叶结点或返回父节点的 `return` 则表示“回退”。
1 year ago
值得说明的是,**回退并不仅仅包括函数返回**。为解释这一点,我们对例题一稍作拓展。
2 years ago
2 years ago
!!! question "例题二"
1 year ago
在二叉树中搜索所有值为 $7$ 的节点,**请返回根节点到这些节点的路径**。
2 years ago
1 year ago
在例题一代码的基础上,我们需要借助一个列表 `path` 记录访问过的节点路径。当访问到值为 $7$ 的节点时,则复制 `path` 并添加进结果列表 `res` 。遍历完成后,`res` 中保存的就是所有的解。
2 years ago
=== "Java"
2 years ago
```java title="preorder_traversal_ii_compact.java"
/* 前序遍历:例题二 */
2 years ago
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++"
2 years ago
```cpp title="preorder_traversal_ii_compact.cpp"
/* 前序遍历:例题二 */
2 years ago
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();
}
2 years ago
```
=== "Python"
2 years ago
```python title="preorder_traversal_ii_compact.py"
1 year ago
def pre_order(root: TreeNode):
2 years ago
"""前序遍历:例题二"""
2 years ago
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()
```
=== "Go"
2 years ago
```go title="preorder_traversal_ii_compact.go"
2 years ago
/* 前序遍历:例题二 */
func preOrderII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {
if root == nil {
return
}
// 尝试
*path = append(*path, root)
if int(root.Val) == 7 {
// 记录解
*res = append(*res, *path)
}
preOrderII(root.Left, res, path)
preOrderII(root.Right, res, path)
// 回退
*path = (*path)[:len(*path)-1]
}
2 years ago
```
=== "JavaScript"
2 years ago
```javascript title="preorder_traversal_ii_compact.js"
2 years ago
/* 前序遍历:例题二 */
function preOrder(root, path, res) {
if (root === null) {
return;
}
// 尝试
path.push(root);
if (root.val === 7) {
// 记录解
res.push([...path]);
}
preOrder(root.left, path, res);
preOrder(root.right, path, res);
// 回退
path.pop();
}
2 years ago
```
=== "TypeScript"
2 years ago
```typescript title="preorder_traversal_ii_compact.ts"
2 years ago
/* 前序遍历:例题二 */
function preOrder(
root: TreeNode | null,
path: TreeNode[],
res: TreeNode[][]
): void {
if (root === null) {
return;
}
// 尝试
path.push(root);
if (root.val === 7) {
// 记录解
res.push([...path]);
}
preOrder(root.left, path, res);
preOrder(root.right, path, res);
// 回退
path.pop();
}
2 years ago
```
=== "C"
2 years ago
```c title="preorder_traversal_ii_compact.c"
2 years ago
[class]{}-[func]{preOrder}
```
=== "C#"
2 years ago
```csharp title="preorder_traversal_ii_compact.cs"
/* 前序遍历:例题二 */
2 years ago
void preOrder(TreeNode root) {
if (root == null) {
2 years ago
return;
}
// 尝试
path.Add(root);
2 years ago
if (root.val == 7) {
2 years ago
// 记录解
res.Add(new List<TreeNode>(path));
}
preOrder(root.left);
preOrder(root.right);
// 回退
path.RemoveAt(path.Count - 1);
}
2 years ago
```
=== "Swift"
2 years ago
```swift title="preorder_traversal_ii_compact.swift"
2 years ago
/* 前序遍历:例题二 */
func preOrder(root: TreeNode?) {
guard let root = root else {
return
}
// 尝试
path.append(root)
if root.val == 7 {
// 记录解
res.append(path)
}
preOrder(root: root.left)
preOrder(root: root.right)
// 回退
path.removeLast()
}
2 years ago
```
=== "Zig"
2 years ago
```zig title="preorder_traversal_ii_compact.zig"
2 years ago
[class]{}-[func]{preOrder}
```
1 year ago
=== "Dart"
```dart title="preorder_traversal_ii_compact.dart"
[class]{}-[func]{preOrder}
```
1 year ago
在每次“尝试”中,我们通过将当前节点添加进 `path` 来记录路径;而在“回退”前,我们需要将该节点从 `path` 中弹出,**以恢复本次尝试之前的状态**。
观察该过程,**我们可以将尝试和回退理解为“前进”与“撤销”**,两个操作是互为逆向的。
2 years ago
=== "<1>"
2 years ago
![尝试与回退](backtracking_algorithm.assets/preorder_find_paths_step1.png)
2 years ago
=== "<2>"
![preorder_find_paths_step2](backtracking_algorithm.assets/preorder_find_paths_step2.png)
=== "<3>"
![preorder_find_paths_step3](backtracking_algorithm.assets/preorder_find_paths_step3.png)
=== "<4>"
![preorder_find_paths_step4](backtracking_algorithm.assets/preorder_find_paths_step4.png)
=== "<5>"
![preorder_find_paths_step5](backtracking_algorithm.assets/preorder_find_paths_step5.png)
=== "<6>"
![preorder_find_paths_step6](backtracking_algorithm.assets/preorder_find_paths_step6.png)
=== "<7>"
![preorder_find_paths_step7](backtracking_algorithm.assets/preorder_find_paths_step7.png)
=== "<8>"
![preorder_find_paths_step8](backtracking_algorithm.assets/preorder_find_paths_step8.png)
=== "<9>"
![preorder_find_paths_step9](backtracking_algorithm.assets/preorder_find_paths_step9.png)
=== "<10>"
![preorder_find_paths_step10](backtracking_algorithm.assets/preorder_find_paths_step10.png)
=== "<11>"
![preorder_find_paths_step11](backtracking_algorithm.assets/preorder_find_paths_step11.png)
1 year ago
## 13.1.2. &nbsp; 剪枝
2 years ago
复杂的回溯问题通常包含一个或多个约束条件,**约束条件通常可用于“剪枝”**。
2 years ago
!!! question "例题三"
1 year ago
在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**并要求路径中不包含值为 $3$ 的节点**。
2 years ago
1 year ago
为了满足以上约束条件,**我们需要添加剪枝操作**:在搜索过程中,若遇到值为 $3$ 的节点,则提前返回,停止继续搜索。
2 years ago
=== "Java"
2 years ago
```java title="preorder_traversal_iii_compact.java"
/* 前序遍历:例题三 */
2 years ago
void preOrder(TreeNode root) {
// 剪枝
if (root == null || root.val == 3) {
return;
}
// 尝试
path.add(root);
if (root.val == 7) {
// 记录解
res.add(new ArrayList<>(path));
1 year ago
path.remove(path.size() - 1);
1 year ago
return;
2 years ago
}
preOrder(root.left);
preOrder(root.right);
// 回退
path.remove(path.size() - 1);
}
```
=== "C++"
2 years ago
```cpp title="preorder_traversal_iii_compact.cpp"
/* 前序遍历:例题三 */
2 years ago
void preOrder(TreeNode *root) {
2 years ago
// 剪枝
2 years ago
if (root == nullptr || root->val == 3) {
return;
}
// 尝试
path.push_back(root);
if (root->val == 7) {
// 记录解
res.push_back(path);
1 year ago
path.pop_back();
1 year ago
return;
2 years ago
}
preOrder(root->left);
preOrder(root->right);
// 回退
path.pop_back();
}
2 years ago
```
=== "Python"
2 years ago
```python title="preorder_traversal_iii_compact.py"
1 year ago
def pre_order(root: TreeNode):
2 years ago
"""前序遍历:例题三"""
2 years ago
# 剪枝
if root is None or root.val == 3:
return
# 尝试
path.append(root)
if root.val == 7:
# 记录解
res.append(list(path))
1 year ago
path.pop()
1 year ago
return
2 years ago
pre_order(root.left)
pre_order(root.right)
# 回退
path.pop()
```
=== "Go"
2 years ago
```go title="preorder_traversal_iii_compact.go"
2 years ago
/* 前序遍历:例题三 */
func preOrderIII(root *TreeNode, res *[][]*TreeNode, path *[]*TreeNode) {
// 剪枝
if root == nil || root.Val == 3 {
return
}
// 尝试
*path = append(*path, root)
if int(root.Val) == 7 {
// 记录解
*res = append(*res, *path)
1 year ago
*path = (*path)[:len(*path)-1]
1 year ago
return
2 years ago
}
preOrderIII(root.Left, res, path)
preOrderIII(root.Right, res, path)
// 回退
*path = (*path)[:len(*path)-1]
}
2 years ago
```
=== "JavaScript"
2 years ago
```javascript title="preorder_traversal_iii_compact.js"
2 years ago
/* 前序遍历:例题三 */
function preOrder(root, path, res) {
// 剪枝
if (root === null || root.val === 3) {
return;
}
// 尝试
path.push(root);
if (root.val === 7) {
// 记录解
res.push([...path]);
1 year ago
path.pop();
1 year ago
return;
2 years ago
}
preOrder(root.left, path, res);
preOrder(root.right, path, res);
// 回退
path.pop();
}
2 years ago
```
=== "TypeScript"
2 years ago
```typescript title="preorder_traversal_iii_compact.ts"
2 years ago
/* 前序遍历:例题三 */
function preOrder(
root: TreeNode | null,
path: TreeNode[],
res: TreeNode[][]
): void {
// 剪枝
if (root === null || root.val === 3) {
return;
}
// 尝试
path.push(root);
if (root.val === 7) {
// 记录解
res.push([...path]);
1 year ago
path.pop();
1 year ago
return;
2 years ago
}
preOrder(root.left, path, res);
preOrder(root.right, path, res);
// 回退
path.pop();
}
2 years ago
```
=== "C"
2 years ago
```c title="preorder_traversal_iii_compact.c"
2 years ago
[class]{}-[func]{preOrder}
```
=== "C#"
2 years ago
```csharp title="preorder_traversal_iii_compact.cs"
/* 前序遍历:例题三 */
2 years ago
void preOrder(TreeNode root) {
2 years ago
// 剪枝
2 years ago
if (root == null || root.val == 3) {
2 years ago
return;
}
// 尝试
path.Add(root);
2 years ago
if (root.val == 7) {
2 years ago
// 记录解
res.Add(new List<TreeNode>(path));
1 year ago
path.RemoveAt(path.Count - 1);
1 year ago
return;
2 years ago
}
preOrder(root.left);
preOrder(root.right);
// 回退
path.RemoveAt(path.Count - 1);
}
2 years ago
```
=== "Swift"
2 years ago
```swift title="preorder_traversal_iii_compact.swift"
2 years ago
/* 前序遍历:例题三 */
func preOrder(root: TreeNode?) {
// 剪枝
guard let root = root, root.val != 3 else {
return
}
// 尝试
path.append(root)
if root.val == 7 {
// 记录解
res.append(path)
1 year ago
path.removeLast()
1 year ago
return
2 years ago
}
preOrder(root: root.left)
preOrder(root: root.right)
// 回退
path.removeLast()
}
2 years ago
```
=== "Zig"
2 years ago
```zig title="preorder_traversal_iii_compact.zig"
2 years ago
[class]{}-[func]{preOrder}
```
1 year ago
=== "Dart"
```dart title="preorder_traversal_iii_compact.dart"
[class]{}-[func]{preOrder}
```
1 year ago
剪枝是一个非常形象的名词。在搜索过程中,**我们“剪掉”了不满足约束条件的搜索分支**,避免许多无意义的尝试,从而实现搜索效率的提高。
2 years ago
![根据约束条件剪枝](backtracking_algorithm.assets/preorder_find_constrained_paths.png)
<p align="center"> Fig. 根据约束条件剪枝 </p>
1 year ago
## 13.1.3. &nbsp; 框架代码
2 years ago
1 year ago
接下来,我们尝试将回溯的“尝试、回退、剪枝”的主体框架提炼出来,提升代码的通用性。
2 years ago
1 year ago
在以下框架代码中,`state` 表示问题的当前状态,`choices` 表示当前状态下可以做出的选择。
2 years ago
2 years ago
=== "Java"
```java title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice> choices, List<State> res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
1 year ago
// 停止继续搜索
2 years ago
return;
}
// 遍历所有选择
for (Choice choice : choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "C++"
```cpp title=""
/* 回溯算法框架 */
void backtrack(State *state, vector<Choice *> &choices, vector<State *> &res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
1 year ago
// 停止继续搜索
2 years ago
return;
}
// 遍历所有选择
for (Choice choice : choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "Python"
```python title=""
1 year ago
def backtrack(state: State, choices: list[choice], res: list[state]):
2 years ago
"""回溯算法框架"""
# 判断是否为解
if is_solution(state):
# 记录解
record_solution(state, res)
1 year ago
# 停止继续搜索
2 years ago
return
# 遍历所有选择
for choice in choices:
# 剪枝:判断选择是否合法
if is_valid(state, choice):
# 尝试:做出选择,更新状态
make_choice(state, choice)
backtrack(state, choices, res)
# 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice)
```
=== "Go"
```go title=""
/* 回溯算法框架 */
func backtrack(state *State, choices []Choice, res *[]State) {
// 判断是否为解
if isSolution(state) {
// 记录解
recordSolution(state, res)
1 year ago
// 停止继续搜索
2 years ago
return
}
// 遍历所有选择
for _, choice := range choices {
// 剪枝:判断选择是否合法
if isValid(state, choice) {
// 尝试:做出选择,更新状态
makeChoice(state, choice)
backtrack(state, choices, res)
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice)
}
}
}
```
=== "JavaScript"
```javascript title=""
/* 回溯算法框架 */
function backtrack(state, choices, res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
1 year ago
// 停止继续搜索
2 years ago
return;
}
// 遍历所有选择
for (let choice of choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "TypeScript"
```typescript title=""
/* 回溯算法框架 */
function backtrack(state: State, choices: Choice[], res: State[]): void {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
1 year ago
// 停止继续搜索
2 years ago
return;
}
// 遍历所有选择
for (let choice of choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "C"
```c title=""
/* 回溯算法框架 */
void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res, numRes);
1 year ago
// 停止继续搜索
2 years ago
return;
}
// 遍历所有选择
for (int i = 0; i < numChoices; i++) {
// 剪枝:判断选择是否合法
if (isValid(state, &choices[i])) {
// 尝试:做出选择,更新状态
makeChoice(state, &choices[i]);
backtrack(state, choices, numChoices, res, numRes);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, &choices[i]);
}
}
}
```
=== "C#"
```csharp title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice> choices, List<State> res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
1 year ago
// 停止继续搜索
2 years ago
return;
}
// 遍历所有选择
foreach (Choice choice in choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "Swift"
```swift title=""
/* 回溯算法框架 */
func backtrack(state: inout State, choices: [Choice], res: inout [State]) {
// 判断是否为解
if isSolution(state: state) {
// 记录解
recordSolution(state: state, res: &res)
1 year ago
// 停止继续搜索
2 years ago
return
}
// 遍历所有选择
for choice in choices {
// 剪枝:判断选择是否合法
if isValid(state: state, choice: choice) {
// 尝试:做出选择,更新状态
makeChoice(state: &state, choice: choice)
backtrack(state: &state, choices: choices, res: &res)
// 回退:撤销选择,恢复到之前的状态
undoChoice(state: &state, choice: choice)
}
}
}
```
=== "Zig"
```zig title=""
```
2 years ago
1 year ago
=== "Dart"
```dart title=""
1 year ago
/* 回溯算法框架 */
void backtrack(State state, List<Choice>, List<State> res) {
// 判断是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
1 year ago
// 停止继续搜索
1 year ago
return;
}
// 遍历所有选择
for (Choice choice in choices) {
// 剪枝:判断选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
backtrack(state, choices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
1 year ago
```
1 year ago
接下来,我们基于框架代码来解决例题三。状态 `state` 为节点遍历路径,选择 `choices` 为当前节点的左子节点和右子节点,结果 `res` 是路径列表。
2 years ago
=== "Java"
2 years ago
```java title="preorder_traversal_iii_template.java"
2 years ago
/* 判断当前状态是否为解 */
boolean isSolution(List<TreeNode> state) {
return !state.isEmpty() && state.get(state.size() - 1).val == 7;
}
/* 记录解 */
void recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {
res.add(new ArrayList<>(state));
}
/* 判断在当前状态下,该选择是否合法 */
boolean isValid(List<TreeNode> state, TreeNode choice) {
return choice != null && choice.val != 3;
}
/* 更新状态 */
void makeChoice(List<TreeNode> state, TreeNode choice) {
state.add(choice);
}
/* 恢复状态 */
void undoChoice(List<TreeNode> state, TreeNode choice) {
state.remove(state.size() - 1);
}
2 years ago
/* 回溯算法:例题三 */
2 years ago
void backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {
// 检查是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
}
// 遍历所有选择
for (TreeNode choice : choices) {
// 剪枝:检查选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
2 years ago
// 进行下一轮选择
2 years ago
backtrack(state, Arrays.asList(choice.left, choice.right), res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
```
=== "C++"
2 years ago
```cpp title="preorder_traversal_iii_template.cpp"
2 years ago
/* 判断当前状态是否为解 */
bool isSolution(vector<TreeNode *> &state) {
return !state.empty() && state.back()->val == 7;
}
2 years ago
2 years ago
/* 记录解 */
void recordSolution(vector<TreeNode *> &state, vector<vector<TreeNode *>> &res) {
res.push_back(state);
}
2 years ago
2 years ago
/* 判断在当前状态下,该选择是否合法 */
bool isValid(vector<TreeNode *> &state, TreeNode *choice) {
return choice != nullptr && choice->val != 3;
}
2 years ago
2 years ago
/* 更新状态 */
void makeChoice(vector<TreeNode *> &state, TreeNode *choice) {
state.push_back(choice);
}
2 years ago
2 years ago
/* 恢复状态 */
void undoChoice(vector<TreeNode *> &state, TreeNode *choice) {
state.pop_back();
}
2 years ago
2 years ago
/* 回溯算法:例题三 */
2 years ago
void backtrack(vector<TreeNode *> &state, vector<TreeNode *> &choices, vector<vector<TreeNode *>> &res) {
// 检查是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
}
// 遍历所有选择
for (TreeNode *choice : choices) {
// 剪枝:检查选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
2 years ago
// 进行下一轮选择
2 years ago
vector<TreeNode *> nextChoices{choice->left, choice->right};
backtrack(state, nextChoices, res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
2 years ago
```
=== "Python"
2 years ago
```python title="preorder_traversal_iii_template.py"
2 years ago
def is_solution(state: list[TreeNode]) -> bool:
"""判断当前状态是否为解"""
return state and state[-1].val == 7
def record_solution(state: list[TreeNode], res: list[list[TreeNode]]):
"""记录解"""
res.append(list(state))
def is_valid(state: list[TreeNode], choice: TreeNode) -> bool:
"""判断在当前状态下,该选择是否合法"""
return choice is not None and choice.val != 3
def make_choice(state: list[TreeNode], choice: TreeNode):
"""更新状态"""
state.append(choice)
def undo_choice(state: list[TreeNode], choice: TreeNode):
"""恢复状态"""
state.pop()
2 years ago
def backtrack(
state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]
):
2 years ago
"""回溯算法:例题三"""
2 years ago
# 检查是否为解
if is_solution(state):
# 记录解
record_solution(state, res)
# 遍历所有选择
for choice in choices:
# 剪枝:检查选择是否合法
if is_valid(state, choice):
# 尝试:做出选择,更新状态
make_choice(state, choice)
2 years ago
# 进行下一轮选择
2 years ago
backtrack(state, [choice.left, choice.right], res)
# 回退:撤销选择,恢复到之前的状态
undo_choice(state, choice)
```
=== "Go"
2 years ago
```go title="preorder_traversal_iii_template.go"
2 years ago
/* 判断当前状态是否为解 */
func isSolution(state *[]*TreeNode) bool {
return len(*state) != 0 && (*state)[len(*state)-1].Val == 7
}
2 years ago
2 years ago
/* 记录解 */
func recordSolution(state *[]*TreeNode, res *[][]*TreeNode) {
*res = append(*res, *state)
}
2 years ago
2 years ago
/* 判断在当前状态下,该选择是否合法 */
func isValid(state *[]*TreeNode, choice *TreeNode) bool {
return choice != nil && choice.Val != 3
}
2 years ago
2 years ago
/* 更新状态 */
func makeChoice(state *[]*TreeNode, choice *TreeNode) {
*state = append(*state, choice)
}
2 years ago
2 years ago
/* 恢复状态 */
func undoChoice(state *[]*TreeNode, choice *TreeNode) {
*state = (*state)[:len(*state)-1]
}
2 years ago
2 years ago
/* 回溯算法:例题三 */
func backtrackIII(state *[]*TreeNode, choices *[]*TreeNode, res *[][]*TreeNode) {
// 检查是否为解
if isSolution(state) {
// 记录解
recordSolution(state, res)
}
// 遍历所有选择
for _, choice := range *choices {
// 剪枝:检查选择是否合法
if isValid(state, choice) {
// 尝试:做出选择,更新状态
makeChoice(state, choice)
// 进行下一轮选择
temp := make([]*TreeNode, 0)
temp = append(temp, choice.Left, choice.Right)
backtrackIII(state, &temp, res)
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice)
}
}
}
2 years ago
```
=== "JavaScript"
2 years ago
```javascript title="preorder_traversal_iii_template.js"
2 years ago
/* 判断当前状态是否为解 */
function isSolution(state) {
return state && state[state.length - 1]?.val === 7;
}
2 years ago
2 years ago
/* 记录解 */
function recordSolution(state, res) {
res.push([...state]);
}
2 years ago
2 years ago
/* 判断在当前状态下,该选择是否合法 */
function isValid(state, choice) {
return choice !== null && choice.val !== 3;
}
2 years ago
2 years ago
/* 更新状态 */
function makeChoice(state, choice) {
state.push(choice);
}
2 years ago
2 years ago
/* 恢复状态 */
function undoChoice(state) {
state.pop();
}
2 years ago
2 years ago
/* 回溯算法:例题三 */
function backtrack(state, choices, res) {
// 检查是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
}
// 遍历所有选择
for (const choice of choices) {
// 剪枝:检查选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
// 进行下一轮选择
backtrack(state, [choice.left, choice.right], res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state);
}
}
}
2 years ago
```
=== "TypeScript"
2 years ago
```typescript title="preorder_traversal_iii_template.ts"
2 years ago
/* 判断当前状态是否为解 */
function isSolution(state: TreeNode[]): boolean {
return state && state[state.length - 1]?.val === 7;
}
2 years ago
2 years ago
/* 记录解 */
function recordSolution(state: TreeNode[], res: TreeNode[][]): void {
res.push([...state]);
}
2 years ago
2 years ago
/* 判断在当前状态下,该选择是否合法 */
function isValid(state: TreeNode[], choice: TreeNode): boolean {
return choice !== null && choice.val !== 3;
}
2 years ago
2 years ago
/* 更新状态 */
function makeChoice(state: TreeNode[], choice: TreeNode): void {
state.push(choice);
}
2 years ago
2 years ago
/* 恢复状态 */
function undoChoice(state: TreeNode[]): void {
state.pop();
}
2 years ago
2 years ago
/* 回溯算法:例题三 */
function backtrack(
state: TreeNode[],
choices: TreeNode[],
res: TreeNode[][]
): void {
// 检查是否为解
if (isSolution(state)) {
// 记录解
recordSolution(state, res);
}
// 遍历所有选择
for (const choice of choices) {
// 剪枝:检查选择是否合法
if (isValid(state, choice)) {
// 尝试:做出选择,更新状态
makeChoice(state, choice);
// 进行下一轮选择
backtrack(state, [choice.left, choice.right], res);
// 回退:撤销选择,恢复到之前的状态
undoChoice(state);
}
}
}
2 years ago
```
=== "C"
2 years ago
```c title="preorder_traversal_iii_template.c"
2 years ago
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "C#"
2 years ago
```csharp title="preorder_traversal_iii_template.cs"
2 years ago
/* 判断当前状态是否为解 */
2 years ago
bool isSolution(List<TreeNode> state) {
2 years ago
return state.Count != 0 && state[^1].val == 7;
}
2 years ago
2 years ago
/* 记录解 */
2 years ago
void recordSolution(List<TreeNode> state, List<List<TreeNode>> res) {
2 years ago
res.Add(new List<TreeNode>(state));
}
2 years ago
2 years ago
/* 判断在当前状态下,该选择是否合法 */
2 years ago
bool isValid(List<TreeNode> state, TreeNode choice) {
2 years ago
return choice != null && choice.val != 3;
}
2 years ago
2 years ago
/* 更新状态 */
2 years ago
void makeChoice(List<TreeNode> state, TreeNode choice) {
2 years ago
state.Add(choice);
}
2 years ago
2 years ago
/* 恢复状态 */
2 years ago
void undoChoice(List<TreeNode> state, TreeNode choice) {
2 years ago
state.RemoveAt(state.Count - 1);
}
2 years ago
2 years ago
/* 回溯算法:例题三 */
2 years ago
void backtrack(List<TreeNode> state, List<TreeNode> choices, List<List<TreeNode>> res) {
2 years ago
// 检查是否为解
2 years ago
if (isSolution(state)) {
2 years ago
// 记录解
recordSolution(state, res);
}
// 遍历所有选择
2 years ago
foreach (TreeNode choice in choices) {
2 years ago
// 剪枝:检查选择是否合法
2 years ago
if (isValid(state, choice)) {
2 years ago
// 尝试:做出选择,更新状态
makeChoice(state, choice);
2 years ago
// 进行下一轮选择
backtrack(state, new List<TreeNode> { choice.left, choice.right }, res);
2 years ago
// 回退:撤销选择,恢复到之前的状态
undoChoice(state, choice);
}
}
}
2 years ago
```
=== "Swift"
2 years ago
```swift title="preorder_traversal_iii_template.swift"
2 years ago
/* 判断当前状态是否为解 */
func isSolution(state: [TreeNode]) -> Bool {
!state.isEmpty && state.last!.val == 7
}
2 years ago
2 years ago
/* 记录解 */
func recordSolution(state: [TreeNode], res: inout [[TreeNode]]) {
res.append(state)
}
2 years ago
2 years ago
/* 判断在当前状态下,该选择是否合法 */
func isValid(state: [TreeNode], choice: TreeNode?) -> Bool {
choice != nil && choice!.val != 3
}
2 years ago
2 years ago
/* 更新状态 */
func makeChoice(state: inout [TreeNode], choice: TreeNode) {
state.append(choice)
}
2 years ago
2 years ago
/* 恢复状态 */
func undoChoice(state: inout [TreeNode], choice: TreeNode) {
state.removeLast()
}
2 years ago
2 years ago
/* 回溯算法:例题三 */
func backtrack(state: inout [TreeNode], choices: [TreeNode], res: inout [[TreeNode]]) {
// 检查是否为解
if isSolution(state: state) {
recordSolution(state: state, res: &res)
}
// 遍历所有选择
for choice in choices {
// 剪枝:检查选择是否合法
if isValid(state: state, choice: choice) {
// 尝试:做出选择,更新状态
makeChoice(state: &state, choice: choice)
// 进行下一轮选择
backtrack(state: &state, choices: [choice.left, choice.right].compactMap { $0 }, res: &res)
// 回退:撤销选择,恢复到之前的状态
undoChoice(state: &state, choice: choice)
}
}
}
2 years ago
```
=== "Zig"
2 years ago
```zig title="preorder_traversal_iii_template.zig"
2 years ago
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
1 year ago
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
=== "Dart"
```dart title="preorder_traversal_iii_template.dart"
[class]{}-[func]{isSolution}
[class]{}-[func]{recordSolution}
2 years ago
[class]{}-[func]{isValid}
[class]{}-[func]{makeChoice}
[class]{}-[func]{undoChoice}
[class]{}-[func]{backtrack}
```
1 year ago
根据题意,当找到值为 7 的节点后应该继续搜索,**因此我们需要将记录解之后的 `return` 语句删除**。下图对比了保留或删除 `return` 语句的搜索过程。
![保留与删除 return 的搜索过程对比](backtracking_algorithm.assets/backtrack_remove_return_or_not.png)
<p align="center"> Fig. 保留与删除 return 的搜索过程对比 </p>
相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰嗦,但通用性更好。实际上,**许多回溯问题都可以在该框架下解决**。我们只需根据具体问题来定义 `state``choices` ,并实现框架中的各个方法即可。
## 13.1.4. &nbsp; 常用术语
为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例。
| 名词 | 定义 | 例题三 |
| ------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| 解 Solution | 解是满足问题特定条件的答案,可能有一个或多个 | 根节点到节点 $7$ 的满足约束条件的所有路径 |
| 约束条件 Constraint | 约束条件是问题中限制解的可行性的条件,通常用于剪枝 | 路径中不包含节点 $3$ ,只包含一个节点 $7$ |
| 状态 State | 状态表示问题在某一时刻的情况,包括已经做出的选择 | 当前已访问的节点路径,即 `path` 节点列表 |
| 尝试 Attempt | 尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解 | 递归访问左(右)子节点,将节点添加进 `path` ,判断节点的值是否为 $7$ |
| 回退 Backtracking | 回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态 | 当越过叶结点、结束结点访问、遇到值为 $3$ 的节点时终止搜索,函数返回 |
| 剪枝 Pruning | 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 | 当遇到值为 $3$ 的节点时,则终止继续搜索 |
!!! tip
问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。
2 years ago
1 year ago
## 13.1.5. &nbsp; 优势与局限性
1 year ago
回溯算法本质上是一种深度优先搜索算法,它尝试所有可能的解决方案直到找到满足条件的解。这种方法的优势在于它能够找到所有可能的解决方案,而且在合理的剪枝操作下,具有很高的效率。
然而,在处理大规模或者复杂问题时,**回溯算法的运行效率可能难以接受**。
1 year ago
- **时间**:回溯算法通常需要遍历状态空间的所有可能,时间复杂度可以达到指数阶或阶乘阶。
- **空间**:在递归调用中需要保存当前的状态(例如路径、用于剪枝的辅助变量等),当深度很大时,空间需求可能会变得很大。
1 year ago
1 year ago
即便如此,**回溯算法仍然是某些搜索问题和约束满足问题的最佳解决方案**。对于这些问题,由于无法预测哪些选择可生成有效的解,因此我们必须对所有可能的选择进行遍历。在这种情况下,**关键是如何进行效率优化**,常见方法有:
1 year ago
1 year ago
- **剪枝**:避免搜索那些肯定不会产生解的路径,从而节省时间和空间。
- **启发式搜索**:在搜索过程中引入一些策略或者估计值,从而优先搜索最有可能产生有效解的路径。
1 year ago
1 year ago
## 13.1.6. &nbsp; 回溯典型例题
2 years ago
1 year ago
回溯算法可用于解决许多搜索问题、约束满足问题和组合优化问题。
2 years ago
**搜索问题**:这类问题的目标是找到满足特定条件的解决方案。
- 全排列问题:给定一个集合,求出其所有可能的排列组合。
- 子集和问题:给定一个集合和一个目标和,找到集合中所有和为目标和的子集。
- 汉诺塔问题:给定三个柱子和一系列大小不同的圆盘,要求将所有圆盘从一个柱子移动到另一个柱子,每次只能移动一个圆盘,且不能将大圆盘放在小圆盘上。
**约束满足问题**:这类问题的目标是找到满足所有约束条件的解。
- $n$ 皇后:在 $n \times n$ 的棋盘上放置 $n$ 个皇后,使得它们互不攻击。
- 数独:在 $9 \times 9$ 的网格中填入数字 $1$ ~ $9$ ,使得每行、每列和每个 $3 \times 3$ 子网格中的数字不重复。
- 图着色问题:给定一个无向图,用最少的颜色给图的每个顶点着色,使得相邻顶点颜色不同。
**组合优化问题**:这类问题的目标是在一个组合空间中找到满足某些条件的最优解。
- 0-1 背包问题:给定一组物品和一个背包,每个物品有一定的价值和重量,要求在背包容量限制内,选择物品使得总价值最大。
- 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。
- 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。
1 year ago
请注意,对于许多组合优化问题,回溯都不是最优解决方案,例如:
- 0-1 背包问题通常使用动态规划解决,以达到更高的时间效率;
- 旅行商是一个著名的 NP-Hard 问题,常用解法有遗传算法和蚁群算法等;
- 最大团问题是图论中的一个经典问题,可用贪心等启发式算法来解决;