it-swarm.com.de

Türme von Hanoi mit K-Stiften

Das Towers of Hanoi Problem ist ein klassisches Problem für die Rekursion. Sie erhalten 3 Pflöcke mit Datenträgern auf einem davon, und Sie müssen alle Datenträger von einem Pflock zu einem anderen verschieben, indem Sie die angegebenen Regeln befolgen. Sie müssen dies auch mit der minimalen Anzahl von Zügen tun.

Hier ist ein rekursiver Algorithmus, der das Problem löst:

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;
}

Stellen Sie sich nun das gleiche Problem vor, nur mit 4 Stiften. Deshalb fügen wir einen weiteren Zwischenstöpsel hinzu. Wenn Sie sich mit dem Problem konfrontiert sehen müssen, welchen Zwischenstift Sie an einer Stelle auswählen müssen, wählen wir den ganz linken aus, falls mehr als 1 frei ist.

Ich habe den folgenden rekursiven Algorithmus für dieses Problem:

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;
}

Nun, meine Frage ist, wie würde ich diesen rekursiven Ansatz verallgemeinern, um für K-Stöpsel zu arbeiten? Die rekursive Funktion würde einen char[] erhalten, der die Beschriftungen der einzelnen Stapel enthalten würde. Die Funktion würde also etwa wie folgt aussehen:

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

Ich kenne den Frame-Stewart-Algorithmus, der höchstwahrscheinlich optimal ist, sich aber nicht bewährt hat und der Ihnen die number von Zügen gibt. Ich interessiere mich jedoch für eine streng rekursive Lösung, die dem Muster der rekursiven Lösungen für 3 und 4 Stifte folgt, das heißt, die tatsächlichen Bewegungen werden gedruckt.

Für mich ist der Pseudocode des Frame-Stewart-Algorithmus, der auf Wikipedia vorgestellt wird, ziemlich abstrakt, und ich konnte ihn nicht in Code umwandeln, der die Züge druckt. Ich würde eine Referenzimplementierung davon (für zufällige k) oder sogar noch detaillierteren Pseudocode akzeptieren.

Ich habe versucht, einen Algorithmus zu entwickeln, der das Label-Array entsprechend permutiert, aber ich hatte kein Glück, es zum Laufen zu bringen. Anregungen sind willkommen.

Update:

Dies scheint viel einfacher in einer funktionalen Sprache zu lösen. Hier ist eine F # -Implementierung basierend auf LarsHs Haskell-Lösung:

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]

Und ohne 3 Klammern als Randfall zu behandeln:

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)

Beachten Sie, dass hier keine degenerierten Fälle behandelt werden, für die es keine Lösung gibt, z. B. HanoiK 2 [1; 2].

29
IVlad

Hier ist eine Implementierung in Haskell (Update: kümmerte sich um den 3-Pegelfall, indem er k = n-1 machte, wenn 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

Laden Sie diese also in GHCi und geben Sie sie ein

hanoiR 4 [1, 2, 3, 4]

Das heißt Führen Sie die Türme von Hanoi mit 4 Scheiben und 4 Stiften aus. Sie können die 4 Pflöcke beliebig benennen, z.

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

Die Ausgabe:

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

Das heißt Bewegen Sie die obere Scheibe von Stift 1 zu Stift 2, dann die obere Scheibe von Stift 1 zu Stift 3 usw.

Ich bin ziemlich neu in Haskell, also muss ich zugeben, dass ich stolz bin, dass dies funktioniert. Aber ich kann dumme Fehler haben, also ist Feedback willkommen.

Wie Sie dem Code entnehmen können, ist die Heuristik für k einfach floor (n/2). Ich habe nicht versucht, k zu optimieren, obwohl n/2 eine gute Vermutung war.

Ich habe die Richtigkeit der Antwort für 4 Festplatten und 4 Heringe bestätigt. Es ist zu spät in der Nacht, um mehr zu überprüfen, ohne einen Simulator zu schreiben. (@ _ @) Hier noch ein paar weitere Ergebnisse:

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)]

Verdeutlicht dies den Algorithmus?

Wirklich das wesentliche Stück ist 

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)

