Aller au contenu

Algorithme

L'algorithme des Tours de Hanoï

Une fonction courte, et l'exemple de récursivité le plus clair de toute l'informatique. Exécutez-la ci-dessous et regardez chacun de ses appels.

Sur cette page
  1. L'idée
  2. L'algorithme
  3. Une partie résolue, coup par coup
  4. Exécutez-le
  5. Ce qu'il faut observer
  6. L'arbre de récursion
  7. Pourquoi c'est correct
  8. La récurrence, résolue correctement
  9. Complexité en temps et en mémoire
  10. Sans récursivité
  11. Depuis une position qui n'est pas le début
  12. Testez-vous
  13. L'algorithme dans votre langage
  14. Les mots de cette page
  15. Questions fréquentes

L'algorithme des Tours de Hanoï déplace une pile de n disques d'une tour à une autre en 2ⁿ − 1 coups, ce qui est le minimum possible. On l'écrit presque toujours de façon récursive, parce que le casse-tête l'est : le problème de déplacer n disques contient deux copies du problème d'en déplacer n − 1, avec un seul coup entre les deux.

L'idée

Le plus grand disque est le seul qui ne puisse se poser sur rien : commencez donc par lui. Il ne peut bouger que lorsque rien ne le recouvre et que rien n'occupe la tour où il va — autrement dit, tous les autres disques doivent être empilés sur la troisième tour. Toute la solution tient donc en trois étapes :

  1. Déplacer les n − 1 disques plus petits du départ vers la tour intermédiaire.
  2. Déplacer le plus grand disque du départ vers l'arrivée.
  3. Déplacer les n − 1 disques plus petits de la tour intermédiaire vers l'arrivée.

Les étapes 1 et 3 sont le même casse-tête avec un disque de moins, et une autre tour dans le rôle d'intermédiaire. Résolvez-les de la même manière, puis celles qu'elles contiennent, jusqu'à ce que la pile à déplacer soit vide. Si la récursivité elle-même vous est nouvelle, la récursivité expliquée avec les Tours de Hanoï repart du début.

Ces trois étapes forment un algorithme diviser pour régner, et c'est là que la plupart des cours rangent ce casse-tête. Il divise le problème en deux sous-problèmes, règle chacun par récursivité, et les combine avec l'étape de combinaison la moins chère qui soit : un seul coup. Ce qu'il n'est pas, c'est une division équilibrée : le tri fusion coupe son entrée en deux, et ici on retire un disque. Cette seule différence explique pourquoi le Master Theorem ne s'y applique pas, ce dont parle une section plus bas.

L'algorithme

Écrit en entier, c'est une fonction en deux moitiés. Le cas de base est la version qu'elle traite sans s'appeler elle-même — ici n == 0, aucun disque à déplacer, rien à faire — et le cas récursif est tout le reste : les trois lignes qui démontent le casse-tête et en rendent les morceaux à la même fonction. Certaines versions s'arrêtent à n == 1 et déplacent le disque là ; les coups sont identiques, et la version ci-dessous garde le déplacement sur une seule ligne.

Sous forme numérotée, avec les tours nommées par le rôle qu'elles jouent dans l'appel plutôt que par leur lettre :

ALGORITHME TOH(n, source, target, spare)

  1. Si n = 0, revenir. Il n'y a rien à déplacer.
  2. TOH(n − 1, source, spare, target) — dégager les n − 1 disques du dessus. Ils vont sur l'intermédiaire, donc l'intermédiaire est l'arrivée de cet appel, et la tour vers laquelle file le plus grand disque est celle qu'il faut garder libre.
  3. Déplacer le disque n de source vers target. C'est la seule ligne qui déplace quoi que ce soit.
  4. TOH(n − 1, spare, target, source) — ramener ces n − 1 disques sur l'arrivée. Ils partent de la tour qui servait d'intermédiaire, et la tour où tous les disques ont commencé est maintenant celle à garder libre.

Une partie résolue, coup par coup

