web-dev-qa-db-fra.com

Tour de Hanoi: algorithme récursif

Bien que je n’aie aucun problème à comprendre la récursion, je ne parviens pas à comprendre la solution récursive au problème de la tour de Hanoi. Voici le code de Wikipedia :

procedure Hanoi(n: integer; source, dest, by: char);
Begin
    if (n=1) then
        writeln('Move the plate from ', source, ' to ', dest)
    else begin
        Hanoi(n-1, source, by, dest);
        writeln('Move the plate from ', source, ' to ', dest);
        Hanoi(n-1, by, dest, source);
    end;
End;

Je comprends le scénario de base et le concept de fragmentation du problème en morceaux plus petits jusqu'à ce que vous puissiez déplacer un seul disque. Cependant, je ne peux pas comprendre comment les deux appels récursifs dans le cas de base ne fonctionnent pas ensemble. Peut-être que quelqu'un peut m'aider? Merci.

62
titaniumdecoy

En fait, la section d'où vous avez pris ce code offre également une explication:

Pour déplacer n disques de la cheville A à la cheville C:

  1. déplacez n-1 disques de A à B. Cela laisse le disque n seul sur la cheville A 
  2. déplacer le disque n ° de A à C 
  3. déplacez n-1 disques de B en C pour qu'ils soient assis sur le disque #n

Il est clair que vous devez d’abord retirer les disques n - 1 pour pouvoir accéder au n ème disque. Et que vous deviez les déplacer d'abord vers une autre cheville que celle où vous souhaitez voir apparaître la tour complète.

Le code de votre message a trois arguments, en plus du nombre de disques: Un pion source, un pion destination et un pion temporaire sur lesquels les disques peuvent être stockés (où chaque disque avec la taille n - 1 correspond).

