Algorithm and Data Structure Interview Guide - Detailed Explanation of Backtracking Algorithm (2)

pruning

Complex backtracking problems often contain one or more constraints, and constraints can often be used for "pruning . "

!!! question "Example question three"

在二叉树中搜索所有值为 $7$ 的节点,请返回根节点到这些节点的路径,**并要求路径中不包含值为 $3$ 的节点**。

In order to satisfy the above constraints, we need to add a pruning operation : During the search process, if the value is 3 33 node, return early and stop searching.

=== “Python”

```python title="preorder_traversal_iii_compact.py"
[class]{}-[func]{pre_order}
```

=== “C++”

```cpp title="preorder_traversal_iii_compact.cpp"
[class]{}-[func]{preOrder}
```

=== “Java”

```java title="preorder_traversal_iii_compact.java"
[class]{preorder_traversal_iii_compact}-[func]{preOrder}
```

=== “C#”

```csharp title="preorder_traversal_iii_compact.cs"
[class]{preorder_traversal_iii_compact}-[func]{preOrder}
```

=== “Go”

```go title="preorder_traversal_iii_compact.go"
[class]{}-[func]{preOrderIII}
```

=== “Swift”

```swift title="preorder_traversal_iii_compact.swift"
[class]{}-[func]{preOrder}
```

=== “JS”

```javascript title="preorder_traversal_iii_compact.js"
[class]{}-[func]{preOrder}
```

=== “TS”

```typescript title="preorder_traversal_iii_compact.ts"
[class]{}-[func]{preOrder}
```

=== “Dart”

```dart title="preorder_traversal_iii_compact.dart"
[class]{}-[func]{preOrder}
```

=== “Rust”

```rust title="preorder_traversal_iii_compact.rs"
[class]{}-[func]{pre_order}
```

=== “C”

```c title="preorder_traversal_iii_compact.c"
[class]{}-[func]{preOrder}
```

=== “Zig”

```zig title="preorder_traversal_iii_compact.zig"
[class]{}-[func]{preOrder}
```

Pruning is a very vivid term. As shown in the figure below, during the search process, we "cut off" search branches that do not meet the constraints to avoid many meaningless attempts, thereby improving search efficiency.

Insert image description here

Framework code

Next, we try to extract the main framework of "try, rollback, and prune" of backtracking to improve the versatility of the code.

In the following frame code, stateit represents the current status of the problem and choicesthe choices that can be made in the current status.

=== “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<Choice *> &choices, vector<State *> &res) {
    // 判断是否为解
    if (isSolution(state)) {
        // 记录解
        recordSolution(state, res);
        // 停止继续搜索
        return;
    }
    // 遍历所有选择
    for (Choice choice : choices) {
        // 剪枝:判断选择是否合法
        if (isValid(state, choice)) {
            // 尝试:做出选择,更新状态
            makeChoice(state, choice);
            backtrack(state, choices, res);
            // 回退:撤销选择,恢复到之前的状态
            undoChoice(state, choice);
        }
    }
}
```

=== “Java”

```java title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice> choices, List<State> res) {
    // 判断是否为解
    if (isSolution(state)) {
        // 记录解
        recordSolution(state, res);
        // 停止继续搜索
        return;
    }
    // 遍历所有选择
    for (Choice choice : choices) {
        // 剪枝:判断选择是否合法
        if (isValid(state, choice)) {
            // 尝试:做出选择,更新状态
            makeChoice(state, choice);
            backtrack(state, choices, res);
            // 回退:撤销选择,恢复到之前的状态
            undoChoice(state, choice);
        }
    }
}
```

=== “C#”

```csharp title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice> choices, List<State> res) {
    // 判断是否为解
    if (isSolution(state)) {
        // 记录解
        recordSolution(state, res);
        // 停止继续搜索
        return;
    }
    // 遍历所有选择
    foreach (Choice choice in choices) {
        // 剪枝:判断选择是否合法
        if (isValid(state, choice)) {
            // 尝试:做出选择,更新状态
            makeChoice(state, choice);
            backtrack(state, choices, res);
            // 回退:撤销选择,恢复到之前的状态
            undoChoice(state, choice);
        }
    }
}
```

=== “Go”

```go title=""
/* 回溯算法框架 */
func backtrack(state *State, choices []Choice, res *[]State) {
    // 判断是否为解
    if isSolution(state) {
        // 记录解
        recordSolution(state, res)
        // 停止继续搜索
        return
    }
    // 遍历所有选择
    for _, choice := range 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)
        // 停止继续搜索
        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)
        }
    }
}
```

=== “JS”

```javascript title=""
/* 回溯算法框架 */
function backtrack(state, choices, res) {
    // 判断是否为解
    if (isSolution(state)) {
        // 记录解
        recordSolution(state, res);
        // 停止继续搜索
        return;
    }
    // 遍历所有选择
    for (let choice of choices) {
        // 剪枝:判断选择是否合法
        if (isValid(state, choice)) {
            // 尝试:做出选择,更新状态
            makeChoice(state, choice);
            backtrack(state, choices, res);
            // 回退:撤销选择,恢复到之前的状态
            undoChoice(state, choice);
        }
    }
}
```

=== “TS”

```typescript title=""
/* 回溯算法框架 */
function backtrack(state: State, choices: Choice[], res: State[]): void {
    // 判断是否为解
    if (isSolution(state)) {
        // 记录解
        recordSolution(state, res);
        // 停止继续搜索
        return;
    }
    // 遍历所有选择
    for (let choice of choices) {
        // 剪枝:判断选择是否合法
        if (isValid(state, choice)) {
            // 尝试:做出选择,更新状态
            makeChoice(state, choice);
            backtrack(state, choices, res);
            // 回退:撤销选择,恢复到之前的状态
            undoChoice(state, choice);
        }
    }
}
```

=== “Dart”

```dart title=""
/* 回溯算法框架 */
void backtrack(State state, List<Choice>, List<State> res) {
  // 判断是否为解
  if (isSolution(state)) {
    // 记录解
    recordSolution(state, res);
    // 停止继续搜索
    return;
  }
  // 遍历所有选择
  for (Choice choice in choices) {
    // 剪枝:判断选择是否合法
    if (isValid(state, choice)) {
      // 尝试:做出选择,更新状态
      makeChoice(state, choice);
      backtrack(state, choices, res);
      // 回退:撤销选择,恢复到之前的状态
      undoChoice(state, choice);
    }
  }
}
```

=== “Rust”

```rust title=""