Voici la réponse de cet algorithme pour trois disques, avant d'exécuter quoi que ce soit. Sept coups, sept plateaux, et la raison pour laquelle chaque coup devait venir à ce moment-là.

  1. Phase 1 · dégager le passage

    Disque 1, de A à C. Le disque 3 ne peut pas bouger tant que les deux disques posés dessus ne sont pas sur B, et le disque 2 ne peut pas aller sur B tant que le disque 1 est sur lui. Le plus petit disque part donc en premier, vers C.

  2. Phase 1 · dégager le passage

    Disque 2, de A à B. Le disque 2 est dégagé et B est vide : il prend sa place sur la tour intermédiaire.

  3. Phase 1 · dégager le passage

    Disque 1, de C à B. Le disque 1 redescend sur le disque 2. Les deux petits disques sont empilés sur B et le disque 3 est libre — ce qui est tout l'objet des trois premiers coups.

  4. Phase 2 · le grand disque traverse

    Disque 3, de A à C. Rien n'est sur le disque 3 ni sur C : le plus grand disque traverse en un seul coup. C'est le seul des sept que l'appel le plus extérieur joue lui-même.

  5. Phase 3 · reconstruire par-dessus

    Disque 1, de B à A. La même danse en trois coups, mais dans l'autre sens : B est maintenant le départ et A, où tous les disques ont commencé, est l'intermédiaire. Le disque 1 s'ôte d'abord du chemin du disque 2.

  6. Phase 3 · reconstruire par-dessus

    Disque 2, de B à C. Le disque 2 passe sur le disque 3. C'est légal parce que le disque 3 est plus grand, et c'est la seule condition qui compte jamais.

  7. Phase 3 · reconstruire par-dessus

    Disque 1, de A à C. Le disque 1 se pose sur la pile et le casse-tête est résolu en 7 coups — 2³ − 1, et le minimum possible.

Tout le casse-tête à trois disques, un plateau par coup, chacun tel qu'il est une fois ce coup joué — le disque ombré est celui qui vient de se poser. Les sept coups forment trois groupes : les coups 1 à 3 portent les deux petits disques sur B, le coup 4 est le plus grand disque qui traverse, et les coups 5 à 7 ramènent les petits par-dessus. Les groupes un et trois sont le même casse-tête à deux disques, ce qui est exactement ce que fait la fonction récursive quand elle s'appelle deux fois.

Remarquez ce qu'ont en commun les coups 1 à 3 et les coups 5 à 7 : les deux sont le casse-tête à deux disques, joué entre une autre paire de tours. C'est la récursivité, visible dans la sortie avant même que le programme tourne — et c'est pourquoi la fonction ci-dessous n'a besoin que d'une seule ligne qui déplace un disque.

Exécutez-le

Les mêmes sept coups, cette fois avec le programme qui les produit. Chaque étape exécute une ligne, et la pile d'appels énumère tous les appels commencés et non encore terminés. Essayez quatre ou cinq disques : le nombre d'appels double à chaque fois, et la pile gagne un étage.

hanoi(3, A, C, B)

Déplacer 3 disques de A vers C.

Rien ne s'exécute encore. La première étape fait cet appel.

Étape 0 sur 43

Pile d'appels

Profondeur 0 sur 4 au plus

  1. Vide. Rien n'a encore été appelé.

Pseudocode

procedure hanoi(n, source, target, spare)
    if n == 0 then
        return
    hanoi(n - 1, source, spare, target)
    move disk n from source to target
    hanoi(n - 1, spare, target, source)
end procedure

Coups

7 en tout

Choisissez une ligne pour amener le programme à ce coup.

Tous les coups que le programme joue pour 3 disques
CoupDisqueDeVers
11AC
22AB
31CB
43AC
51BA
62BC
71AC
Les 15 appels, dans l'ordre où le programme les fait
  1. hanoi(3, A, C, B)profondeur 1 · joue le coup 4
  2. hanoi(2, A, B, C)profondeur 2 · joue le coup 2
  3. hanoi(1, A, C, B)profondeur 3 · joue le coup 1
  4. hanoi(0, A, B, C)profondeur 4 · n vaut 0 — revient aussitôt
  5. hanoi(0, B, C, A)profondeur 4 · n vaut 0 — revient aussitôt
  6. hanoi(1, C, B, A)profondeur 3 · joue le coup 3
  7. hanoi(0, C, A, B)profondeur 4 · n vaut 0 — revient aussitôt
  8. hanoi(0, A, B, C)profondeur 4 · n vaut 0 — revient aussitôt
  9. hanoi(2, B, C, A)profondeur 2 · joue le coup 6
  10. hanoi(1, B, A, C)profondeur 3 · joue le coup 5
  11. hanoi(0, B, C, A)profondeur 4 · n vaut 0 — revient aussitôt
  12. hanoi(0, C, A, B)profondeur 4 · n vaut 0 — revient aussitôt
  13. hanoi(1, A, C, B)profondeur 3 · joue le coup 7
  14. hanoi(0, A, B, C)profondeur 4 · n vaut 0 — revient aussitôt
  15. hanoi(0, B, C, A)profondeur 4 · n vaut 0 — revient aussitôt

