web-dev-qa-db-fra.com

Tours de Hanoi avec piquets K

Le problème Tours de Hanoi est un problème classique de récursivité. On vous donne 3 piquets avec des disques sur l’un d’eux, et vous devez déplacer tous les disques d’un piquet à un autre en suivant les règles données. Vous devez également le faire avec le nombre minimum de mouvements.

Voici un algorithme récursif qui résout le problème:

void Hanoi3(int nDisks, char source, char intermed, char dest)
{
    if( nDisks > 0 )
    {
        Hanoi3(nDisks - 1, source, dest, intermed);
        cout << source << " --> " << dest << endl;
        Hanoi3(nDisks - 1, intermed, source, dest);
    }
}


int main()
{
    Hanoi3(3, 'A', 'B', 'C');

    return 0;
}

Maintenant, imaginez le même problème, seulement avec 4 piquets, ajoutons donc un autre pion intermédiaire. Face au problème de devoir choisir quelle cheville intermédiaire à choisir à un moment donné, nous choisirons celle qui reste le plus à gauche, au cas où plus de 1 serait libre.

J'ai l'algorithme récursif suivant pour ce problème:

void Hanoi4(int nDisks, char source, char intermed1, char intermed2, char dest)
{
    if ( nDisks == 1 )
        cout << source << " --> " << dest << endl;
    else if ( nDisks == 2 )
    {
        cout << source << " --> " << intermed1 << endl;
        cout << source << " --> " << dest << endl;
        cout << intermed1 << " --> " << dest << endl;
    }
    else
    {
        Hanoi4(nDisks - 2, source, intermed2, dest, intermed1);
        cout << source << " --> " << intermed2 << endl;
        cout << source << " --> " << dest << endl;
        cout << intermed2 << " --> " << dest << endl;
        Hanoi4(nDisks - 2, intermed1, source, intermed2, dest);
    }
}

int main()
{
    Hanoi4(3, 'A', 'B', 'C', 'D');

    return 0;
}

Maintenant, ma question est de savoir comment je généraliserais cette approche récursive pour travailler avec des variables K? La fonction récursive recevrait un char[] qui contiendrait les étiquettes de chaque pile, de sorte que la fonction ressemblerait à ceci:

void HanoiK(int nDisks, int kStacks, char labels[]) { ... }

Je connais l'algorithme Frame-Stewart, qui est probablement optimal mais non prouvé et qui vous donne le nombre de mouvements. Cependant, je suis intéressé par une solution strictement récursive qui suit le modèle des solutions récursives pour 3 et 4 piquets, ce qui signifie qu’elle imprime les déplacements réels.

Pour moi du moins, le pseudocode de l'algorithme Frame-Stewart présenté sur Wikipedia est plutôt abstrait et je n'ai pas réussi à le traduire en code qui imprime les déplacements. J'accepterais une implémentation de référence de cela (pour random k), ou même un pseudocode plus détaillé.

J'ai essayé de concevoir un algorithme qui permute le tableau d'étiquettes en conséquence, mais je n'ai pas eu la chance de le faire fonctionner. Toutes les suggestions sont appréciées.

Mettre à jour:

Cela semble être beaucoup plus facile à résoudre dans un langage fonctionnel ..__ Voici une implémentation de F # basée sur la solution Haskell de LarsH:

let rec HanoiK n pegs = 
    if n > 0 then 
        match pegs with
        | p1::p2::rest when rest.IsEmpty            
            ->  printfn "%A --> %A" p1 p2
        | p1::p2::p3::rest when rest.IsEmpty        
            ->  HanoiK (n-1) (p1::p3::p2::rest)
                printfn "%A --> %A" p1 p2
                HanoiK (n-1) (p3::p2::p1::rest)    
        | p1::p2::p3::rest when not rest.IsEmpty    
            ->  let k = int(n / 2)
                HanoiK k (p1::p3::p2::rest)
                HanoiK (n-k) (p1::p2::rest)
                HanoiK k (p3::p2::p1::rest)

let _ =
    HanoiK 6 [1; 2; 3; 4; 5; 6]

Et sans traiter 3 piquets comme une affaire Edge:

let rec HanoiK n pegs = 
    if n > 0 then 
        match pegs with
        | p1::p2::rest when rest.IsEmpty            
            ->  printfn "%A --> %A" p1 p2
        | p1::p2::p3::rest     
            ->  let k = if rest.IsEmpty then n - 1 else int(n / 2) 
                HanoiK k (p1::p3::p2::rest)
                HanoiK (n-k) (p1::p2::rest)
                HanoiK k (p3::p2::p1::rest)