```

=== “C”

```c title=""
/* 回溯算法框架 */
void backtrack(State *state, Choice *choices, int numChoices, State *res, int numRes) {
    // 判断是否为解
    if (isSolution(state)) {
        // 记录解
        recordSolution(state, res, numRes);
        // 停止继续搜索
        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]);
        }
    }
}
```

=== “Zig”

```zig title=""

```

Next, we solve example problem 3 based on the framework code. The state statetraverses the path for the node, selects choicesthe left child node and right child node of the current node, and the result resis a path list.

=== “Python”

```python title="preorder_traversal_iii_template.py"
[class]{}-[func]{is_solution}

[class]{}-[func]{record_solution}

[class]{}-[func]{is_valid}

[class]{}-[func]{make_choice}

[class]{}-[func]{undo_choice}

[class]{}-[func]{backtrack}
```

=== “C++”

```cpp title="preorder_traversal_iii_template.cpp"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrack}
```

=== “Java”

```java title="preorder_traversal_iii_template.java"
[class]{preorder_traversal_iii_template}-[func]{isSolution}

[class]{preorder_traversal_iii_template}-[func]{recordSolution}

[class]{preorder_traversal_iii_template}-[func]{isValid}

[class]{preorder_traversal_iii_template}-[func]{makeChoice}

[class]{preorder_traversal_iii_template}-[func]{undoChoice}

[class]{preorder_traversal_iii_template}-[func]{backtrack}
```

=== “C#”

```csharp title="preorder_traversal_iii_template.cs"
[class]{preorder_traversal_iii_template}-[func]{isSolution}

[class]{preorder_traversal_iii_template}-[func]{recordSolution}

[class]{preorder_traversal_iii_template}-[func]{isValid}

[class]{preorder_traversal_iii_template}-[func]{makeChoice}

[class]{preorder_traversal_iii_template}-[func]{undoChoice}

[class]{preorder_traversal_iii_template}-[func]{backtrack}
```

=== “Go”

```go title="preorder_traversal_iii_template.go"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrackIII}
```

=== “Swift”

```swift title="preorder_traversal_iii_template.swift"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrack}
```

=== “JS”

```javascript title="preorder_traversal_iii_template.js"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrack}
```

=== “TS”

```typescript title="preorder_traversal_iii_template.ts"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[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}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrack}
```

=== “Rust”

```rust title="preorder_traversal_iii_template.rs"
[class]{}-[func]{is_solution}

[class]{}-[func]{record_solution}

[class]{}-[func]{is_valid}

[class]{}-[func]{make_choice}

[class]{}-[func]{undo_choice}

[class]{}-[func]{backtrack}
```

=== “C”

```c title="preorder_traversal_iii_template.c"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrack}
```

=== “Zig”

```zig title="preorder_traversal_iii_template.zig"
[class]{}-[func]{isSolution}

[class]{}-[func]{recordSolution}

[class]{}-[func]{isValid}

[class]{}-[func]{makeChoice}

[class]{}-[func]{undoChoice}

