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.

Insérer la description de l'image ici

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.

Insérer la description de l'image ici

Par rapport à l'implémentation de code basée sur le parcours de précommande, l'implémentation de code basée sur le cadre d'algorithme de backtracking semble verbeuse, mais elle est plus polyvalente. En fait, de nombreux problèmes de retour en arrière peuvent être résolus dans ce cadre . stateIl nous suffit de définir et en fonction du problème spécifique choiceset de mettre en œuvre chaque méthode dans le cadre.

Termes communs

Afin d'analyser plus clairement les problèmes algorithmiques, nous résumons la signification des termes couramment utilisés dans les algorithmes de backtracking et donnons des exemples correspondants par rapport à l'exemple 3.

Tableau Termes courants des algorithmes de backtracking

nom définition Troisième exemple
Solution Une solution est une réponse qui satisfait des conditions spécifiques du problème, qui peuvent être une ou plusieurs Nœud racine au nœud 7 77. Tous les chemins qui satisfont aux contraintes
Contrainte Les contraintes sont des conditions dans un problème qui limitent la faisabilité d'une solution, généralement utilisées pour l'élagage. Le nœud 3 3 n'est pas inclus dans le chemin3
ÉtatÉtat Le statut représente la situation du problème à un moment donné, y compris les choix qui ont été faits Le chemin du nœud actuellement visité, c'est-à-dire pathla liste des nœuds
EssayerTentative Essayer est le processus d'exploration de l'espace de solutions en fonction des choix disponibles, notamment en faisant un choix, en mettant à jour l'état et en vérifiant s'il s'agit d'une solution. Accédez de manière récursive au nœud enfant gauche (droit), ajoutez le nœud pathet déterminez si la valeur du nœud est 7 77
Retour en arrière Rollback signifie annuler le choix précédent et revenir à l'état précédent lorsqu'on rencontre un état qui ne satisfait pas aux contraintes. Lors du franchissement du nœud feuille, mettant fin à l'accès au nœud, la valeur rencontrée est 3 3La recherche se termine lorsque le nœud 3 est atteint et la fonction renvoie
Taille L'élagage est une méthode permettant d'éviter les chemins de recherche inutiles en fonction des caractéristiques et des contraintes du problème, ce qui peut améliorer l'efficacité de la recherche. Lorsque vous rencontrez une valeur de 3 3Lorsque le nœud atteint 3 , la recherche sera terminée.

!!! conseil

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

Avantages et limites

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 reine : enn × nn \times nn×Placernn sur l'échiquier de nn reines pour qu'elles ne s'attaquent pas.
  • Sudoku : à 9 × 9 9 \fois 99×Remplissez la grille de 9 avec les chiffres 1 11 ~9 99 , de sorte que chaque ligne, chaque colonne et chaque3 × 3 3 \times 33×Les nombres dans 3 sous-grilles ne sont pas répétés.
  • Problème de coloration de graphique : étant donné un graphique non orienté, coloriez chaque sommet du graphique avec le moins de couleur afin que les sommets adjacents aient des couleurs différentes.

Problèmes d'optimisation combinatoire : Le but de ce type de problème est de trouver la solution optimale qui satisfait certaines conditions dans un espace combinatoire.

  • Problème de sac à dos 0-1 : étant donné un ensemble d'articles et un sac à dos, chaque article a une certaine valeur et un certain poids. Il est nécessaire de sélectionner les articles pour maximiser la valeur totale dans la limite de capacité du sac à dos.
  • Problème du voyageur de commerce : dans un graphique, commencez à partir d'un point, visitez tous les autres points exactement une fois et revenez au point de départ pour trouver le chemin le plus court.
  • Problème de clique maximale : étant donné un graphe non orienté, trouvez le plus grand sous-graphe complet, c'est-à-dire que deux sommets quelconques du sous-graphe sont reliés par une arête.

Notez que pour de nombreux problèmes d’optimisation combinatoire, le retour en arrière n’est pas une solution optimale.

  • Le problème du sac à dos 0-1 est généralement résolu à l’aide d’une programmation dynamique pour obtenir une plus grande efficacité en termes de temps.
  • Le voyageur de commerce est un problème NP-Hard bien connu. Les solutions couramment utilisées incluent l'algorithme génétique et l'algorithme de colonie de fourmis.
  • Le problème de la clique maximale est un problème classique de la théorie des graphes et peut être résolu par des algorithmes heuristiques tels que gloutons.

Acho que você gosta

Origin blog.csdn.net/zy_dreamer/article/details/132865393
Recomendado
Clasificación