Guide d'entretien sur l'algorithme et la structure des données - Explication détaillée de l'algorithme de retour en arrière (2)

taille

Les problèmes complexes de retour en arrière contiennent souvent une ou plusieurs contraintes, et les contraintes peuvent souvent être utilisées pour « élaguer » .

!!! question "Exemple de question trois"

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

Afin de satisfaire les contraintes ci-dessus, nous devons ajouter une opération d'élagage : Lors du processus de recherche, si la valeur est 3 33 nœuds, revenez tôt et arrêtez de chercher.

=== « 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}
```

=== « Partez »

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

=== « Rapide »

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

=== « Fléchette »

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

=== « Rouille »

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

La taille est un terme très vivant. Comme le montre la figure ci-dessous, pendant le processus de recherche, nous « coupons » les branches de recherche qui ne répondent pas aux contraintes afin d'éviter de nombreuses tentatives inutiles, améliorant ainsi l'efficacité de la recherche.

在这里插入图片描述

Code-cadre

Ensuite, nous essayons d'extraire le cadre principal de « essayer, restaurer et élaguer » du retour en arrière pour améliorer la polyvalence du code.

Dans le code frame suivant, stateil représente l'état actuel du problème et choicesles choix qui peuvent être faits dans l'état actuel.

=== « 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);
        }
    }
}
```

=== « Partez »

```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)
        }
    }
}
```

=== « Rapide »

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

=== « Fléchette »

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

=== « Rouille »

```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=""

```

Ensuite, nous résolvons l’exemple de problème 3 basé sur le code-cadre. L'état stateparcourt le chemin du nœud, sélectionne choicesle nœud enfant gauche et le nœud enfant droit du nœud actuel, et le résultat resest une liste de chemins.