Ce qu'il faut observer

  • Les rôles tournent. La tour B est l'intermédiaire du premier appel et l'arrivée de celui qu'il lance. Chaque niveau de récursivité réétiquette les trois tours, et ce réétiquetage est toute l'astuce.
  • La pile ne devient jamais profonde. Trois disques n'ont jamais plus de 4 appels en cours en même temps — un par nombre de disques, de 3 à 0 — alors que le programme fait 15 appels en tout.
  • La moitié des appels ne font rien. Sur ces 15 appels, 8 portent sur zéro disque et reviennent aussitôt. C'est le prix du cas de base le plus simple possible.
  • La ligne qui déplace s'exécute exactement 2ⁿ − 1 fois. Sept fois, pour trois disques, au fil des 43 étapes de l'exécution.

Le premier point est le plus difficile à saisir pendant que le programme tourne, parce que rien ne bouge sur le plateau quand cela se produit : seuls changent les noms sous les tours. Le voici figé : le premier appel, les deux qui mènent au coup 1, et l'appel qui ramène la pile.

  1. hanoi(3, A, C, B)

    Le premier appel : déplacer les 3 disques de A vers C. Cela laisse B comme intermédiaire. Départ A, arrivée C, intermédiaire B.

  2. hanoi(2, A, B, C)

    Son premier appel. Le disque 3 ne peut pas bouger avec les disques 1 et 2 dessus, et ceux-ci ne peuvent pas aller sur C, où se rend le disque 3. Donc B, l'intermédiaire d'il y a un instant, devient l'arrivée, et C devient l'intermédiaire. Départ A, arrivée B, intermédiaire C.

  3. hanoi(1, A, C, B)

    Un niveau plus bas, le même échange recommence. Pour dégager le disque 2, le disque 1 va sur C, et B redevient l'intermédiaire. Départ A, arrivée C, intermédiaire B.

  4. hanoi(2, B, C, A)

    Le disque 3 a traversé. Le second appel de la racine ramène les disques 1 et 2 depuis B par-dessus lui : B est donc maintenant le départ, et A, où tous les disques ont commencé, est l'intermédiaire. Départ B, arrivée C, intermédiaire A.

hanoi(3, A, C, B) et trois des appels qu'il contient, dans l'ordre où ils s'exécutent, chacun sur le plateau tel que cet appel le trouve. Les disques ombrés sont ceux que l'appel doit déplacer. Les tours restent où elles sont ; seuls leurs noms changent. Chaque appel transmet ses trois tours avec deux d'entre elles échangées — arrivée et intermédiaire au premier appel qu'il lance, départ et intermédiaire au second — et c'est là tout le réétiquetage.

Le deuxième est plus facile à mettre en doute qu'à vérifier ; voici donc toute l'exécution avec la hauteur de la pile à chacune de ses étapes :

  • L'exécution prend 43 étapes et fait 15 appels en tout.
  • Le nombre d'appels sur la pile monte et descend entre 0 et 4, et atteint 4 à 8 de ces étapes — chaque fois qu'un appel sans disque à déplacer commence et revient.
  • La profondeur 4 est n + 1 pour n = 3, et aucune étape de l'exécution ne la dépasse.
Chaque étape de l'exécution à trois disques, avec le nombre d'appels sur la pile à cet instant. La forme est large parce que le travail double à chaque disque ajouté — 43 étapes ici, et 15 appels — et plate parce que la mémoire, elle, ne double pas : la pile ne garde qu'un appel par nombre de disques, de 3 à 0, donc elle touche 4 et s'arrête. Cet écart entre la largeur et la hauteur, c'est Θ(2ⁿ) de temps contre Θ(n) de mémoire, dessiné.

L'arbre de récursion