Notez que cela ne gère pas les cas dégénérés pour lesquels il n'y a pas de solution, telle que HanoiK 2 [1; 2]

29
IVlad

Voici une implémentation dans Haskell (update: s'est occupé du cas 3-peg en faisant k = n-1 quand r = 3):

-- hanoi for n disks and r pegs [p1, p2, ..., pr]
hanoiR :: Int -> [a] -> [(a, a)]

-- zero disks: no moves needed.
hanoiR 0 _ = []

-- one disk: one move and two pegs needed.
hanoiR 1 (p1 : p2 : rest) = [(p1, p2)] -- only needed for smart-alecks?

{-
-- n disks and 3 pegs -- unneeded; covered by (null rest) below.
hanoiR n [p1, p2, p3] =
    hanoiR (n - 1) [p1, p3, p2] ++
    [(p1, p2)] ++
    hanoiR (n - 1) [p3, p2, p1]
-}

-- n disks and r > 3 pegs: use Frame-Stewart algorithm
hanoiR n (p1 : p2 : p3 : rest) =
    hanoiR k (p1 : p3 : p2 : rest) ++
    hanoiR (n - k) (p1 : p2 : rest) ++
    hanoiR k (p3 : p2 : p1 : rest)
    where k
        | null rest   = n - 1
        | otherwise   = n `quot` 2

Alors chargez ceci dans GHCi et entrez

hanoiR 4 [1, 2, 3, 4]

C'est à dire. exécutez les tours de Hanoi avec 4 disques et 4 piquets. Vous pouvez nommer les 4 piquets comme vous le souhaitez, par exemple:.

hanoiR 4 ['a', 'b', 'c', 'd']

Le résultat:

[(1,2),(1,3),(2,3),(1,4),(1,2),(4,2),(3,1),(3,2),(1,2)]

C'est à dire. déplacez le disque supérieur de la cheville 1 à la cheville 2, puis le disque supérieur de la cheville 1 à la cheville 3, etc.

Je suis assez nouveau à Haskell, alors je dois admettre que je suis fier que cela fonctionne. Mais il se peut que je commette des erreurs stupides, les commentaires sont les bienvenus.

Comme vous pouvez le constater d'après le code, l'heuristique de k est simplement floor (n/2). Je n'ai pas essayé d'optimiser k, même si n/2 semblait être une bonne hypothèse.

J'ai vérifié l'exactitude de la réponse pour 4 disques et 4 piquets. Il est trop tard dans la nuit pour que je vérifie davantage, sans écrire de simulateur. (@ _ @) Voici quelques résultats supplémentaires:

ghci>  hanoiR 6 [1, 2, 3, 4, 5]
[(1,2),(1,4),(1,3),(4,3),(2,3),(1,4),(1,5),(1,2),
 (5,2),(4,2),(3,1),(3,4),(3,2),(4,2),(1,2)]
ghci>  hanoiR 6 [1, 2, 3, 4]
[(1,2),(1,4),(1,3),(4,3),(2,3),(1,2),(1,4),(2,4),(1,2),
 (4,1),(4,2),(1,2),(3,1),(3,4),(3,2),(4,2),(1,2)]
ghci>  hanoiR 8 [1, 2, 3, 4, 5]
[(1,3),(1,2),(3,2),(1,4),(1,3),(4,3),(2,1),(2,3),(1,3),(1,2),
 (1,4),(2,4),(1,5),(1,2),(5,2),(4,1),(4,2),(1,2),
 (3,2),(3,1),(2,1),(3,4),(3,2),(4,2),(1,3),(1,2),(3,2)]

Cela clarifie-t-il l'algorithme?

Vraiment la pièce essentielle est 

hanoiR k (p1 : (p3 : (p2 : rest))) ++      -- step 1; corresponds to T(k,r)
hanoiR (n-k) (p1 : (p2 : rest)) ++         -- step 2; corresponds to T(n-k, r-1)
hanoiR k (p3 : (p2 : (p1 : rest)))         -- step 3; corresponds to T(k,r)

où nous concaténons les séquences de mouvements pour les étapes 1, 2 et 3 de l'algorithme Frame-Stewart. Afin de déterminer les déplacements, nous annotons les étapes de F-S comme suit:

  • Classiquement, lorsque hanoi est appelé, l’objectif est défini (sans perte de généralité) comme le transfert des disques du premier piquet au second piquet, en utilisant tous les piquets restants pour un stockage temporaire. Nous utilisons cette convention lors de la récurrence pour définir la source, la destination et le stockage autorisé des sous-problèmes divisés et conquis.
  • Ainsi, la cheville source est p1 et la cheville de destination est p2. Tous les piquets restants sont disponibles en tant que stockage temporaire, pour le problème de niveau supérieur de Hanoi.
  • Étape 1: "Pour certains k, 1 <= k <n, transférez les k premiers disques sur une seule autre cheville": nous choisissons p3 comme "une seule autre cheville".
  • Ainsi, "sans perturber le pion qui contient maintenant les k meilleurs disques" (étape 2) signifie recurse en utilisant tous les pions sauf p3. C'est à dire. p1, p2 et le reste au-delà de p3.
  • "Transférer les k premiers disques vers la cheville de destination" (étape 3) signifie transférer de "l'autre cheville" (p3) à p2.

Cela a-t-il du sens?

16
LarsH

Pour résoudre les tours de Hanoi, tout ce que vous avez à faire est:

L'algorithme Frame Stewart n'est pas vraiment complexe. Pour l'essentiel, vous devez déplacer un certain nombre de disques (par exemple, la moitié) vers un piquet: Traitez ces disques comme leur propre tour séparée. Il est facile de définir la solution pour 1 ou 2 disques. Si vous déplacez la première moitié vers sa destination, vous déplacez la seconde moitié vers l’endroit où elle doit se terminer.

Vous pouvez continuellement la segmenter si vous voulez faciliter l'écriture (seul cas particulier devenant 1), mais sans un nombre important de piquets, cela ne fonctionnera pas.

De plus, si k >= 3, vous pouvez le résoudre exactement comme les tours à trois pics de Hanoi en ignorant simplement le reste des piquets, bien que cela ne soit pas optimal.

2
TaslemGuy

Hinze, Ralf. Perle fonctionnelle: La Tour d'Hanoi, http://www.comlab.ox.ac.uk/ralf.hinze/publications/ICFP09.pdf

Cette perle a pour but de démontrer le idées de complète et projective programmation utilisant les tours de Hanoi puzzle comme un exemple en cours d'exécution. Le puzzle a sa propre beauté, qui nous espère exposer en cours de route.

2
sclv

Le casse-tête Tours de Hanoi a été publié dans le monde occidental en 1883 par le mathématicien français Edouard Lucas, sous le nom de plume N. Lucas de Siam. La "légende" qui accompagnait le match déclarait qu'à Bénarès, pendant le règne de l'empereur Fo Hi, il y avait un temple indien avec une coupole qui marquait le centre du monde (Kashi Vishwanath). À l'intérieur du dôme, des prêtres (brahmanes) déplaçaient des disques d'or entre 3 aiguilles en diamant (tiges usées), hautes d'une coudée et aussi épaisses que le corps d'une abeille. Dieu (Brahma) a placé 64 disques d'or sur une aiguille au moment de la création. (Les disques ont été déplacés selon les lois immuables de Brahma pour les transférer d'un piquet à un autre) Il a été dit que, une fois leur tâche accomplie, l'univers prendrait fin. La légende varie selon les sites, mais elle est généralement cohérente . Les lois définies par Brahma sont les suivantes: 1) Un seul disque à la fois peut être déplacé 2) Aucun disque ne peut être placé sur un disque plus petit 3) Seul le disque du haut peut être retiré, puis placé au sommet d'une autre cheville et ses disques Le jeu se termine lorsque toute la pile de disques a été déplacée sur une autre cheville. Il a été rapidement découvert que la solution à 3 peg existait mais n’était pas résolue pour une solution à 4+ peg . En 1939, l’American Mathematical Monthly a organisé un concours pour résoudre les problèmes de recherche et de lecture et n disques. Deux ans plus tard, deux algorithmes distincts (mais égaux par la suite) ont été publiés par J. S. Frame et B. M. Stewart. Les deux n'ont pas encore été prouvés corrects, bien qu'ils soient généralement supposés corrects. Il n'y a pas encore eu d'avancée sur une solution appropriée . ***** Cela ne fonctionne que sur des problèmes à 3 chevilles ***** Le nombre minimum de déplacements pour une tour de n disques s'est rapidement révélé être égal à 2n-1, avec la solution récursive simple suivante: Indiquez le début, l’objectif et la temp. Pour déplacer n chevilles de la cheville de départ à la cheville objectif via la cheville temporaire: Pour n> 1, (I) Déplacez récursivement les n-1 premiers disques du début à la température via l'objectif . ii) Déplacez le nième disque du début à l’objectif . (iii) Déplacez récursivement les n-1 disques de temp en objectif via start . Cette solution prend 2n-1 coups: (1) Si n = 1, f(n) = 1 = 2n − 1 (2) Si n> 1, f(n) = 2 (2n − 1 -1) +1 = 2n −2 + 1 = 2n − 1 Le moyen facile de le résoudre ... 1,3,7,15,31 sont les solutions aux premiers n disques. Récursivement, cela ressemble à nk = 2nk-1 + 1. De là, on peut induire que n = 2n-1. Prouvant par induction, je vous laisse . ***** l'algorithme de base de Frame/Stewart ***** Pour m peg et n disques, choisissez un l tel que 0 ≤ l <n (alors que l est un entier qui minimise les étapes de la configuration suivante) .......... • Déplacez les l meilleurs disques du point de départ à un cheville intermédiaire; ceci peut être accompli en mouvements Hk (l) (puisque les n disques l inférieurs n'interfèrent pas du tout avec les mouvements) . • Déplacez les n disques l inférieurs du pion de départ au pion objectif en utilisant Hk − 1. (n-l) se déplace. (Un piquet étant occupé par une tour de disques plus petits, il ne peut pas être utilisé à ce stade.) c'est essentiellement Hk (n) = 2Hk (l) + Hk-1 (n-1) -----> le l minimisé ***** Facile comme bonjour! Ne pas!***** Vérifier que cela fonctionne par rapport à notre solution à 3 chevilles devrait être simple. En utilisant k = 2; nous fixons H2 (0) = 0, H2 (1) = 1 et H2 (n> 1) =. .__ Pour k = 3, on peut définir l = n-1. (Cela fait que notre H2 (n-1) devient fini) Cela nous permettra d'écrire H3 (n) = 2H3 (n-1) + H2 (1), ce qui équivaut à 2n-1. C'est un peu un jeu de mots, mais ça marche . ***** Une description légèrement différente pour aider à clarifier ***** L'algorithme Frame – Stewart, qui donne une solution probablement optimale pour quatre chevilles (ou même davantage), est décrit ci-dessous: Définissez H (n, m) comme le nombre minimum de déplacements nécessaires pour transférer n disques à l'aide de m pegs L’algorithme peut être décrit de manière récursive: 1. Pour certains l, 1

