La solution récursive des tours de Hanoi fonctionne de telle sorte que si vous voulez déplacer N disques de la cheville A à C, vous déplacez d'abord N-1 de A à B, puis vous déplacez celui du bas à C, et enfin vous déplacez à nouveau N-1 disques de B à C. En substance,
hanoi(from, to, spare, N):
hanoi(from, spare, to, N-1)
moveDisk(from, to)
hanoi(spare, to, from, N-1)
Clairement, hanoi( _ , _ , _ , 1) prend un coup, et hanoi ( _ , _ , _ , k) prend autant de coups que 2 * hanoi( _ , _ , _ , k-1) + 1. La longueur de la solution croît donc dans la séquence 1, 3, 7, 15, ... C'est la même séquence que (1 << k) - 1, ce qui explique la longueur de la boucle dans l'algorithme que vous avez posté.
Si vous regardez les solutions elles-mêmes, pour N = 1, vous obtenez
FROM TO
; hanoi(0, 2, 1, 1)
0 2 movedisk
Pour N = 2, on obtient
FROM TO
; hanoi(0, 2, 1, 2)
; hanoi(0, 1, 2, 1)
0 1 ; movedisk
0 2 ; movedisk
; hanoi(1, 2, 0, 1)
1 2 ; movedisk
Et pour N = 3, on obtient
FROM TO
; hanoi(0, 2, 1, 3)
; hanoi(0, 1, 2, 2)
; hanoi(0, 2, 1, 1)
0 2 ; movedisk
0 1 ; movedisk
; hanoi(2, 1, 0, 1)
2 1 ; movedisk
0 2 ; movedisk ***
; hanoi(1, 2, 0, 2)
; hanoi(1, 0, 2, 1)
1 0 ; movedisk
1 2 ; movedisk
; hanoi(0, 2, 1, 1)
0 2 ; movedisk
En raison de la nature récursive de la solution, les colonnes FROM et TO suivent une logique récursive : si l'on prend l'entrée centrale des colonnes, les parties situées au-dessus et au-dessous sont des copies les unes des autres, mais avec des nombres permutés. Il s'agit d'une conséquence évidente de l'algorithme lui-même, qui n'effectue aucune opération arithmétique sur les nombres pairs, mais se contente de les permuter. Dans le cas de N=4, la rangée du milieu est à x=4 (marquée de trois étoiles ci-dessus).
Maintenant, l'expression (X & (X-1)) annule le bit le moins activé de X, de sorte qu'elle associe par exemple les nombres de 1 à 7 comme ceci :
1 -> 0
2 -> 0
3 -> 2
4 -> 0 (***)
5 -> 4 % 3 = 1
6 -> 4 % 3 = 1
7 -> 6 % 3 = 0
L'astuce est la suivante : comme la rangée du milieu est toujours à une puissance exacte de deux et a donc exactement un bit activé, la partie après la rangée du milieu est égale à la partie avant lorsque vous ajoutez la valeur de la rangée du milieu (4 dans ce cas) aux rangées (c'est-à-dire 4=0+4, 6=2+6). Ceci met en œuvre la propriété de "copie", l'ajout de la ligne du milieu met en œuvre la partie de permutation. L'expression (X | (X-1)) + 1 définit le bit zéro le plus bas qui a des uns à sa droite, et efface ces uns, elle a donc des propriétés similaires à celles attendues :
1 -> 2
2 -> 4 % 3 = 1
3 -> 4 % 3 = 1
4 -> 8 (***) % 3 = 2
5 -> 6 % 3 = 0
6 -> 8 % 3 = 2
7 -> 8 % 3 = 2
Pour savoir pourquoi ces séquences produisent effectivement les bons numéros de piquet, considérons la colonne FROM. La solution récursive commence avec hanoi(0, 2, 1, N), donc à la ligne du milieu (2 ** (N-1)) vous devez avoir movedisk(0, 2). Maintenant, par la règle de récursion, à (2 ** (N-2)) vous devez avoir déplacéisk(0, 1) et à (2 ** (N-1)) + 2 ** (N-2) déplacéisk (1, 2). Cela crée le motif "0,0,1" pour les chevilles de départ qui est visible avec différentes permutations dans le tableau ci-dessus (vérifiez les lignes 2, 4 et 6 pour 0,0,1 et les lignes 1, 2, 3 pour 0,0,2, et les lignes 5, 6, 7 pour 1,1,0, toutes des versions permutées du même motif).
Parmi toutes les fonctions qui ont cette propriété de créer des copies d'elles-mêmes autour des puissances de deux, mais avec des décalages, les auteurs ont sélectionné celles qui produisent modulo 3 les permutations correctes. Ce n'est pas une tâche trop difficile car il n'y a que 6 permutations possibles des trois entiers 0..2 et les permutations progressent dans un ordre logique dans l'algorithme. (X|(X-1))+1 n'est pas nécessairement lié au problème de Hanoi ou n'a pas besoin de l'être ; il suffit qu'il ait la propriété de copie et qu'il produise les permutations correctes dans l'ordre correct.