Algorithmus
Der Algorithmus der Türme von Hanoi
Eine kurze Funktion, und das klarste Beispiel für Rekursion, das die Informatik zu bieten hat. Führen Sie sie unten aus und sehen Sie jedem Aufruf zu, den sie macht.
Auf dieser Seite
- Die Idee
- Der Algorithmus
- Ein durchgespielter Lauf, Zug für Zug
- Ausführen
- Worauf zu achten ist
- Der Rekursionsbaum
- Warum er korrekt ist
- Die Rekursionsgleichung, richtig gelöst
- Zeit- und Speicherkosten
- Ohne Rekursion
- Aus einer Stellung, die nicht der Anfang ist
- Prüfen Sie sich selbst
- Der Algorithmus in Ihrer Sprache
- Die Wörter auf dieser Seite
- Häufige Fragen
Der Algorithmus der Türme von Hanoi bringt einen Stapel aus n Scheiben in 2ⁿ − 1 Zügen von einem Turm auf einen anderen, und das sind die wenigsten, die möglich sind. Fast immer wird er rekursiv geschrieben, weil das Rätsel selbst rekursiv ist: Das Problem, n Scheiben zu bewegen, enthält zwei Kopien des Problems, n − 1 zu bewegen, mit einem einzigen Zug dazwischen.
Die Idee
Die größte Scheibe ist die einzige, die auf nichts obenauf liegen kann — also fangen Sie mit ihr an. Sie kann sich nur bewegen, wenn nichts auf ihr liegt und nichts auf dem Turm liegt, zu dem sie soll. Das heißt: Jede andere Scheibe muss auf dem dritten Turm gestapelt sein. Die ganze Lösung besteht also aus drei Schritten:
- Bewegen Sie die n − 1 kleineren Scheiben vom Start auf den Hilfsturm.
- Bewegen Sie die größte Scheibe vom Start auf das Ziel.
- Bewegen Sie die n − 1 kleineren Scheiben vom Hilfsturm auf das Ziel.
Schritt 1 und 3 sind dasselbe Rätsel mit einer Scheibe weniger und einem anderen Turm in der Rolle des Hilfsturms. Lösen Sie die genauso, und die darin ebenso, bis der Stapel, der zu bewegen ist, leer ist. Wenn Ihnen Rekursion selbst neu ist: Rekursion, erklärt mit den Türmen von Hanoi fängt ganz vorn an.
Diese drei Schritte sind ein Teile-und-herrsche-Algorithmus, und dort ordnen die meisten Vorlesungen dieses Rätsel ein. Er teilt das Problem in zwei Teilprobleme, erledigt jedes per Rekursion und fügt sie mit dem billigsten Kombinationsschritt zusammen, den es gibt: einem einzigen Zug. Was er nicht ist, ist eine ausgewogene Teilung: Mergesort halbiert seine Eingabe, hier wird eine Scheibe abgezogen. Genau dieser eine Unterschied ist der Grund, warum sich das Master-Theorem nicht anwenden lässt — die Falle, um die es weiter unten geht.
Der Algorithmus
Ausgeschrieben ist das eine Funktion mit zwei Hälften. Der Basisfall ist die Fassung, die sie ohne Selbstaufruf beantwortet — hier n == 0, keine Scheibe zu bewegen, also nichts zu tun — und der rekursive Fall ist alles andere: die drei Zeilen, die das Rätsel zerlegen und die Teile wieder an dieselbe Funktion übergeben. Manche Fassungen hören bei n == 1 auf und bewegen die Scheibe dort; die Züge sind dieselben, und die Fassung unten hält den Zug auf einer einzigen Zeile.
In nummerierter Form, mit den Türmen nach der Aufgabe benannt, die sie im jeweiligen Aufruf haben, statt nach ihrem Buchstaben:
ALGORITHMUS TOH(n, source, target, spare)
- Wenn
n = 0, zurückkehren. Es gibt nichts zu bewegen. TOH(n − 1, source, spare, target)— die oberen n − 1 Scheiben aus dem Weg räumen. Sie gehen auf den Hilfsturm, also ist der Hilfsturm das Ziel dieses Aufrufs, und der Turm, auf den die größte Scheibe zusteuert, ist der, den es frei zu halten gilt.- Scheibe n von
sourcenachtargetbewegen. Das ist die einzige Zeile, die überhaupt etwas bewegt. TOH(n − 1, spare, target, source)— diese n − 1 Scheiben auf das Ziel bringen. Sie starten von dem Turm, der eben noch Hilfsturm war, und der Turm, auf dem alle Scheiben begonnen haben, ist nun der freizuhaltende.
Ein durchgespielter Lauf, Zug für Zug
Hier ist die Antwort dieses Algorithmus für drei Scheiben, bevor irgendetwas ausgeführt wird. Sieben Züge, sieben Bretter, und der Grund, warum jeder Zug genau dann kommen musste.
Phase 1 · den Weg frei machen
Scheibe 1, von A nach C. Scheibe 3 kann sich nicht bewegen, solange die beiden Scheiben auf ihr nicht auf B liegen, und Scheibe 2 kann nicht nach B, solange Scheibe 1 auf ihr liegt. Also zieht die kleinste Scheibe zuerst, nach C.
Phase 1 · den Weg frei machen
Scheibe 2, von A nach B. Scheibe 2 ist nun frei und B ist leer, also nimmt sie ihren Platz auf dem Hilfsturm ein.
Phase 1 · den Weg frei machen
Scheibe 1, von C nach B. Scheibe 1 kommt wieder auf Scheibe 2 herunter. Beide kleinen Scheiben liegen auf B, und Scheibe 3 ist frei — was der ganze Sinn der ersten drei Züge ist.
Phase 2 · die große Scheibe wechselt
Scheibe 3, von A nach C. Auf Scheibe 3 liegt nichts und auf C liegt nichts, also wechselt die größte Scheibe in einem einzigen Zug. Es ist der einzige der sieben, den der äußerste Aufruf selbst macht.
Phase 3 · oben wieder aufbauen
Scheibe 1, von B nach A. Derselbe Dreischritt noch einmal, andersherum: Jetzt ist B der Start und A, wo alle Scheiben angefangen haben, der Hilfsturm. Scheibe 1 geht zuerst Scheibe 2 aus dem Weg.
Phase 3 · oben wieder aufbauen
Scheibe 2, von B nach C. Scheibe 2 wechselt auf Scheibe 3. Das ist erlaubt, weil Scheibe 3 größer ist — die einzige Bedingung, auf die es je ankommt.
Phase 3 · oben wieder aufbauen
Scheibe 1, von A nach C. Scheibe 1 landet auf dem Stapel, und das Rätsel ist in 7 Zügen gelöst — 2³ − 1, und das sind die wenigsten möglichen.
Achten Sie darauf, was die Züge 1 bis 3 und die Züge 5 bis 7 gemeinsam haben: Beide sind das Zwei-Scheiben-Rätsel, gespielt zwischen einem anderen Paar von Türmen. Das ist die Rekursion, sichtbar in der Ausgabe, noch bevor das Programm überhaupt läuft — und der Grund, warum die Funktion unten nur eine einzige Zeile braucht, die eine Scheibe bewegt.
Ausführen
Dieselben sieben Züge noch einmal, diesmal mit dem Programm, das sie erzeugt. Jeder Schritt führt eine Zeile aus, und der Aufrufstapel listet jeden Aufruf, der begonnen und noch nicht geendet hat. Probieren Sie vier oder fünf Scheiben: Die Zahl der Aufrufe verdoppelt sich jedes Mal, und der Stapel wächst um eine Stufe.
hanoi(3, A, C, B)
3 Scheiben von A nach C bewegen.
Noch läuft nichts. Der erste Schritt macht diesen Aufruf.
Aufrufstapel
Tiefe 0 von höchstens 4
- Leer. Es wurde noch nichts aufgerufen.
Züge
7 insgesamt
Wählen Sie eine Zeile, um das Programm zu diesem Zug zu bringen.
| Zug | Scheibe | Von | Nach |
|---|---|---|---|
| 1 | 1 | A | C |
| 2 | 2 | A | B |
| 3 | 1 | C | B |
| 4 | 3 | A | C |
| 5 | 1 | B | A |
| 6 | 2 | B | C |
| 7 | 1 | A | C |
Alle 15 Aufrufe, in der Reihenfolge, in der das Programm sie macht
hanoi(3, A, C, B)Tiefe 1 · macht Zug 4hanoi(2, A, B, C)Tiefe 2 · macht Zug 2hanoi(1, A, C, B)Tiefe 3 · macht Zug 1hanoi(0, A, B, C)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(0, B, C, A)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(1, C, B, A)Tiefe 3 · macht Zug 3hanoi(0, C, A, B)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(0, A, B, C)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(2, B, C, A)Tiefe 2 · macht Zug 6hanoi(1, B, A, C)Tiefe 3 · macht Zug 5hanoi(0, B, C, A)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(0, C, A, B)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(1, A, C, B)Tiefe 3 · macht Zug 7hanoi(0, A, B, C)Tiefe 4 · n ist 0 — kehrt sofort zurückhanoi(0, B, C, A)Tiefe 4 · n ist 0 — kehrt sofort zurück
Worauf zu achten ist
- Die Rollen wandern. Turm B ist der Hilfsturm des ersten Aufrufs und das Ziel des Aufrufs, den dieser macht. Jede Rekursionsebene beschriftet die drei Türme neu, und dieses Umbeschriften ist der ganze Trick.
- Der Stapel wird nie tief. Bei drei Scheiben laufen nie mehr als 4 Aufrufe gleichzeitig — einer je Scheibenzahl von 3 bis 0 —, obwohl das Programm insgesamt 15 Aufrufe macht.
- Die Hälfte der Aufrufe tut nichts. Von diesen 15 Aufrufen sind 8 Aufrufe für null Scheiben und kehren sofort zurück. Sie sind der Preis für den einfachsten möglichen Basisfall.
- Die Zeile, die bewegt, läuft genau 2ⁿ − 1-mal. Siebenmal bei drei Scheiben, verteilt über die 43 Schritte des Laufs.
Der erste Punkt ist am schwersten zu erwischen, während das Programm läuft, denn auf dem Brett bewegt sich dabei nichts — nur die Namen unter den Türmen ändern sich. Hier steht er still: der erste Aufruf, die beiden darunter auf dem Weg zu Zug 1, und der Aufruf, der den Stapel zurückholt.
hanoi(3, A, C, B)Der erste Aufruf: alle 3 Scheiben von A nach C bringen. Damit bleibt B als Hilfsturm. Start A, Ziel C, Hilfsturm B.
hanoi(2, A, B, C)Sein erster Aufruf. Scheibe 3 kann sich nicht bewegen, solange die Scheiben 1 und 2 auf ihr liegen, und die können nicht nach C, wohin Scheibe 3 unterwegs ist. Also wird B, eben noch der Hilfsturm, zum Ziel, und C wird zum Hilfsturm. Start A, Ziel B, Hilfsturm C.
hanoi(1, A, C, B)Eine Ebene tiefer derselbe Tausch noch einmal. Um Scheibe 2 frei zu legen, geht Scheibe 1 nach C, und B ist wieder der Hilfsturm. Start A, Ziel C, Hilfsturm B.
hanoi(2, B, C, A)Scheibe 3 ist gewechselt. Der zweite Aufruf der Wurzel holt die Scheiben 1 und 2 von B auf sie herüber, also ist jetzt B der Start und A, wo alle Scheiben angefangen haben, der Hilfsturm. Start B, Ziel C, Hilfsturm A.
hanoi(3, A, C, B) und drei der Aufrufe darunter, in der Reihenfolge, in der sie laufen, jeder auf dem Brett, wie dieser Aufruf es vorfindet. Die schattierten Scheiben sind die, die der Aufruf bewegen muss. Die Türme bleiben, wo sie sind; nur ihre Namen ändern sich. Jeder Aufruf reicht seine drei Türme mit zweien davon vertauscht nach unten — Ziel und Hilfsturm an den ersten Aufruf, den er macht, Start und Hilfsturm an den zweiten — und mehr Umbeschriften gibt es nicht.Der zweite lässt sich leichter bezweifeln als prüfen, also hier der ganze Lauf mit der Höhe des Stapels bei jedem seiner Schritte:
- Der Lauf braucht 43 Schritte und macht insgesamt 15 Aufrufe.
- Die Zahl der Aufrufe auf dem Stapel steigt und fällt zwischen 0 und 4 und erreicht 4 bei 8 dieser Schritte — jedes Mal, wenn ein Aufruf ohne zu bewegende Scheibe beginnt und zurückkehrt.
- Tiefe 4 ist n + 1 für n = 3, und kein Schritt des Laufs geht darüber hinaus.
Der Rekursionsbaum
Zeichnen Sie jeden Aufruf als Kasten unter den Aufruf, der ihn gemacht hat, und das Programm wird zu einem Baum. Setzen Sie jeden Kasten über die Nummer des Zuges, den er macht, und etwas anderes wird sichtbar: Liest man die Kästen von links nach rechts, ergeben sie die Züge in der richtigen Reihenfolge. Bei drei Scheiben ist der eigene Zug des ersten Aufrufs — die größte Scheibe — Zug 4, genau in der Mitte, mit der vollständigen Drei-Zug-Lösung für zwei Scheiben auf jeder Seite.
- Zug 1: Der Aufruf mit n = 1, von A nach C, bewegt Scheibe 1.
- Zug 2: Der Aufruf mit n = 2, von A nach B, bewegt Scheibe 2, zwischen seinen beiden Aufrufen mit n = 1.
- Zug 3: Der Aufruf mit n = 1, von C nach B, bewegt Scheibe 1.
- Zug 4: Der Aufruf mit n = 3, von A nach C, bewegt Scheibe 3, zwischen seinen beiden Aufrufen mit n = 2.
- Zug 5: Der Aufruf mit n = 1, von B nach A, bewegt Scheibe 1.
- Zug 6: Der Aufruf mit n = 2, von B nach C, bewegt Scheibe 2, zwischen seinen beiden Aufrufen mit n = 1.
- Zug 7: Der Aufruf mit n = 1, von A nach C, bewegt Scheibe 1.
hanoi(3, A, C, B), die eine Scheibe bewegen. Jeder Aufruf steht über den beiden, die er macht, und jeder steht über der Nummer des Zuges, den er macht — die Kästen, von links nach rechts gelesen, sind also die Lösung in der richtigen Reihenfolge. Die 8 Aufrufe mit n = 0 sind nicht gezeichnet: Einer hängt an jeder Seite jedes untersten Kastens, und jeder kehrt zurück, ohne etwas zu tun. Führen Sie das Programm oben aus, und dieser Baum folgt ihm: Der laufende Aufruf leuchtet, die Aufrufe, die auf ihn warten, sind umrandet, und jede Zugnummer füllt sich, sobald ihr Zug gemacht ist. Geben Sie dem Programm vier oder fünf Scheiben, und der Baum wird in dieser Größe neu gezeichnet.Diese Symmetrie gilt bei jeder Größe. Zug 2ⁿ⁻¹ ist immer die größte Scheibe beim Wechsel, und die Züge davor und danach sind die Lösung für (n − 1) Scheiben, zweimal gespielt, mit zwei getauschten Turmrollen. Deshalb lässt sich eine Zugliste beliebiger Länge auf einen Blick lesen, und deshalb ist die Lösung für fünf Scheiben die für vier, ein Zug, und noch einmal die für vier.
Warum er korrekt ist
Der Algorithmus verstößt nie gegen eine Regel und endet immer, und der Beweis ist kurz genug, um ihn ganz zu geben. Die Behauptung lautet für jedes n:
Wenn die obersten n Scheiben des Startturms die Scheiben 1 bis n sind und jede Scheibe anderswo größer als n ist, dann bewegt hanoi(n, source, target, spare) diese n Scheiben regelkonform auf das Ziel und rührt nichts anderes an.
Für n = 0 gibt es nichts zu bewegen, und nichts wird angerührt.
Nehmen wir an, die Behauptung gilt für n − 1. Dann ist jeder der drei Schritte erlaubt:
- Der erste Aufruf bewegt die Scheiben 1 bis n − 1 auf den Hilfsturm. Er darf das: Jede andere Scheibe, auch Scheibe n, ist größer als n − 1.
- Scheibe n wechselt auf das Ziel. Jetzt liegt nichts mehr auf ihr, und auf dem Ziel liegen nur Scheiben, die größer als n sind.
- Der zweite Aufruf bewegt die Scheiben 1 bis n − 1 vom Hilfsturm auf das Ziel. Erlaubt aus demselben Grund: Scheibe n, nun unter ihnen, ist größer.
Alle n Scheiben liegen auf dem Ziel, und sonst hat sich nichts bewegt. Per Induktion gilt die Behauptung für jedes n, und das vollständige Rätsel ist der Fall, in dem der Startturm alle Scheiben trägt.
Angenommendie Behauptung für n − 1
Die Hypothese sagt, dass ein Aufruf auf n − 1 Scheiben sie regelkonform dorthin bewegt, wohin man ihm sagt, und nichts anderes anrührt. Hier angewandt, trägt er die Scheiben 1 bis n − 1 vom Start auf den Hilfsturm. Er darf das: Jede Scheibe, auf der er landen könnte — Scheibe n eingeschlossen — ist größer als jede, die er trägt.
Hier bewiesenein erlaubter Zug
Das ist der einzige Schritt, den der Beweis selbst begründen muss, und er ist ein einziger Zug. Scheibe n ist frei, weil der Schritt davor sie frei geräumt hat. Auf dem Ziel liegen nur Scheiben, die größer als n sind, weil alles Kleinere auf dem Hilfsturm liegt. Also ist der Zug erlaubt.
Angenommendie Behauptung für n − 1, noch einmal
Dieselbe Hypothese, ein zweites Mal benutzt, bringt die Scheiben 1 bis n − 1 vom Hilfsturm auf das Ziel. Erlaubt aus demselben Grund wie vorher: Scheibe n liegt jetzt unter ihnen und ist größer. Alle n Scheiben liegen auf dem Ziel, und sonst hat sich nichts bewegt — das ist die Behauptung für n.
Die Rekursionsgleichung, richtig gelöst
T(n) sei die Zahl der Züge, die der Algorithmus für n Scheiben macht. Die Funktion macht zwei Aufrufe auf n − 1 Scheiben und einen eigenen Zug, und ohne Scheiben macht sie überhaupt keinen Zug:
Zum Lösen setzen Sie die Gleichung in sich selbst ein und suchen das Muster. Das heißt Ausrollen oder Teleskopieren:
Ein Muster zu erkennen heißt nicht, es bewiesen zu haben — bestätigen Sie es per Induktion. T(0) = 0 = 2⁰ − 1. Und wenn T(n − 1) = 2ⁿ⁻¹ − 1 ist, dann ist T(n) = 2(2ⁿ⁻¹ − 1) + 1 = 2ⁿ − 2 + 1 = 2ⁿ − 1. Die geschlossene Form stimmt für jedes n exakt.
Es gibt einen dritten Weg, und er ist ein Bild, das Sie schon gesehen haben. Der Rekursionsbaum oben ist ein vollständiger Binärbaum: Die Wurzel bewegt n Scheiben, die zwei Aufrufe darunter je n − 1, die vier darunter je n − 2, und so weiter. Tiefe k enthält also 2ᵏ Aufrufe, die unterste Ebene ist Tiefe n − 1, und jeder dieser Aufrufe macht genau einen Zug. Addieren Sie die Ebenen, und die Summe fällt als geometrische Reihe heraus:
Drei Methoden — Ausrollen, Induktion und der Baum — und eine Antwort. Das ist gut zu wissen, denn es gibt eine vierte, die ständig angeboten wird, und die ist falsch.
Das ist keine Rekursionsgleichung für das Master-Theorem
Eine verbreitete Antwort — auch in vielfach abgeschriebenen Tutorials — löst das mit dem Master-Theorem. Es ist nicht anwendbar.
- Das Master-Theorem gilt für T(n) = aT(n/b) + f(n) mit b > 1: Jedes Teilproblem ist ein Bruchteil des ursprünglichen, wie bei Mergesort.
- Diese Gleichung ist T(n) = 2T(n − 1) + 1: Das Teilproblem ist eine Scheibe kleiner, nicht um einen Faktor kleiner — es gibt also gar kein b zum Einsetzen.
Für Gleichungen dieser Art gibt es ein eigenes Ergebnis, das Subtraktions-Pendant: Ist T(n) = aT(n − b) + f(n) mit a > 1 und f einem Polynom, so wächst T(n) höchstens wie aⁿᐟᵇ mal dieses Polynom. Das passt zur Antwort oben — aber Ausrollen ist kürzer, als sich das zu merken.
Dieselbe Rechnung zählt die Aufrufe. Jeder Aufruf mit n {">"} 0 Scheiben macht zwei weitere, also gilt C(n) = 2C(n − 1) + 1 mit C(0) = 1, was sich zu 2ⁿ⁺¹ − 1 ausrollt. Das sind 2ⁿ − 1 Aufrufe, die eine Scheibe bewegen, plus 2ⁿ, die sofort zurückkehren.
Zeit- und Speicherkosten
Jeder Aufruf leistet neben seinen beiden rekursiven Aufrufen eine konstante Menge Arbeit, also ist die Laufzeit proportional zur Zahl der Aufrufe: Θ(2ⁿ). Kein klügerer Algorithmus kann das unterbieten, weil die Ausgabe selbst 2ⁿ − 1 Züge lang ist — und 2ⁿ − 1 ist ein bewiesenes Minimum, nicht nur das Beste, was jemand gefunden hat. Der Beweis, dass keine Lösung kürzer ist, hat eine eigene Seite: warum das Minimum 2ⁿ − 1 ist.
Mit dem Speicher verhält es sich anders. Die Aufrufe existieren nicht alle gleichzeitig: Ein Aufruf endet, bevor sein Geschwister beginnt, also hält der Stapel nur einen Aufruf je Ebene, von n hinunter bis 0. Der Platzbedarf ist Θ(n) — fünfundsechzig Stapelrahmen für die vierundsechzig Scheiben der Legende, was jeder Rechner wegsteckt, ohne es zu merken. Was ausgeht, ist die Zeit: 18.446.744.073.709.551.615 Züge sind bei einer Milliarde pro Sekunde immer noch 584 Jahre.
Diese Zahl gehört der Legende, und die Legende ist jünger, als sie klingt. Ein Tempel, in dem Priester vierundsechzig goldene Scheiben bewegen, und das Ende der Welt, wenn sie fertig sind, taucht 1884 zum ersten Mal im Druck auf — in einem Artikel, der ein Rätsel bewarb, das im Jahr zuvor in Paris in den Handel gekommen war, unter dem Namen eines Professors, den es nicht gab. Im Tempo der Mönche, ein Zug pro Sekunde, bleiben der Welt noch 585 Milliarden Jahre, ungefähr das Vierzigfache des heutigen Alters des Universums. Der Mathematiker hinter dem Rätsel und der Geschichte war Édouard Lucas, der von Hand bewies, dass 2¹²⁷ − 1 eine Primzahl ist, und mit 49 Jahren an einer Infektion starb, die von einem Schnitt durch einen Teller herrührte, den ein Kellner bei einem Bankett hatte fallen lassen.
| Rekursiv | Iterativ | |
|---|---|---|
| Gemachte Züge | 2ⁿ − 1 | 2ⁿ − 1 |
| Geleistete Arbeit | 2ⁿ⁺¹ − 1 Aufrufe | 2ⁿ − 1 Schleifendurchläufe |
| Zeit | Θ(2ⁿ) | Θ(2ⁿ) |
| Zusätzlicher Speicher | Θ(n) Aufrufstapel | Θ(n) — die Türme, oder nur ein n-Bit-Zugzähler |
Setzen Sie Zahlen in diese Formeln ein, und die beiden Kostenarten gehen auseinander. Jede weitere Scheibe macht Züge und Aufrufe etwas mehr als doppelt so zahlreich und fügt dem tiefsten Stand des Stapels einen einzigen Rahmen hinzu:
| Scheiben | Züge | Aufrufe | Tiefster Stapel | Bei einer Milliarde Zügen pro Sekunde |
|---|---|---|---|---|
| 3 | 7 | 15 | 4 | 7 Nanosekunden |
| 4 | 15 | 31 | 5 | 15 Nanosekunden |
| 5 | 31 | 63 | 6 | 31 Nanosekunden |
| 10 | 1.023 | 2.047 | 11 | 1 Mikrosekunde |
| 20 | 1.048.575 | 2.097.151 | 21 | 1 Millisekunde |
| 32 | 4.294.967.295 | 8.589.934.591 | 33 | 4 Sekunden |
| 64 | 18.446.744.073. | 36.893.488.147. | 65 | 584 Jahre |
Der Rechner für die minimale Zugzahl ermittelt Züge und Zeit für jede Scheibenzahl, und die letzte Zeile — die Größe der Legende — hat eine eigene Seite.
Ohne Rekursion
Dieselben Züge lassen sich auch mit einer schlichten Schleife und zwei Regeln erzeugen:
- Ungerade Züge: Die kleinste Scheibe rückt einen Turm weiter, auf einem festen Rundweg — A → C → B bei ungerader Scheibenzahl, A → B → C bei gerader.
- Gerade Züge: Machen Sie den einzigen erlaubten Zug, der die kleinste Scheibe nicht anfasst. Es gibt immer genau einen.
Diese beiden Regeln geben die rekursive Lösung Zug für Zug wieder; die Seite zur iterativen Lösung zeigt, warum, und spielt sie auf einem Brett ab.
Es gibt eine noch seltsamere Fassung, die überhaupt keine Türme speichert: Scheibe, Start und Ziel von Zug m lassen sich jeweils an den Binärziffern von m ablesen. Dieser Zusammenhang, und der Gray-Code, der darin steckt, stehen in Türme von Hanoi, Binär und Gray-Code.
Aus einer Stellung, die nicht der Anfang ist
Die Lehrbuchfunktion setzt voraus, dass alle Scheiben auf dem Startturm beginnen. Geben Sie ihr ein halb fertiges Rätsel, und sie hat nichts dazu zu sagen. Ein weiterer Fall behebt das. Sehen Sie sich die größte Scheibe an:
- Schon auf dem Turm, den sie erreichen soll? Dann braucht sie keinen Zug. Lösen Sie die kleineren Scheiben und schicken Sie sie auf denselben Turm.
- Irgendwo anders? Räumen Sie die kleineren Scheiben auf den Turm, der weder ihr jetziger noch ihr Zielturm ist, bewegen Sie sie, und machen Sie weiter wie zuvor.
Diese Fassung findet die kürzeste Lösung aus jeder erlaubten Stellung, und genau sie lässt der Türme-von-Hanoi-Löser laufen.
Prüfen Sie sich selbst
Drei Fragen, die die Seite schon beantwortet hat. Denken Sie sie durch, bevor Sie aufklappen — kommt eine nicht, ist der Abschnitt, der bei der Antwort genannt wird, der zum Nachlesen.
Drei Scheiben sind sieben Züge. Wie viele sind 5 Scheiben, und wie viele davon bewegen Scheibe 5 selbst?
Antwort anzeigen
31 Züge, also 2⁵ − 1 — und genau einer davon bewegt die größte Scheibe. Jede Scheibe außer der größten bewegt sich mehr als einmal, weil jede der beiden Hälften der Lösung sie bewegen muss. Die Rekursionsgleichung, richtig gelöst.
hanoi(3, A, C, B)machthanoi(2, A, B, C)als ersten Aufruf. Warum ist B das Ziel dieses Aufrufs und nicht C?Antwort anzeigen
Weil Scheibe 3 nach C soll und dort nur landen kann, wenn C leer ist. Die beiden Scheiben darüber müssen also woanders geparkt werden, und dieses Woanders ist nur B. Der Turm, auf den ein Aufruf zielt, wird zu dem Turm, den sein erster Aufruf frei halten muss — deshalb tauschen zwei der drei Argumente beim Hineingehen die Plätze. Worauf zu achten ist.
20 Scheiben zu lösen macht 2.097.151 Aufrufe. Wie viele davon sind höchstens im selben Moment auf dem Stapel?
Antwort anzeigen
21 — einer je Scheibenzahl von 20 bis 0. Ein Aufruf endet, bevor sein Geschwister beginnt, also existieren die Aufrufe nie alle gleichzeitig: Die Arbeit ist exponentiell und der Speicher linear. Zeit- und Speicherkosten.
Der Algorithmus in Ihrer Sprache
Dieselbe Funktion in sechs Programmiersprachen, jede mit einem lauffähigen Programm, einer Erklärung Zeile für Zeile, dem Brett daneben und den Dingen, die für diese Sprache eigen sind — und die zählen, sobald man die Züge für vierundsechzig Scheiben zählen will. Diese sechs Seiten sind auf Englisch.
- Türme von Hanoi in PythonENRecursive and iterative programs, why Python's recursion limit never bites here, and integers that simply do not overflow.
- Türme von Hanoi in JavaScriptENWhy 2 ** 64 − 1 prints the wrong number, BigInt, and a generator that hands out one move at a time.
- Türme von Hanoi in JavaENWhy (1L << 64) − 1 is zero, counting with BigInteger, and the same solution driven by an explicit stack.
- Türme von Hanoi in CENUndefined shifts, uint64_t, and a loop that reads every move straight off the bits of the move number.
- Türme von Hanoi in C++ENCollecting the moves in a std::vector, the 64-disk count checked by the compiler, and three pairs of towers in a loop.
- Türme von Hanoi in C#ENTop-level statements, an iterator whose moves LINQ can query, and ulong, checked arithmetic and UInt128 for the move count.
Die Wörter auf dieser Seite
- Basisfall
- Die Fassung des Problems, die eine rekursive Funktion unmittelbar beantwortet, ohne sich selbst aufzurufen. Er ist es, der die Rekursion anhält. Hier ist es n = 0: keine Scheibe zu bewegen, also nichts zu tun, und der Aufruf kehrt zurück. Jede Kette von Aufrufen, wie tief auch immer, endet in einem davon.
- Rekursiver Fall
- Alles, was nicht der Basisfall ist: der Teil der Funktion, der eine kleinere Fassung desselben Problems löst und seine Antwort daraus aufbaut. Hier sind es die drei Zeilen, die n − 1 Scheiben aus dem Weg räumen, Scheibe n bewegen und die n − 1 zurückbringen.
- Aufrufstapel
- Die Liste der Funktionsaufrufe, die begonnen und noch nicht geendet haben, der jüngste zuletzt. Ein Aufruf kommt hinzu, wenn er beginnt, und fällt weg, wenn er zurückkehrt — der Stapel ist also die Art, wie ein Programm sich merkt, wohin es zurück muss. Genau ihn zeichnet das Feld oben, und er ist echter Speicher: deshalb ist seine Tiefe der Platzbedarf des Algorithmus.
- Stapelrahmen
- Ein Eintrag auf dem Aufrufstapel: die Argumente und lokalen Variablen eines einzelnen Aufrufs. Jede Zeile des Stapels im Feld oben ist ein Rahmen, mit dem eigenen n dieses Aufrufs und seiner eigenen Auffassung davon, welcher Turm Start, Ziel und Hilfsturm ist. Vierundsechzig Scheiben brauchen fünfundsechzig Rahmen, was nichts ist; das Problem sind die Züge.
- Teleskopieren
- Auch Ausrollen genannt: eine Rekursionsgleichung lösen, indem man sie so lange in sich selbst einsetzt, bis das Muster offensichtlich ist, und dann die Zahl der Einsetzungen so wählt, dass nur der bekannte Startwert übrig bleibt. Teleskopieren heißt es, weil die mittleren Glieder zusammenfallen wie die Auszüge eines Fernrohrs.
- Θsaid “Groß-Theta”
- Eine Wachstumsrate, die zugleich obere und untere Schranke ist: Θ(2ⁿ) heißt, dass die Kosten wie 2ⁿ wachsen, nicht bloß nicht schneller. O(2ⁿ) würde nur die Decke versprechen. Θ ist hier die ehrliche Schreibweise, weil dieser Algorithmus tatsächlich 2ⁿ − 1 Züge macht — er kann kein Glück haben und früher fertig sein.
Häufige Fragen
Wie funktioniert der rekursive Algorithmus der Türme von Hanoi?
Um n Scheiben von einem Startturm auf einen Zielturm zu bringen, bewegt er zuerst die oberen n − 1 Scheiben auf den Hilfsturm, dann die größte Scheibe auf das Ziel, dann die n − 1 Scheiben vom Hilfsturm auf das Ziel. Jede dieser beiden kleineren Umschichtungen ist dasselbe Problem mit einer Scheibe weniger, also ruft sich die Funktion dafür selbst auf, und ein Aufruf ohne zu bewegende Scheibe kehrt einfach zurück.
Sind die Türme von Hanoi Teile und herrsche oder dynamische Programmierung?
Teile und herrsche. Das Problem wird in zwei Teilprobleme mit n − 1 Scheiben geteilt, jedes per Rekursion erledigt, und der Kombinationsschritt ist ein einzelner Zug der größten Scheibe. Dynamische Programmierung ist es nicht, und Memoisierung hilft nicht — obwohl sich die Teilprobleme tatsächlich wiederholen. Mit drei Türmen gibt es nur sechs Arten, Start, Ziel und Hilfsturm zu benennen, also wird hanoi(1, A, C, B) beim Lösen von drei Scheiben wirklich zweimal aufgerufen. Das zwischenzuspeichern bringt nichts, denn die Antwort auf ein Teilproblem ist eine Liste von 2ᵏ − 1 Zügen, und eine gespeicherte Liste dieser Länge abzuspielen kostet so viel, wie sie zu erzeugen. Die Arbeit ist hier die Ausgabe, und keine Tabelle macht die Ausgabe kürzer.
Gilt das Master-Theorem für die Rekursionsgleichung der Türme von Hanoi?
Nein. Das Master-Theorem deckt Gleichungen der Form T(n) = aT(n/b) + f(n) ab, bei denen jedes Teilproblem ein Bruchteil des ursprünglichen ist. Die Gleichung der Türme von Hanoi ist T(n) = 2T(n − 1) + 1: Das Teilproblem ist eine Scheibe kleiner, nicht um einen Faktor. Ausrollen ergibt direkt T(n) = 2ⁿ − 1, und Induktion bestätigt es.
Wie tief geht die Rekursion?
Mit einem Basisfall bei n = 0 liegen höchstens n + 1 Aufrufe gleichzeitig auf dem Stapel: einer je Scheibenzahl von n bis 0. Mit einem Basisfall bei n = 1 sind es n. So oder so wächst die Tiefe linear, und deshalb braucht die rekursive Lösung nur O(n) Speicher, obwohl sie exponentiell viele Züge macht.
Wie viele Funktionsaufrufe macht die rekursive Lösung?
2ⁿ⁺¹ − 1 Aufrufe, wenn der Basisfall n = 0 ist: die 2ⁿ − 1 Aufrufe, die eine Scheibe bewegen, plus 2ⁿ Aufrufe, die nichts zu bewegen finden und zurückkehren. Drei Scheiben machen 15 Aufrufe. Mit einem Basisfall bei n = 1 geschrieben, macht dasselbe Programm genau 2ⁿ − 1 Aufrufe, einen je Zug.
Ist der iterative Algorithmus schneller als der rekursive?
Nicht auf eine Weise, die zählt. Beide müssen 2ⁿ − 1 Züge erzeugen, also brauchen beide Zeit proportional zu 2ⁿ. Die iterative Fassung spart die Kosten der Funktionsaufrufe und kann den Stapel nicht überlaufen lassen, aber die Rekursion ist hier nur n Ebenen tief: Ein Stapelüberlauf ist für keine Scheibenzahl, auf die Sie warten könnten, ein echtes Risiko.
Wie sieht der Algorithmus der Türme von Hanoi als Flussdiagramm aus?
Nach nicht viel, und das ist die ehrliche Antwort. Ein Flussdiagramm zeigt, wie die Steuerung einmal durch Kästen läuft, und der zweite Kasten dieses Algorithmus ist das ganze Flussdiagramm noch einmal — drei Ebenen tiefer, mit zwei vertauschten Türmen und einer Scheibe weniger. Dafür hat ein Flussdiagramm keine Notation: Ein Flussdiagramm der rekursiven Lösung ist also entweder falsch, oder es ist das Bild eines einzelnen Aufrufs mit zwei Kästen, auf denen „hanoi aufrufen“ steht — und das lehrt nichts, was der vierzeilige Pseudocode nicht auch lehrt. Das Diagramm, das es ausdrückt, ist der Rekursionsbaum: jeder Aufruf unter dem Aufruf gezeichnet, der ihn gemacht hat, jeder Kasten über dem Zug, den er macht. Er steht auf dieser Seite, und seine Kästen von links nach rechts gelesen ergeben die Züge der Reihe nach.
Kann der Algorithmus ein schon halb gelöstes Rätsel lösen?
Die Lehrbuchfassung nicht: Sie setzt voraus, dass alle Scheiben auf einem Turm beginnen. Sie lässt sich mit einem zusätzlichen Fall verallgemeinern — liegt die größte Scheibe schon auf ihrem Ziel, überspringen Sie sie und lösen Sie die Scheiben darüber — und diese Fassung findet die kürzeste Lösung aus jeder erlaubten Stellung. Der Löser dieser Seite lässt genau das laufen. Den Löser öffnen →