`Option VBASupport 1
Option Explicit
Dim n as double
dim m as double
dim l as double
dim rx as double
dim rxtra as double
dim r as double
dim x as double
dim s1 as double
dim s2 as double
dim i as integer
dim a ()
dim b ()
dim c ()
dim d ()
dim aa as double
dim bb as double
dim cc as double
dim dd as double
dim total as double

Sub Hanoi
on error goto errorhandler

m=inputbox ("m# pegs=??")
n=inputbox ("n# discs=??")
x=-1
l=m-1
rx=1
s1=0
s2=0
aa=0

while n>rx
        x=x+1
        r=(l+x)/(x+1)
        rx=r*rx
wend
rx=1
for i=0 to x-1
        r=(l+i)/(i+1)
        rx=r*rx
        redim a (-1 to x)
        redim b (-1 to x)
        redim c (-1 to x)
        redim d (-1 to x)
            a(i)=rx
            b(i)=i
            bb=b(i)
            c(i)=rx-aa
            aa=a(i)
            cc=c(i)
            d(i)=cc*2^bb
            dd=d(i)
            s1=s1+dd
next

rxtra=n-aa
s2=rxtra*2^(bb+1)
total = 2*(s1+s2)-1
msgbox total

exit sub
errorhandler: msgbox "dang it!!"
'1, 3, 5, 9, 13, 17, 25, 33 first 8 answers for 4 peg
'16=161,25=577,32=1281,64=18433
End Sub`

Divulgation: Ces sources ont été utilisées pour confirmer les réponses et pour l'historique du problème. Il est difficile d'indiquer avec exactitude le montant dû car plusieurs sites ont été utilisés pour vérifier ... de sorte qu'ils constituent toutes les sources de nombreuses parties de l'historique.

0
Molumpy