=== « 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}
```

=== « Partez »

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

=== « Rapide »

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

=== « Fléchette »

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

=== « Rouille »

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

Selon le sens de la question, nous avons trouvé la valeur 7 7La recherche doit continuer après le nœud 7 , donc les instructions après la solution d'enregistrement doivent êtrereturnsupprimées. La figure suivante comparereturnle processus de recherche pour conserver ou supprimer des instructions.

在这里插入图片描述

相比基于前序遍历的代码实现,基于回溯算法框架的代码实现虽然显得啰嗦,但通用性更好。实际上,许多回溯问题都可以在该框架下解决。我们只需根据具体问题来定义 statechoices ,并实现框架中的各个方法即可。

常用术语

为了更清晰地分析算法问题,我们总结一下回溯算法中常用术语的含义,并对照例题三给出对应示例。

表   常见的回溯算法术语

名词 定义 例题三
解 Solution 解是满足问题特定条件的答案,可能有一个或多个 根节点到节点 7 7 7 的满足约束条件的所有路径
约束条件 Constraint 约束条件是问题中限制解的可行性的条件,通常用于剪枝 路径中不包含节点 3 3 3
状态 State 状态表示问题在某一时刻的情况,包括已经做出的选择 当前已访问的节点路径,即 path 节点列表
尝试 Attempt 尝试是根据可用选择来探索解空间的过程,包括做出选择,更新状态,检查是否为解 递归访问左(右)子节点,将节点添加进 path ,判断节点的值是否为 7 7 7
回退 Backtracking 回退指遇到不满足约束条件的状态时,撤销前面做出的选择,回到上一个状态 当越过叶结点、结束结点访问、遇到值为 3 3 3 的节点时终止搜索,函数返回
剪枝 Pruning 剪枝是根据问题特性和约束条件避免无意义的搜索路径的方法,可提高搜索效率 当遇到值为 3 3 3 的节点时,则终止继续搜索

!!! tip

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

优势与局限性

L’algorithme de backtracking est essentiellement un algorithme de recherche en profondeur qui essaie toutes les solutions possibles jusqu’à ce qu’il en trouve une qui satisfasse aux conditions. L’avantage de cette méthode est qu’elle permet de trouver toutes les solutions possibles et qu’elle est très efficace dans des opérations de taille raisonnables.

Cependant, lorsqu'il s'agit de problèmes complexes ou à grande échelle, l'efficacité de l'algorithme de retour en arrière peut s'avérer inacceptable .

  • Temps : les algorithmes de backtracking doivent généralement parcourir toutes les possibilités de l'espace d'état, et la complexité temporelle peut atteindre un ordre exponentiel ou factoriel.
  • Espace : L'état actuel (comme les chemins, les variables auxiliaires pour l'élagage, etc.) doit être sauvegardé dans les appels récursifs. Lorsque la profondeur est grande, les besoins en espace peuvent devenir très importants.

Malgré cela, les algorithmes de backtracking restent la meilleure solution à certains problèmes de recherche et de satisfaction de contraintes . Pour ces problèmes, puisqu’il est impossible de prédire quels choix produiront une solution valable, nous devons parcourir tous les choix possibles. Dans ce cas, la clé est de savoir comment optimiser l'efficacité . Il existe deux méthodes courantes d'optimisation de l'efficacité.

  • Élagage : Gagnez du temps et de l'espace en évitant de chercher des chemins qui ne donneront certainement pas de solution.
  • Recherche heuristique : introduction de certaines stratégies ou estimations au cours du processus de recherche pour prioriser la recherche des chemins les plus susceptibles de produire des solutions efficaces.

Examiner des exemples typiques

Les algorithmes de backtracking peuvent être utilisés pour résoudre de nombreux problèmes de recherche, de satisfaction de contraintes et d’optimisation combinatoire.

Problèmes de recherche : Le but de ce type de problème est de trouver une solution qui satisfait certaines conditions.

  • Problème de permutation totale : étant donné un ensemble, trouvez toutes les permutations et combinaisons possibles.
  • Problème de somme de sous-ensemble : étant donné un ensemble et une somme cible, trouvez le sous-ensemble dont la somme dans l'ensemble est la somme cible.
  • Problème de la tour de Hanoï : étant donné trois piliers et une série de disques de tailles différentes, il est nécessaire de déplacer tous les disques d'un pilier à l'autre. Un seul disque peut être déplacé à la fois, et le grand disque ne peut pas être placé sur un petit disque.

Problèmes de satisfaction de contraintes : Le but de ce type de problème est de trouver une solution qui satisfait toutes les contraintes.

  • nnn 皇后:在 n × n n \times n n×n 的棋盘上放置 n n n 个皇后,使得它们互不攻击。
  • 数独:在 9 × 9 9 \times 9 9×9 的网格中填入数字 1 1 1 ~ 9 9 9 ,使得每行、每列和每个 3 × 3 3 \times 3 3×3 子网格中的数字不重复。
  • 图着色问题:给定一个无向图,用最少的颜色给图的每个顶点着色,使得相邻顶点颜色不同。

组合优化问题:这类问题的目标是在一个组合空间中找到满足某些条件的最优解。

  • 0-1 背包问题:给定一组物品和一个背包,每个物品有一定的价值和重量,要求在背包容量限制内,选择物品使得总价值最大。
  • 旅行商问题:在一个图中,从一个点出发,访问所有其他点恰好一次后返回起点,求最短路径。
  • 最大团问题:给定一个无向图,找到最大的完全子图,即子图中的任意两个顶点之间都有边相连。

请注意,对于许多组合优化问题,回溯都不是最优解决方案。

  • 0-1 背包问题通常使用动态规划解决,以达到更高的时间效率。
  • 旅行商是一个著名的 NP-Hard 问题,常用解法有遗传算法和蚁群算法等。
  • 最大团问题是图论中的一个经典问题,可用贪心等启发式算法来解决。

Je suppose que tu aimes

Origine blog.csdn.net/zy_dreamer/article/details/132865393
conseillé
Classement