[class]{}-[func]{backtrack}
```

According to the meaning of the question, we found the value 7 7The search should continue after the node 7 , so the statements after the record solution need to bereturndeleted. The following figure comparesreturnthe search process for retaining or deleting statements.

Insert image description here

Compared with the code implementation based on preorder traversal, the code implementation based on the backtracking algorithm framework seems verbose, but it is more versatile. In fact, many backtracking problems can be solved under this framework . stateWe only need to define and according to the specific problem choicesand implement each method in the framework.

Common terms

In order to analyze algorithmic issues more clearly, we summarize the meanings of commonly used terms in backtracking algorithms and give corresponding examples compared to Example 3.

Table Common backtracking algorithm terms

noun definition Example three
Solution A solution is an answer that satisfies specific conditions of the problem, which may be one or more Root node to node 7 77. All paths that satisfy the constraints
Constraint Constraints are conditions in a problem that limit the feasibility of a solution, usually used for pruning Node 3 3 is not included in the path3
StateState The status represents the situation of the problem at a certain moment, including the choices that have been made The currently visited node path, that is, paththe node list
TryAttempt Trying is the process of exploring the solution space based on available choices, including making a choice, updating the state, and checking whether it is a solution Recursively access the left (right) child node, add the node path, and determine whether the value of the node is 7 77
Backtracking Rollback means to cancel the previous choice and return to the previous state when encountering a state that does not satisfy the constraints. When crossing the leaf node, ending the node access, the value encountered is 3 3The search is terminated when node 3 is reached, and the function returns
Pruning Pruning is a method to avoid meaningless search paths based on problem characteristics and constraints, which can improve search efficiency. When encountering a value of 3 3When the node reaches 3 , the search will be terminated.

!!! tip

问题、解、状态等概念是通用的,在分治、回溯、动态规划、贪心等算法中都有涉及。

Advantages and limitations

The backtracking algorithm is essentially a depth-first search algorithm that tries all possible solutions until it finds one that satisfies the conditions. The advantage of this method is that it can find all possible solutions and is very efficient under reasonable pruning operations.

However, when dealing with large-scale or complex problems, the efficiency of the backtracking algorithm may be unacceptable .

  • Time : Backtracking algorithms usually need to traverse all possibilities of the state space, and the time complexity can reach exponential or factorial order.
  • Space : The current state (such as paths, auxiliary variables for pruning, etc.) needs to be saved in recursive calls. When the depth is large, the space requirements may become large.

Even so, backtracking algorithms are still the best solution to some search problems and constraint satisfaction problems . For these problems, since it is impossible to predict which choices will produce a valid solution, we must iterate through all possible choices. In this case, the key is how to optimize efficiency . There are two common efficiency optimization methods.

  • Pruning : Save time and space by avoiding searching for paths that will definitely not yield a solution.
  • Heuristic search : Introducing some strategies or estimates during the search process to prioritize the search for paths that are most likely to produce effective solutions.

Review typical examples

Backtracking algorithms can be used to solve many search problems, constraint satisfaction problems, and combinatorial optimization problems.

Search problems : The goal of this type of problem is to find a solution that satisfies certain conditions.

  • Total permutation problem: Given a set, find all possible permutations and combinations.
  • Subset sum problem: Given a set and a target sum, find the subset whose sum in the set is the target sum.
  • Tower of Hanoi problem: Given three pillars and a series of discs of different sizes, it is required to move all the discs from one pillar to another. Only one disc can be moved at a time, and the large disc cannot be placed On a small disc.

Constraint Satisfaction Problems : The goal of this type of problem is to find a solution that satisfies all constraints.

  • n n n queen: inn × nn \times nn×Placenn on the chessboard of nn queens so that they do not attack each other.
  • Sudoku: at 9 × 9 9 \times 99×Fill in the grid of 9 with the numbers 1 11 ~ 9 9 9 , so that each row, each column and each3 × 3 3 \times 33×Numbers in 3 subgrids are not repeated.
  • Graph coloring problem: Given an undirected graph, color each vertex of the graph with the least color so that adjacent vertices have different colors.

Combinatorial optimization problems : The goal of this type of problem is to find the optimal solution that satisfies certain conditions in a combinatorial space.

  • 0-1 knapsack problem: Given a set of items and a backpack, each item has a certain value and weight. It is required to select items to maximize the total value within the capacity limit of the backpack.
  • Traveling Salesman Problem: In a graph, start from one point, visit all other points exactly once and return to the starting point to find the shortest path.
  • Maximum clique problem: Given an undirected graph, find the largest complete subgraph, that is, any two vertices in the subgraph are connected by an edge.

Note that for many combinatorial optimization problems, backtracking is not an optimal solution.

  • The 0-1 knapsack problem is usually solved using dynamic programming to achieve higher time efficiency.
  • The traveling salesman is a well-known NP-Hard problem. Commonly used solutions include genetic algorithm and ant colony algorithm.
  • The maximum clique problem is a classic problem in graph theory and can be solved by heuristic algorithms such as greedy.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132865393