hier verketten wir die Bewegungsabläufe für die Schritte 1, 2 und 3 des Frame-Stewart-Algorithmus. Um die Bewegungen zu bestimmen, kommentieren wir die Schritte von F-S wie folgt:

  • Üblicherweise wird beim Aufruf von hanoi das Ziel definiert (ohne Verlust der Allgemeinheit), dass die Platten vom ersten zum zweiten Stift übertragen werden, wobei alle verbleibenden Stifte für die vorübergehende Speicherung verwendet werden. Bei der Rekursion verwenden wir diese Konvention, um die Quelle, das Ziel und die zulässige Speicherung der geteilten und eroberten Teilprobleme zu definieren.
  • Somit ist der Ursprungspeg p1 und der Zielpeg ist p2. Alle verbleibenden Stifte sind als temporärer Speicher für das Hanoi-Problem der obersten Ebene verfügbar.
  • Schritt 1, "Für einige k, 1 <= k <n, übertragen Sie die obersten k-Platten auf einen einzelnen anderen Stift": Wir wählen p3 als "einen anderen anderen Stift".
  • "Ohne den Stift, der jetzt die obersten k-Platten enthält, zu stören" (Schritt 2), bedeutet dies, unter Verwendung aller Stifte mit Ausnahme von p3 erneut zu rekursieren. Das heißt p1, p2 und der Rest jenseits von p3.
  • "Übertragen der obersten k Platten auf den Zielstift" (Schritt 3) bedeutet die Übertragung vom "anderen Stift" (p3) auf p2.

Ist das sinnvoll?

16
LarsH

Um die Türme von Hanoi zu lösen, müssen Sie nur Folgendes tun:

Der Frame-Stewart-Algorithmus ist nicht wirklich so komplex. Im Wesentlichen müssen Sie eine bestimmte Anzahl von Festplatten (z. B. die Hälfte) auf einen Stift verschieben: Behandeln Sie diese Festplatten wie ihren eigenen separaten Turm. Es ist einfach, die Lösung für 1 oder 2 Festplatten zu definieren, und wenn Sie die erste Hälfte an den Zielort verschieben, verschieben Sie die zweite Hälfte an die Stelle, an der sie enden muss.

Sie können es kontinuierlich segmentieren, wenn Sie das Schreiben vereinfachen möchten (der einzige Sonderfall wird 1), aber ohne eine große Anzahl von Stiften funktioniert es nicht.

Wenn k >= 3, können Sie es außerdem wie ein Turm mit drei Stiften von Hanoi lösen, indem Sie den Rest der Stifte einfach ignorieren, obwohl dies nicht optimal wäre.

2
TaslemGuy

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

Diese Perle zielt darauf ab, die Ideen der Vollkorn- und Projektivprogrammierung (.____.) Zu demonstrieren, wobei das Rätsel Türme von Hanoi Als laufendes Beispiel verwendet wird. Das Puzzle hat seine eigene Schönheit, die wir Auf dem Weg zeigen wollen.

2
sclv