Dessinez chaque appel comme une case sous l'appel qui l'a lancé et le programme devient un arbre. Placez chaque case au-dessus du numéro du coup qu'elle joue, et autre chose apparaît : lire les cases de gauche à droite donne les coups dans l'ordre. Avec trois disques, le coup propre au premier appel — le plus grand disque — est le coup 4, pile au milieu, avec de part et d'autre la solution complète à deux disques en trois coups.

  1. Coup 1 : l'appel avec n = 1, de A vers C, déplace le disque 1.
  2. Coup 2 : l'appel avec n = 2, de A vers B, déplace le disque 2, entre ses deux appels avec n = 1.
  3. Coup 3 : l'appel avec n = 1, de C vers B, déplace le disque 1.
  4. Coup 4 : l'appel avec n = 3, de A vers C, déplace le disque 3, entre ses deux appels avec n = 2.
  5. Coup 5 : l'appel avec n = 1, de B vers A, déplace le disque 1.
  6. Coup 6 : l'appel avec n = 2, de B vers C, déplace le disque 2, entre ses deux appels avec n = 1.
  7. Coup 7 : l'appel avec n = 1, de A vers C, déplace le disque 1.
Les 7 appels de hanoi(3, A, C, B) qui déplacent un disque. Chaque appel se tient au-dessus des deux qu'il lance, et chacun est placé au-dessus du numéro du coup qu'il joue — de sorte que les cases, lues de gauche à droite, sont la solution dans l'ordre. Les 8 appels avec n = 0 ne sont pas dessinés : il y en a un accroché de chaque côté de chaque case du bas, et chacun revient sans rien faire. Exécutez le programme ci-dessus et cet arbre le suit : l'appel en cours s'allume, les appels qui l'attendent sont détourés, et chaque numéro de coup se remplit quand son coup est joué. Donnez au programme quatre ou cinq disques et l'arbre est redessiné à cette taille.

Cette symétrie tient à toutes les tailles. Le coup 2ⁿ⁻¹ est toujours le plus grand disque qui traverse, et les coups avant et après sont la solution à (n − 1) disques jouée deux fois, avec deux tours qui échangent leurs rôles. C'est pourquoi une liste de coups de n'importe quelle longueur se lit d'un coup d'œil, et pourquoi la solution à cinq disques est celle à quatre, un coup, puis celle à quatre de nouveau.

Pourquoi c'est correct

L'algorithme n'enfreint jamais une règle et se termine toujours, et la démonstration est assez courte pour être donnée en entier. L'affirmation, pour tout n, est :

Si les n disques du dessus de la tour de départ sont les disques 1 à n, et que tout disque situé ailleurs est plus grand que n, alors hanoi(n, source, target, spare) déplace ces n disques vers l'arrivée, légalement, et ne touche à rien d'autre.

Pour n = 0, il n'y a rien à déplacer et rien n'est touché.

Supposons l'affirmation vraie pour n − 1. Alors chacune des trois étapes est légale :

  1. Le premier appel déplace les disques 1 à n − 1 sur l'intermédiaire. Il en a le droit : tous les autres disques, disque n compris, sont plus grands que n − 1.
  2. Le disque n va sur l'arrivée. Rien ne le recouvre désormais, et l'arrivée ne contient que des disques plus grands que n.
  3. Le second appel déplace les disques 1 à n − 1 de l'intermédiaire vers l'arrivée. Avec le même droit : le disque n, désormais sous eux, est plus grand.

Les n disques sont sur l'arrivée et rien d'autre n'a bougé. Par récurrence, l'affirmation vaut pour tout n, et le casse-tête complet est le cas où la tour de départ porte tous les disques.

  1. Supposél'affirmation pour n − 1

    L'hypothèse dit qu'un appel sur n − 1 disques les déplace là où on le lui demande, légalement, et ne touche à rien d'autre. Appliquée ici, elle emporte les disques 1 à n − 1 du départ vers l'intermédiaire. Elle en a le droit : tout disque sur lequel elle pourrait les poser — disque n compris — est plus grand que n'importe lequel de ceux qu'elle porte.

  2. Démontré iciun coup légal

    C'est la seule étape que la démonstration doit défendre elle-même, et c'est un seul coup. Le disque n est dégagé, puisque l'étape précédente l'a dégagé. L'arrivée ne contient que des disques plus grands que n, puisque tout ce qui est plus petit est sur l'intermédiaire. Le coup est donc légal.

  3. Supposél'affirmation pour n − 1, de nouveau

    La même hypothèse, utilisée une seconde fois, ramène les disques 1 à n − 1 de l'intermédiaire vers l'arrivée. Légal pour la même raison qu'avant : le disque n, maintenant sous eux, est plus grand. Les n disques sont sur l'arrivée et rien d'autre n'a bougé — ce qui est l'affirmation pour n.

