Le problème des tours de Hanoï est un divertissement mathématique conçu en
1883 par le mathématicien Édouard Lucas. De nos jours, c'est une excellente
introduction à la récursion en mathématiques et à la récursivité en
programmation.
Règles
Les règles sont les suivantes :
- On dispose de trois piquets et de plusieurs disques de tailles différentes.
- Initialement, tous les disques sont enfilés sur le premier piquet, du plus petit au plus grand, avec le plus petit au sommet.
- Le but du jeu est de déplacer la pile de disques du premier au troisième piquet, sans jamais qu'un disque plus petit ne soit placé sous un disque plus gros que lui.
- Un mouvement consiste à prendre un disque au sommet de l'un des trois piquets et à le déposer sur un autre piquet, si cela est possible.
Un joueur humain raisonnable peut espérer résoudre le problème pour un nombre
de disques entre deux et cinq (31 coups) voire six (63 coups). En réalité, le
nombre de coups croît de façon exponentielle, comme nous le verrons plus loin.
Résolution
Nous allons décomposer le problème récursivement. L'idée est que, pour
transférer tous les n disques sur le troisième piquet, il suffit de
transférer les n−1 premiers sur le piquet intermédiaire, puis de
déplacer le plus gros disque du premier au dernier piquet avant de déplacer les
n−1 disques du piquet intermédiaire sur le dernier piquet. Ainsi,
résoudre le problème avec n disques revient à résoudre le problème pour
n−1 disques, puis à effectuer quelques opérations bien prcises. Comme
il est facile de trouver la solution pour 1 disque, on peut raisonner
récursivement.
Pseudo-code
Supposons que l'on dispose d'une fonction deplacer(piquet A, piquet B)
qui
déplace le disque au sommet du piquet A sur le piquet B, ou retourne une erreur
lorsque cela est impossible. On peut alors traduire le raisonnement précédent
sous forme de pseudo-code :
Fonction hanoi(source, inter, dest, nombre_disques)
Si nombre_disques = 1 :
deplacer(source, dest)
Sinon :
hanoi(source, dest, inter, nombre_disques - 1)
deplacer(source, dest)
hanoi(inter, source, dest, nombre_disques - 1)
Ici, source
désigne le piquet de départ du raisonnement, inter
le
piquet intermédiaire qu'on utilise pour stocker les n−1 disques, et
dest
est le piquet de destination sur lequel on souhaite déplacer tous les
disques.
Implémentation en OCaml
On peut écrire la fonction deplacer de manière à afficher la liste des
mouvements pour résoudre le problème. C'est ce qu'on fait dans l'exemple OCaml qui suit :
let deplacer source dest =
print_string (source ^ " => " ^ dest);
print_newline();;
let rec hanoi source inter dest n =
let m1 = if n = 1 then 0 else hanoi source dest inter (pred n) in
deplacer source dest;
let m2 = if n = 1 then 0 else hanoi inter source dest (pred n) in
(m1 + m2 + 1);;
On remarquera que la fonction hanoi
retourne, au passage, le nombre de
mouvements effectués. Essayons-là avec quatre piquets en utilisant des noms de
camps fortifiés romains :
hanoi "Babaorum" "Laudanum" "Petibonum" 4;;
Cet appel de fonction imprime :
Babaorum => Laudanum
Babaorum => Petibonum
Laudanum => Petibonum
Babaorum => Laudanum
Petibonum => Babaorum
Petibonum => Laudanum
Babaorum => Laudanum
Babaorum => Petibonum
Laudanum => Petibonum
Laudanum => Babaorum
Petibonum => Babaorum
Laudanum => Petibonum
Babaorum => Laudanum
Babaorum => Petibonum
Laudanum => Petibonum
Cette solution comporte 15=24−1 coups. Nous allons maintenant voir
pourquoi.
Nombre de coups nécessaires
Notons Cn le nombre de coups nécessaires pour résoudre le problème à
n disques. On a directement :
C1=1Mais nous avons vu que, pour résoudre le problème avec n+1 disques,
il fallait déplacer les n premiers disques sur le piquet intermédiaire,
puis déplacer le plus gros disque sur le piquet final avec de déplacer les
n autres disques du piquet intermédiaire au piquet final. Traduit
mathématiquement, ce processus donne :
Cn+1=2×Cn+1Il s'agit d'une suite arithmético-géométrique
que l'on sait résoudre de manière générique : sa solution est la somme de la
solution homogène λ2n et d'une solution particulière, ici la
constante -1. Le premier terme de la série C1=1 nous donne
λ=1, de sorte que :
Cn=2n−1Donc, quand n devient grand, on a :
Cn∼2nEt donc un nombre de coups exponentiel. Par exemple, C10≈1.000 et C20≈1.000.000.
Pour aller plus loin
Le petit tour d'horizon de ce classique touche à sa fin. Il y aurait encore
beaucoup à dire sur ce problème, mais nous nous cantonnerons ici à des notions
et méthodes générales. Pour de plus amples informations, vous pouvez consulter
les articles qui suivent :
Discussion
Feel free to post a comment by e-mail using the form below. Your e-mail address will not be disclosed.