La récursivité se produit en fait deux fois, une fois avant la writeln, une fois après. Celui précédant la writeln déplacera n - 1 disques sur la cheville temporaire, en utilisant la cheville de destination comme mémoire de stockage temporaire (les arguments de l'appel récursif sont dans un ordre différent). Après cela, le disque restant sera déplacé vers la cheville de destination et ensuite la seconde récursivité impliquera le déplacement de la tour entière, en déplaçant la tour n - 1 de la cheville temporaire vers la cheville de destination, au dessus du disque n.

46
Joey

il y a un an, j'ai suivi un cours de programmation fonctionnelle et dessiné cette illustration pour l'algorithme . espérons que cela aidera!

(0)  _|_         |          |
    __|__        |          |
   ___|___       |          |
  ____|____  ____|____  ____|____

(1.1) |          |          |
    __|__        |          |
   ___|___      _|_         |
  ____|____  ____|____  ____|____ (A -> B)

(1.2) |          |          |
      |          |          |
   ___|___      _|_       __|__
  ____|____  ____|____  ____|____ (A -> C)

(1.3) |          |          |
      |          |         _|_
   ___|___       |        __|__
  ____|____  ____|____  ____|____ (B -> C)



(2.1) |          |          |
      |          |         _|_
      |       ___|___     __|__
  ____|____  ____|____  ____|____ (A -> B)



(3.1) |          |          |
      |          |          |
     _|_      ___|___     __|__
  ____|____  ____|____  ____|____ (C -> A)

(3.2) |          |          |
      |        __|__        |
     _|_      ___|___       |
  ____|____  ____|____  ____|____ (C -> B)

(3.3) |         _|_         |
      |        __|__        |
      |       ___|___       |
  ____|____  ____|____  ____|____ (A -> B)

Le problème des 3 anneaux a été divisé en 2 problèmes des 2 anneaux (1.x et 3.x)

31
f0b0s

Vous trouverez une bonne explication de la mise en œuvre récursive de Hanoi à l’adresse http://www.cs.cmu.edu/~cburch/survey/recurse/hanoiimpl.html .

En résumé, si vous voulez déplacer la plaque inférieure d'un bâtonnet A à un bâtonnet B, vous devez d'abord déplacer toutes les plaques plus petites dessus de A à C. Le deuxième appel récursif consiste ensuite à déplacer les plaques que vous avez déplacées vers C sur B après que votre cas de base ait déplacé la grande plaque unique de A à B.

14
matthock

Je conviens que celui-ci n’est pas immédiat au premier abord, mais c’est assez simple quand on y arrive.

Cas de base: votre tour est de taille 1. Vous pouvez donc le faire en un seul mouvement, de la source directement à la destination.

Cas récursif: votre tour est de taille n> 1. Vous déplacez donc la tour supérieure de taille n-1 vers un piquet supplémentaire (par), vous déplacez la "tour" inférieure de taille 1 vers le piquet de destination, puis vous déplacez la tour supérieure de par à dest.

Donc, avec un cas simple, vous avez une tour de hauteur 2:

 _|_    |     |
__|__   |     |
===== ===== =====

Première étape: déplacez la tour supérieure de 2-1 (= 1) vers le piquet supplémentaire (celui du milieu, disons).

  |     |     |
__|__  _|_    |
===== ===== =====

Suivant: déplacez le disque du bas vers la destination:

  |     |     |
  |    _|_  __|__
===== ===== =====

Enfin, déplacez la tour supérieure de (2-1) = 1 vers la destination.

  |     |    _|_
  |     |   __|__
===== ===== =====

Si vous y réfléchissez, même si la tour comptait 3 personnes ou plus, il y aura toujours un piquet supplémentaire vide, ou un piquet avec tous les disques plus grands, que la récursion utilisera pour échanger des tours.

13
Sean

Supposons que nous voulions déplacer un disque de A à C à travers B puis:

  1. déplacez un disque plus petit sur B.
  2. déplacez un autre disque sur C.
  3. déplacez B vers C.
  4. passer de A à B.
  5. déplacez tous de C à A.

Si vous répétez toutes les étapes ci-dessus, le disque sera transféré.

4

Considérez-le comme une pile dont le diamètre des disques est représenté par un entier (4,3,2,1) Le premier appel récursif sera appelé 3 fois et remplira ainsi la pile d'exécution comme suit

  1. premier appel: 1. Deuxième appel: 2,1. et troisième appel: 3,2,1.

Une fois la première récursion terminée, le contenu de la pile d'exécution est affiché dans le pôle central du plus grand diamètre au plus petit (premier entré dernier sorti). Ensuite, le disque de diamètre 4 est déplacé vers la destination.

Le deuxième appel récursif est identique au premier, à l'exception du déplacement des éléments du pôle central vers la destination. 

2
Ahmad

Je ressens la douleur! 

Bien que ce soit un vieux post, je pense que ce qu'il faut vraiment comprendre, ce n'est pas l'approche "déplacez ceci vers cela" mais la réponse implique l'utilisation de l'effet secondaire de la récursion.

Le "Petit Schemer" m'a été d'une aide précieuse et m'a appris à penser et à écrire des fonctions récursives. 

Cependant, cela enseigne au lecteur à utiliser les résultats du résultat renvoyé lors du prochain appel récursif. 

Dans la tour de Hanoi, la réponse ne se trouve pas dans le résultat renvoyé en soi, mais dans l'observation du résultat renvoyé. 

Le magie se produit lors du réarrangement successif des paramètres de la fonction.

Oui, le problème est vraiment en trois parties:

  • déplacer une tour plus petite à la cheville de rechange
  • déplacer le dernier disque vers la cheville de destination
  • déplacer la tour restante de la cheville de rechange vers la cheville de destination.

En programme:

(define (th n a b c)
    (if (zero? n) 'done
        (begin
           (th (- n 1) a c b)
           (display (list a c))
           (newline)
           (th (- n 1) b a c))))
(th 5 'source 'spare 'destination)

Cependant, c'est l'affichage des paramètres de fonction qui constitue la solution au problème et la compréhension cruciale de la structure en double arborescence des appels.

La solution transmet également la puissance de proof by induction et une chaleur chaude à tous les programmeurs qui ont lutté avec des structures de contrôle conventionnelles.

Incidemment, résoudre le problème à la main est très satisfaisant.

  • compter le nombre de disques
  • si c'est le cas, déplacez le premier disque sur le piquet de rechange, effectuez le prochain déplacement légal (sans le disque supérieur). Puis déplacez le disque du haut vers la cheville de destination, puis effectuez le prochain coup légal (nittd). Ensuite, déplacez le disque du haut vers la cheville source, effectuez le prochain coup légal (nittd) ...
  • si impair, déplacez le premier disque vers la cheville de destination, effectuez le prochain coup légal (sans le disque supérieur). Déplacez ensuite le disque du haut vers le piquet de rechange, puis effectuez le prochain mouvement légal (min.) Ensuite, déplacez le disque du haut vers la cheville source, effectuez le prochain coup légal (nittd) ...

Il est préférable de toujours tenir le disque supérieur avec la même main et de toujours déplacer cette main dans la même direction.

Le nombre final de déplacements pour les disques n est 2^n - 1 et le move n disc to destination est à mi-chemin du processus.

Enfin, il est amusant de voir comment expliquer un problème à un collègue, à votre femme/mari ou même au chien (même s’ils ne l’écoutent pas) peut cimenter l’Illumination. 

2
potong

Après avoir lu toutes ces explications, j’ai pensé que j’utiliserais la méthode utilisée par mon professeur pour expliquer la solution récursive des Tours de Hanoi. Voici à nouveau l'algorithme avec n représentant le nombre de sonneries et A, B, C représentant les piquets. Le premier paramètre de la fonction est le nombre d'anneaux, le deuxième paramètre représente le pion source, le troisième est le pion de destination et le quatrième est le pion de réserve.

procedure Hanoi(n, A, B, C);
  if n == 1
    move ring n from peg A to peg B
  else
    Hanoi(n-1, A, C, B);
    move ring n-1 from A to C
    Hanoi(n-1, C, B, A);
end;

On m'a appris aux études supérieures à ne jamais avoir honte de penser petit. Alors, regardons cet algorithme pour n = 5. La question à vous poser d’abord est si je veux déplacer le 5ème anneau de A à B, où sont les 4 autres anneaux? Si le cinquième anneau occupe la cheville A et que nous voulons le déplacer vers la cheville B, les 4 autres bagues ne peuvent être que sur la cheville C. Dans l'algorithme situé au-dessus de la fonction Hanoi (n-1, A, C, B ) essaie de déplacer tous ces 4 autres anneaux sur la cheville C, ainsi l'anneau 5 pourra passer de A à B. En suivant cet algorithme, on considère n = 4. Si l'anneau 4 est déplacé de A à C , où sont les anneaux 3 et moins? Ils ne peuvent être que sur la cheville B. Ensuite, pour n = 3, si l'anneau 3 est déplacé de A à B, où sont les anneaux 2 et 1? Sur la cheville C bien sûr. Si vous continuez à suivre ce modèle, vous pouvez visualiser ce que fait l'algorithme récursif. Cette approche diffère de celle du novice dans la mesure où il considère le dernier disque en premier et le premier disque en dernier.

2
MNRC
public static void hanoi(int number, String source, String aux, String dest)
{
    if (number == 1)
    {
        System.out.println(source + " - > "+dest);
    }
    else{
        hanoi(number -1, source, dest, aux);
        hanoi(1, source, aux, dest);
        hanoi(number -1, aux, source, dest);
    }
}
1
Rafael Amsili

C'est simple. Supposons que vous souhaitiez passer de A à C

s'il n'y a qu'un seul disque, déplacez-le.

S'il y a plus d'un disque, est-ce que

  • déplacer tous les disques (n-1 disques), sauf le dernier de A à B
  • déplacez le disque du bas de A à C
  • déplacer les n-1 disques de la première étape de A à C

Gardez à l'esprit que lors du déplacement des n-1 disques, le nième ne sera plus un problème (une fois qu'il sera plus grand que tous les autres)

Notez que le déplacement des n-1 disques revient toujours sur le même problème, jusqu'à ce que n-1 = 1, auquel cas vous serez sur le premier if (où vous devez simplement le déplacer).

1
Samuel Carrijo

Comme certains de nos amis l’ont suggéré, j’ai supprimé les deux réponses précédentes et j’ai consolidé ici.

Cela vous donne une compréhension claire.

Qu'est-ce que l'algorithme général est ....

Algorithme: 

solve(n,s,i,d) //solve n discs from s to d, s-source i-intermediate d-destination
{
    if(n==0)return;
    solve(n-1,s,d,i); // solve n-1 discs from s to i Note:recursive call, not just move
    move from s to d; // after moving n-1 discs from s to d, a left disc in s is moved to d
    solve(n-1,i,s,d); // we have left n-1 disc in 'i', so bringing it to from i to d (recursive call)
}

voici l'exemple de travail Cliquez ici

1
Vivek MVK

La réponse à la question, comment le programme sait-il que même est "src" à "aux", et impaire est "src" à "dst" car le mouvement d'ouverture se trouve dans le programme. Si vous décomposez le mouvement du poing avec 4 disques, ceci ressemble à ceci:

hanoi(4, "src", "aux", "dst");
if (disc > 0) {
    hanoi(3, 'src', 'dst', 'aux');
        if (disc > 0) {
            hanoi(2, 'src', 'aux', 'dst');
                if (disc > 0) {
                    hanoi(1, 'src', 'dst', 'aux');
                        if (disc > 0) {
                            hanoi(0, 'src', 'aux', 'dst');
                                END
                        document.writeln("Move disc" + 1 + "from" + Src + "to" + Aux);
                        hanoi(0, 'aux', 'src', 'dst');
                                END
                        }

aussi le premier coup avec 4 disques (pair) va de Src à Aux. 

1
dspkwa
void TOH(int n, int a, int b){
        /*Assuming a as source stack numbered as 1, b as spare stack numbered as 2 and  c as target stack numbered as 3. So once we know values of a and b, we can determine c as there sum is a constant number (3+2+1=)6.
       */
int c = 6-a-b;
if(n==1){
    cout<<"Move from "<<a<<" to "<<b<<"\n";
}
else{
    // Move n-1 disks from 1st to 2nd stack. As we are not allowed to move more than one disks at a time, we do it by recursion. Breaking the problem into a simpler problem.
    TOH(n-1, a, c);
    // Move the last alone disk from 1st to 3rd stack.
    TOH(1, a, b);
    // Put n-1 disks from 2nd to 3rd stack. As we are not allowed to move more than one disks at a time, we do it by recursion. Breaking the problem into a simpler problem.        
    TOH(n-1, c, b);
}
}
int main() {

TOH(2, 1, 3);
cout<<"FINISHED                        \n";
TOH(3, 1, 3);
cout<<"FINISHED                        \n";
TOH(4, 1, 3);
return 0;
}
1
Aditya Raj

Il y a trois tours, à savoir la tour source, la tour de destination et la tour auxiliaire. La tour source contient tous les disques et votre objectif est de déplacer tous les disques vers la tour de destination et de vous assurer que vous ne placez jamais un disque plus grand sur un disque plus petit. Nous pouvons résoudre ce problème en utilisant la récursivité dans les étapes ci-dessous:

Nous avons n nombre de disques sur la tour source

Cas de base: n = 1 S'il n'y a qu'un seul disque dans la tour source, déplacez-le vers la tour de destination. 

Cas récursif: n> 1  

  • Déplacer les n-1 premiers disques de la tour source à la tour auxiliaire 
  • Déplacez le seul disque restant (après l'étape 1) vers la tour Tour
  • Déplacez les disques n-1 qui se trouvent maintenant dans la tour d'assistance, vers la destination
    tour, en utilisant la tour source comme aide.

Code source en Java:  

private void towersOfHanoi(int n, char source, char destination, char helper) {
    //Base case, If there is only one disk move it direct from source to destination
    if(n==1){
        System.out.println("Move from "+source+" to "+destination);
    }
    else{
        //Step1: Move the top n-1 disks from source to helper
        towersOfHanoi(n-1, source, helper, destination);
        //Step2: Move the nth disk from source to destination
        System.out.println("Move from "+source+" to "+destination);
        /*Step3: Move the n-1 disks(those you moved from source to helper in step1) 
         * from helper to destination, using source(empty after step2) as helper
         */
        towersOfHanoi(n-1, helper, destination, source);
    }
}
1
bpjoshi

Voici l'explication. Regardez la photo ->

 enter image description here

En appelant Movetower(3,a,b,c), vous avez l’intention de déplacer les 3 disques de la tour A vers la tour B. Donc, les appels séquentiels sont ->

1. Movetower(3,a,b,c)  // No Move needed
2. Movetower(2,a,c,b)  // No move needed
3. Movetower(1,a,b,c)  // Here is the time to move, move disc1 from a to b
4. Movetower(2,a,c,b)  // Returning to this call again, this is the time to move disc2 from a to c
5. Movetower(1,b,c,a)  // Again the time to move, this time disc1 from b to c
6. Movetower(3,a,b,c)  // Returning to this call again, this is the time to move disc3 from a to b
7. Movetower(2,c,b,a)  // Not the time to move
8. Movetower(1,c,a,b)  // Here is the time to move, move disc1 from c to a
9. Movetower(2,c,b,a)  // Returning to this call again, this is the time to move disc2 from c to b
10.Movetower(1,c,a,b)  // Here is the time to move, move disc1 from a to b

J'espère que ça aide :)

Pour l'animation: https://www.cs.cmu.edu/~cburch/survey/recurse/hanoiex.html

0
jbsu32

Voici mon code de solution au problème de Towers of Hanoi qui utilise la récursion avec golang. `paquet principal

import "fmt"

func main() {
    toi(4, "src", "dest", "swap")
}

func toi(n int, from, to, swap string) {
    if n == 0 {
        return
    }
    if n == 1 {
        fmt.Printf("mov %v %v -> %v\n", n, from, to)
        return
    }
    toi(n-1, from, swap, to)
    fmt.Printf("mov %v %v -> %v\n", n, from, to)
    toi(n-1, swap, to, from)
}`
0
Gaurav Sinha

Cet exemple python3 utilise une solution récursive:

# Hanoi towers puzzle
# for each n, you have to move n-1 disks off the n disk onto another peg
# then you move the n disk to a free peg
# then you move the n-1 disks on the other peg back onto the n disk

def hanoi(n):
    if n == 1:
        return 1
    else:
        return hanoi(n-1) + 1 + hanoi(n-1)


for i in range(1, 11):
    print(f"n={i}, moves={hanoi(i)}")

Sortie:

n=1, moves=1
n=2, moves=3
n=3, moves=7
n=4, moves=15
n=5, moves=31
n=6, moves=63
n=7, moves=127
n=8, moves=255
n=9, moves=511
n=10, moves=1023

Mais bien sûr, le moyen le plus efficace de calculer le nombre de déplacements est de réaliser que les réponses sont toujours 1 moins que 2 ^ n. Donc, la solution mathématique est 2 ^ n - 1

0
Angus Comber

Le premier appel récursif déplace toutes les pièces sauf la plus grosse, de source à destination, en utilisant dest comme pile auxiliaire. Une fois cela fait, toutes les pièces, sauf la plus grande, resteront à côté et la plus grande est gratuite. Vous pouvez maintenant déplacer le plus gros fichier à destination et utiliser un autre appel récursif pour déplacer toutes les pièces de par en.

Les appels récursifs ne sauront rien sur le plus gros morceau (c'est-à-dire qu'ils l'ignoreront), mais ce n'est pas grave, car les appels récursifs ne traiteront que des morceaux plus petits et pourront donc être déplacés librement.

0
sepp2k

En tant qu'élève CS, vous avez peut-être entendu parler de l'induction mathématique… La solution récursive de la tour de Hanoi fonctionne de manière analogue - la seule différence est de ne pas se perdre avec B et C, comme la tour complète se termine.

0
weismat

En termes simples, l’idée est de remplir une autre tour parmi les trois tours définies dans le même ordre de disques que celui présent sans qu’un disque plus grand recouvre un petit disque à tout moment de la procédure.

Soit 'A', 'B' et 'C', trois tours. 'A' sera la tour contenant les disques 'n' initialement. 'B' peut être utilisé comme tour intermédiaire et 'C' est la tour cible. 

L'algo est comme suit:

  1. Déplacez les n-1 disques de la tour 'A' vers 'B' à l'aide de 'C'
  2. Déplacer un disque de 'A' à 'C'
  3. Déplacez les n-1 disques de la tour "B" vers "C" en utilisant "A"

Le code est le suivant en Java:

classe publique TowerOfHanoi {

public void TOH(int n, int A , int B , int C){
    if (n>0){
        TOH(n-1,A,C,B);
        System.out.println("Move a disk from tower "+A +" to tower " + C);
        TOH(n-1,B,A,C);
    }
}

public static void main(String[] args) {
    new TowerOfHanoi().TOH(3, 1, 2, 3);
}   

}

0
mohit verma