L'étape de récurrence, et la raison pour laquelle la démonstration tient en quatre phrases. Deux des trois panneaux ne font aucun travail nouveau : ils sont l'hypothèse pour n − 1, dépensée une fois dans chaque sens. Seul le panneau du milieu doit être défendu, et c'est un coup. Voilà ce qu'achète la récurrence : n disques coûtent exactement une idée nouvelle de plus que n − 1, et le cas de base n = 0 n'en coûte aucune, puisqu'il n'y a rien à déplacer.

La récurrence, résolue correctement

Soit T(n) le nombre de coups que l'algorithme joue pour n disques. La fonction lance deux appels sur n − 1 disques et joue un coup elle-même, et ne joue aucun coup quand il n'y a pas de disque :

T(n) = 2 · T(n − 1) + 1 T(0) = 0

Pour la résoudre, substituez la récurrence dans elle-même et cherchez le motif. Cela s'appelle dérouler, ou télescoper :

T(n) = 2T(n − 1) + 1   = 2(2T(n − 2) + 1) + 1 = 4T(n − 2) + 3   = 4(2T(n − 3) + 1) + 3 = 8T(n − 3) + 7   = 2ᵏ · T(n − k) + (2ᵏ − 1) après k substitutions. Prenez k = n : T(n) = 2ⁿ · T(0) + 2ⁿ − 1 = 2ⁿ − 1

Repérer un motif n'est pas le démontrer : confirmez-le par récurrence. T(0) = 0 = 2⁰ − 1. Et si T(n − 1) = 2ⁿ⁻¹ − 1, alors T(n) = 2(2ⁿ⁻¹ − 1) + 1 = 2ⁿ − 2 + 1 = 2ⁿ − 1. La forme close est exacte pour tout n.

Il existe une troisième voie, et c'est une image que vous avez déjà vue. L'arbre de récursion ci-dessus est un arbre binaire complet : la racine déplace n disques, les deux appels en dessous en déplacent n − 1 chacun, les quatre encore en dessous n − 2, et ainsi de suite. La profondeur k contient donc 2ᵏ appels, le dernier niveau est la profondeur n − 1, et chacun de ces appels joue exactement un coup. Additionnez les niveaux et le compte tombe comme une série géométrique :

T(n) = 1 + 2 + 4 + ⋯ + 2ⁿ⁻¹ n termes, chacun le double du précédent :   = 2ⁿ − 1

Trois méthodes — dérouler, le raisonnement par récurrence et l'arbre — et une seule réponse. Bon à savoir, car il en existe une quatrième, proposée sans arrêt, et elle est fausse.

Ce n'est pas une récurrence pour le Master Theorem

Une réponse courante — y compris dans des tutoriels très recopiés — résout ceci avec le Master Theorem. Il ne s'applique pas.

  • Le Master Theorem vaut pour T(n) = aT(n/b) + f(n) avec b > 1 : chaque sous-problème est une fraction de l'original, comme dans le tri fusion.
  • Cette récurrence est T(n) = 2T(n − 1) + 1 : le sous-problème est plus petit d'un disque, pas d'un facteur, donc il n'y a aucun b à y mettre.

Les récurrences de ce type ont leur propre résultat, dit de soustraction : quand T(n) = aT(n − b) + f(n) avec a > 1 et f un polynôme, T(n) croît au plus comme aⁿᐟᵇ fois ce polynôme. Cela concorde avec la réponse ci-dessus — mais dérouler est plus court que de s'en souvenir.

La même arithmétique compte les appels. Chaque appel avec n {">"} 0 disques en lance deux autres, il y a donc C(n) = 2C(n − 1) + 1 appels avec C(0) = 1, ce qui se déroule en 2ⁿ⁺¹ − 1. Soit 2ⁿ − 1 appels qui déplacent un disque, plus 2ⁿ qui reviennent immédiatement.

Complexité en temps et en mémoire