Das Towers of Hanoi-Puzzle wurde 1883 vom französischen Mathematiker Edouard Lucas unter dem Pseudonym N. Lucas de Siam in der westlichen Welt veröffentlicht. Die "Legende", die das Spiel begleitete, besagte, dass es in Benares während der Regierungszeit von Kaiser Fo Hi einen indischen Tempel mit einer Kuppel gab, die den Mittelpunkt der Welt (Kashi Vishwanath) markierte. In der Kuppel bewegten (Brahmanen) Priester goldene Scheiben zwischen 3 Diamantnadelpunkten (abgenutzte Pfosten), eine Ellenhöhe und so dick wie der Körper einer Biene. Gott (Brahma) hat zum Zeitpunkt der Erstellung 64 Goldscheiben auf eine Nadel gesetzt. (Die Scheiben wurden gemäß den unveränderlichen Gesetzen von Brahma bewegt, um sie von einem Stift auf einen anderen zu übertragen.) Es wurde gesagt, dass das Universum, wenn sie ihre Aufgabe erfüllt hätten, zu Ende gehen würde. Die Legende variiert über mehrere Standorte hinweg, ist aber im Allgemeinen konsistent. Die von Brahma gesetzten "Gesetze" sind wie folgt: 1) Es kann jeweils nur eine Disc bewegt werden. 2 ) Keine Disc darf auf einer kleineren Disc platziert werden 3) Nur die obere Disc darf entfernt und dann an einem anderen Stift und den Discs Platziert werden. Das Spiel endet, wenn der gesamte Disc-Stapel eingelegt ist wurde auf einen anderen Stift verschoben. Es stellte sich schnell heraus, dass die 3-Peg-Lösung existiert, aber keine Lösung für eine 4-Peg-Lösung. Im Jahr 1939 veranstaltete der American Mathematical Monthly einen Wettbewerb, um nach und nach Peg- und n-Discs zu suchen. Zwei Jahre später wurden von J. S. Frame und B. M. Stewart zwei separate (aber später nachgewiesene gleichwertige) Algorithmen veröffentlicht. Beide haben sich noch nicht als richtig erwiesen, obwohl sie im Allgemeinen als richtig gelten. Es wurden noch keine Fortschritte bei einer richtigen Lösung gemacht. ***** Dies funktioniert nur bei 3-Stift-Problemen. ***** Die Mindestanzahl von Zügen für einen Turm mit n Platten Es zeigte sich schnell, dass es sich um 2n− 1 handelt, wobei die einfache rekursive Lösung folgendermaßen lautet: Beschriften Sie die drei Starts, das Ziel und die Temperatur. Um n Stifte vom Startsteg über den Temp-Steg zum Zielstöpsel zu verschieben: Für n> 1, (I) Verschieben Sie die obersten n-1-Scheiben rekursiv über das Tor vom Start zum Temp. (ii) Verschiebe die n-te Scheibe vom Start zum Ziel. (iii) Verschiebe die n-1 Scheiben rekursiv vom Start zum Ziel über den Start. Diese Lösung dauert 2n-1 bewegt: (1) Wenn n = 1, f(n) = 1 = 2n-1 (2) Wenn n> 1, f(n) = 2 ∗ (2n - 1 - 1) +1 = 2n - 2 + 1 = 2n - 1 Der einfachste Weg, um es zu lösen ... 1,3,7,15,31 sind die Lösungen zu den ersten n Scheiben. Rekursiv ähnelt nk = 2nk-1 + 1. Von dort aus können wir induzieren, dass n = 2n-1 ist. Beweisen durch Induktion überlasse ich Ihnen. ***** der grundlegende Frame/Stewart-Algorithmus ***** Wählen Sie für m peg und n Scheiben ein l so, dass 0 ≤ l <ist n (wohingegen l eine ganze Zahl ist, die die Schritte in der folgenden Konfiguration minimiert) ... • Bewegen Sie die oberen l-Scheiben vom Startzapfen zu einem Zwischenzapfen. Dies kann in Hk (l) -Zeichen ausgeführt werden (da die unteren n-l-Scheiben die Bewegungen überhaupt nicht stören). • Bewegen Sie die unteren n-l-Scheiben mit Hk vom Startpunkt zum Zielstift −1 (n - l) bewegt sich. (Da ein Zapfen von einem Turm mit kleineren Scheiben belegt ist, kann er in dieser Phase nicht verwendet werden.) • Bewegen Sie die Original-l-Scheiben vom Zwischenstecker zum Zielstift in Hk (l). ____.] Im Wesentlichen ist es also Hk (n) = 2Hk (l) + Hk-1 (n-1) -----> das l ist minimiert ***** Einfach wie Torte !! Nicht! ***** Es sollte einfach sein, zu überprüfen, ob es gegen unsere 3-Peg-Lösung funktioniert. Unter Verwendung von k = 2; wir setzen H2 (0) = 0, H2 (1) = 1 und H2 (n> 1) = ∞. Für k = 3 können wir l = n-1 setzen. (Dadurch wird unser H2 (n-1) endlich). Das erlaubt uns, H3 (n) = 2H3 (n-1) + H2 (1) zu schreiben, was 2n-1 entspricht. Es ist ein bisschen ein Spiel mit Wörtern, aber es funktioniert. ***** Eine etwas andere Beschreibung, um zu helfen, den Rahmen-Stewart-Algorithmus zu verdeutlichen Eine optimale Lösung für vier (oder sogar mehr) Stifte wird im Folgenden beschrieben: Definieren Sie H (n, m) als Mindestanzahl von Bewegungen, die erforderlich sind, um n Platten mit m Stiften zu übertragen. Der Algorithmus kann rekursiv beschrieben werden: 1. Für einige 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`

Offenlegung: Diese Quellen wurden verwendet, um die Antworten zu bestätigen und um einige Probleme zu behandeln. Es ist schwer zu sagen, wo genau es liegt, weil mehrere Websites zur Überprüfung verwendet wurden. Daher sind sie alle Quellen für viele Teile der Geschichte.

0
Molumpy