Chaque appel fait une quantité constante de travail en plus de ses deux appels récursifs : le temps d'exécution est donc proportionnel au nombre d'appels, soit Θ(2ⁿ). Aucun algorithme plus malin ne peut faire mieux, parce que la sortie elle-même fait 2ⁿ − 1 coups — et 2ⁿ − 1 est un minimum démontré, pas seulement le meilleur résultat connu. La démonstration qu'aucune solution n'est plus courte a sa propre page : pourquoi le minimum est 2ⁿ − 1.

La mémoire, c'est une autre histoire. Les appels n'existent pas tous en même temps : un appel se termine avant que son frère ne commence, donc la pile ne garde qu'un appel par niveau, de n jusqu'à 0. L'espace utilisé est Θ(n) — soixante-cinq blocs de pile pour les soixante-quatre disques de la légende, ce que n'importe quel ordinateur encaisse sans sourciller. C'est le temps qui manque : 18 446 744 073 709 551 615 coups à un milliard par seconde font encore 584 ans.

Ce nombre est celui de la légende, et la légende est plus jeune qu'elle n'en a l'air. Un temple où des prêtres déplacent soixante-quatre disques d'or, et la fin du monde quand ils auront fini, apparaît pour la première fois imprimé en 1884 — dans un article vantant un casse-tête mis en vente à Paris l'année précédente, sous le nom d'un professeur qui n'existait pas. Au rythme des moines, un coup par seconde, il reste 585 milliards d'années au monde, soit environ quarante fois l'âge actuel de l'univers. Le mathématicien derrière le casse-tête et l'histoire était Édouard Lucas, qui démontra à la main que 2¹²⁷ − 1 est premier, puis mourut à 49 ans d'une infection due à une coupure faite par une assiette qu'un serveur avait laissée tomber lors d'un banquet.

RécursifItératif
Coups joués2ⁿ − 12ⁿ − 1
Travail effectué2ⁿ⁺¹ − 1 appels2ⁿ − 1 tours de boucle
TempsΘ(2ⁿ)Θ(2ⁿ)
Mémoire supplémentaireΘ(n) de pile d'appelsΘ(n) — les tours, ou juste un compteur de n bits

Mettez des nombres dans ces formules et les deux sortes de coût se séparent. Chaque disque supplémentaire fait un peu plus que doubler les coups et les appels, et ajoute un seul bloc à la profondeur maximale de la pile :

DisquesCoupsAppelsPile la plus profondeÀ un milliard de coups par seconde
371547 nanosecondes
41531515 nanosecondes
53163631 nanosecondes
101 0232 047111 microseconde
201 048 5752 097 151211 milliseconde
324 294 967 2958 589 934 591334 secondes
6418 446 744 073 709 551 61536 893 488 147 419 103 23165584 ans

La calculatrice des coups minimaux donne les coups et le temps pour n'importe quel nombre de disques, et la dernière ligne — la taille de la légende — a sa propre page.

Sans récursivité

Les mêmes coups peuvent être produits par une simple boucle avec deux règles :

  • Coups impairs : le plus petit disque avance d'une tour sur un circuit fixe — A → C → B pour un nombre impair de disques, A → B → C pour un nombre pair.
  • Coups pairs : jouez le seul coup légal qui ne touche pas au plus petit disque. Il y en a toujours exactement un.

Ces deux règles reproduisent la solution récursive coup pour coup ; la page de la solution itérative montre pourquoi, et la joue sur un plateau.

Il existe une version plus étrange encore, qui ne stocke aucune tour : le disque, le départ et l'arrivée du coup m se lisent chacun sur les chiffres binaires de m. Ce lien, et le code de Gray qui s'y cache, sont dans Tours de Hanoï, binaire et code de Gray.

Depuis une position qui n'est pas le début

La fonction du manuel suppose que tous les disques partent de la tour de départ. Donnez-lui un casse-tête à moitié fait et elle n'a rien à en dire. Un cas de plus règle cela. Regardez le plus grand disque :

  • Déjà sur la tour qu'il doit atteindre ? Il n'a besoin d'aucun coup. Résolvez les disques plus petits en les envoyant sur cette même tour.
  • Ailleurs ? Dégagez les disques plus petits vers la tour qui n'est ni celle où il est ni celle où il va, déplacez-le, et continuez comme avant.

Cette version trouve la solution la plus courte depuis n'importe quelle position légale, et c'est elle que fait tourner le solveur des Tours de Hanoï.

Testez-vous

Trois questions auxquelles la page a déjà répondu. Cherchez la réponse avant d'ouvrir — si l'une ne vient pas, la section nommée avec la réponse est celle à relire.

  1. Trois disques font sept coups. Combien en font 5 disques, et combien d'entre eux déplacent le disque 5 lui-même ?

    Voir la réponse

    31 coups, soit 2⁵ − 1 — et exactement un d'entre eux déplace le plus grand disque. Tous les disques sauf le plus grand bougent plus d'une fois, parce que chacune des deux moitiés de la solution doit les déplacer. La récurrence, résolue correctement.

  2. hanoi(3, A, C, B) lance hanoi(2, A, B, C) comme premier appel. Pourquoi l'arrivée de cet appel est-elle B plutôt que C ?

    Voir la réponse

    Parce que le disque 3 va en C, et qu'il ne peut s'y poser que si C est vide. Les deux disques posés dessus doivent donc être garés ailleurs, et cet ailleurs ne peut être que B. La tour que vise un appel devient celle que son premier appel doit garder libre — c'est pourquoi deux des trois arguments échangent leur place en entrant. Ce qu'il faut observer.

  3. Résoudre 20 disques fait 2 097 151 appels. Combien d'entre eux sont sur la pile au même instant, au maximum ?

    Voir la réponse

    21 — un par nombre de disques, de 20 à 0. Un appel se termine avant que son frère ne commence, donc les appels n'existent jamais tous en même temps : le travail est exponentiel et la mémoire est linéaire. Complexité en temps et en mémoire.

L'algorithme dans votre langage

La même fonction dans six langages, chacun avec un programme exécutable, une explication ligne à ligne, le plateau qui tourne à côté, et ce qui est propre à ce langage — ce qui devient important dès qu'on essaie de compter les coups pour soixante-quatre disques. Ces six pages sont en anglais.

Les mots de cette page

Cas de base
La version du problème qu'une fonction récursive traite directement, sans s'appeler elle-même. C'est ce qui arrête la récursivité. Ici c'est n = 0 : aucun disque à déplacer, donc rien à faire, et l'appel revient. Toute chaîne d'appels, si profonde soit-elle, se termine sur l'un d'eux.
Cas récursif
Tout ce qui n'est pas le cas de base : la partie de la fonction qui résout une version plus petite du même problème et construit sa réponse à partir d'elle. Ici, ce sont les trois lignes qui dégagent n − 1 disques, déplacent le disque n, et ramènent ces n − 1 disques.
Pile d'appels
La liste des appels de fonction commencés et pas encore terminés, le plus récent en dernier. Un appel est empilé quand il commence et dépilé quand il revient : la pile est donc la façon dont un programme se souvient d'où revenir. C'est ce que dessine le panneau ci-dessus, et c'est de la vraie mémoire — d'où le fait que sa profondeur soit le coût en espace de l'algorithme.
Bloc de pile
Une entrée de la pile d'appels : les arguments et les variables locales d'un seul appel. Chaque ligne de la pile dans le panneau est un bloc, avec son propre n et sa propre idée de quelle tour est le départ, l'arrivée et l'intermédiaire. Soixante-quatre disques demandent soixante-cinq blocs, ce qui n'est rien ; le problème, ce sont les coups.
Télescoper
Aussi appelé dérouler : résoudre une récurrence en la substituant dans elle-même jusqu'à ce que le motif saute aux yeux, puis en fixant le nombre de substitutions pour qu'il ne reste que la valeur initiale connue. On parle de télescopage parce que les termes du milieu se replient comme les sections d'une longue-vue.
Θsaid “grand thêta”
Un ordre de croissance qui est à la fois une borne supérieure et une borne inférieure : Θ(2ⁿ) veut dire que le coût croît comme 2ⁿ, pas seulement qu'il ne croît pas plus vite. O(2ⁿ) ne promettrait que le plafond. Θ est ici la notation honnête, parce que cet algorithme joue vraiment 2ⁿ − 1 coups — il ne peut pas avoir de la chance et finir plus tôt.

Questions fréquentes

Comment fonctionne l'algorithme récursif des Tours de Hanoï ?

Pour déplacer n disques d'une tour de départ vers une tour d'arrivée, il déplace d'abord les n − 1 disques du dessus sur la tour intermédiaire, puis le plus grand disque vers l'arrivée, puis les n − 1 disques de l'intermédiaire vers l'arrivée. Chacun de ces deux transferts plus petits est le même problème avec un disque de moins, donc la fonction s'appelle elle-même pour eux, et un appel sans disque à déplacer se contente de revenir.

Les Tours de Hanoï, est-ce diviser pour régner ou de la programmation dynamique ?

C'est diviser pour régner. Le problème est divisé en deux sous-problèmes de n − 1 disques, chacun est réglé par récursivité, et l'étape de combinaison est un unique déplacement du plus grand disque. Ce n'est pas de la programmation dynamique, et la mémoïsation n'aide pas — même si les sous-problèmes se répètent réellement. Avec trois tours, il n'y a que six façons de nommer un départ, une arrivée et un intermédiaire, donc hanoi(1, A, C, B) est bel et bien appelé deux fois pendant la résolution à trois disques. Le mettre en cache n'apporte rien, car la réponse à un sous-problème est une liste de 2ᵏ − 1 coups, et rejouer une liste stockée de cette longueur coûte autant que la produire. Ici, le travail, c'est la sortie, et aucune table ne raccourcit la sortie.

Le Master Theorem s'applique-t-il à la récurrence des Tours de Hanoï ?

Non. Le Master Theorem couvre les récurrences de la forme T(n) = aT(n/b) + f(n), où chaque sous-problème est une fraction de l'original. La récurrence des Tours de Hanoï est T(n) = 2T(n − 1) + 1 : le sous-problème est plus petit d'un disque, pas d'un facteur. La dérouler donne directement T(n) = 2ⁿ − 1, et un raisonnement par récurrence le confirme.

Jusqu'où descend la récursivité ?

Avec un cas de base à n = 0, il y a au plus n + 1 appels sur la pile en même temps : un par nombre de disques, de n à 0. Avec un cas de base à n = 1, c'est n. Dans les deux cas la profondeur croît linéairement, et c'est pourquoi la solution récursive n'a besoin que de O(n) de mémoire alors qu'elle joue un nombre exponentiel de coups.

Combien d'appels de fonction la solution récursive fait-elle ?

2ⁿ⁺¹ − 1 appels quand le cas de base est n = 0 : les 2ⁿ − 1 appels qui déplacent un disque, plus 2ⁿ appels qui ne trouvent rien à déplacer et reviennent. Trois disques font 15 appels. Écrit avec un cas de base à n = 1, le même programme fait exactement 2ⁿ − 1 appels, un par coup.

L'algorithme itératif est-il plus rapide que le récursif ?

Pas d'une façon qui compte. Les deux doivent produire 2ⁿ − 1 coups, donc les deux prennent un temps proportionnel à 2ⁿ. La version itérative évite le coût des appels de fonction et ne peut pas épuiser la pile, mais la récursivité ici n'a que n niveaux de profondeur : épuiser la pile n'est pas un risque réel pour un nombre de disques que vous pourriez attendre.

À quoi ressemble l'algorithme des Tours de Hanoï en organigramme ?

À pas grand-chose, et c'est la réponse honnête. Un organigramme montre le contrôle qui traverse des cases une fois, et la deuxième case de cet algorithme est l'organigramme tout entier de nouveau — trois niveaux plus bas, avec deux des tours échangées et un disque de moins. Un organigramme n'a pas de notation pour cela : un organigramme de la solution récursive est donc soit faux, soit l'image d'un seul appel avec deux cases marquées « appeler hanoi », ce qui n'apprend rien de plus que le pseudocode en quatre lignes. Le schéma qui, lui, l'exprime, c'est l'arbre de récursion : chaque appel dessiné sous l'appel qui l'a lancé, chaque case au-dessus du coup qu'elle joue. Il est sur cette page, et lire ses cases de gauche à droite donne les coups dans l'ordre.

L'algorithme peut-il résoudre un casse-tête déjà à moitié fait ?

La version du manuel ne le peut pas : elle suppose que tous les disques partent d'une même tour. Elle se généralise avec un cas de plus — si le plus grand disque est déjà sur son arrivée, passez-le et résolvez les disques du dessus — et cette version trouve la solution la plus courte depuis n'importe quelle position légale. Le solveur de ce site fait tourner exactement cela. Ouvrir le solveur →