Betrachten Sie ein Quadrat-n-n-Gitterdiagramm, das so aussieht.
Es ist wichtig zu beachten, dass dieses Diagramm 11 mal 11 ist .
An einem bestimmten Punkt steht ein Mann an einer Kreuzung und bewegt sich immer nur um jeweils einen Schritt vertikal oder horizontal zur nächsten Kreuzung. Leider hat er etwas zu viel getrunken und wählt die Richtung, in die er sich bewegt, zufällig aus den bis zu 4 möglichen Richtungen (oben, unten, links, rechts). Das sind bis zu 4, als ob er an einer Wand steht, er hat natürlich nur 3 Möglichkeiten und in einer Ecke hat er nur 2.
Er startet in der unteren linken Ecke und sein Ziel ist es, nach Hause zu kommen, das ist die obere rechte Ecke. Die Zeit ist einfach die Anzahl der Schritte, die er benötigt.
Sie sind jedoch ein böswilliger Gegner, der möchte, dass er so langsam wie möglich nach Hause kommt. Sie können während des Gehens jederzeit eine beliebige Anzahl von Kanten aus der Grafik löschen . Die einzige Einschränkung ist, dass Sie ihm immer einen Weg lassen müssen, um nach Hause zu kommen, und Sie eine Kante, die er bereits verwendet hat, nicht löschen können.
Die Herausforderung besteht darin, einen so böswilligen Gegner wie möglich zu finden und ihn dann mit einem zufälligen betrunkenen Spaziergänger auf einem
100 x 10020 x 20-Diagramm zu testen . Ihre Punktzahl ist einfach die durchschnittliche Zeit, die der Zufallsläufer benötigt, um über101000 Läufe nach Hause zu kommen .
Sie können eine beliebige Sprache und Bibliothek verwenden, sofern diese frei verfügbar und unter Linux leicht zu installieren sind.
Was muss ich implementieren?
Sie sollten Code für den Zufallsläufer und auch für den Gegner implementieren und den Code so kombinieren, dass die Ausgabe bei Ausführung einfach der Durchschnitt von 1000 Läufen unter Verwendung Ihres gegnerischen Codes ist. Der Zufalls-Walker-Code sollte sehr einfach zu schreiben sein, da er nur (x-1, y), (x + 1, y), (x, y-1) und (x, y + 1) auswählt, um dies sicherzustellen Keines davon wurde gelöscht oder liegt außerhalb des gültigen Bereichs.
Der Code des Gegners ist natürlich schwieriger und muss sich auch merken, welche Kanten der Betrunkene bereits durchlaufen hat, damit er keine davon löschen und sicherstellen kann, dass für den Betrunkenen noch eine Route nach Hause besteht, was etwas schwieriger ist schnell zu tun.
Nachtrag 10 Läufe ist nicht genug, aber ich wollte keine Leute bestrafen, die es geschafft haben, wirklich lange Spaziergänge zu machen. Auf vielfachen Wunsch habe ich es jetzt auf 1000 erhöht. Wenn Ihr Marsch jedoch so lang ist, dass Sie nicht in realistischer Zeit 1000 Läufe ausführen können, geben Sie einfach die maximale Anzahl von Läufen an.
Highscore-Tabelle für 100 mal 100.
- 976124.754 von Optimizer.
- 103000363.218 von Peter Taylor.
Bearbeiten 1. Die Diagrammgröße wurde auf 20 mal 20 geändert, um die Laufzeit der Tests zu verbessern. Ich werde einen neuen High-Table-Score für diese Größe erstellen, wenn Leute die Scores einreichen.
Highscore-Tabelle für 20 mal 20.
230,794.38 (100k runs) by justhalf
227,934 by Sparr
213,000 (approx) by Peter Taylor
199,094.3 by stokastic
188,000 (approx) by James_pic
64,281 by Geobits
Antworten:
230.794,38 auf 20x20, 100k Läufen
Letztes Update: Ich habe endlich eine perfekte dynamische 2-Pfad-Lösung erstellt. Ich sagte perfekt, da die vorherige Version eigentlich nicht symmetrisch ist, war es einfacher, einen längeren Weg zu finden, wenn der Betrunkene einen Weg über den anderen nahm. Die aktuelle ist symmetrisch, sodass eine höhere erwartete Anzahl von Schritten möglich ist. Nach wenigen Versuchen scheint es sich um 230.000 zu handeln, eine Verbesserung gegenüber der vorherigen, die sich auf etwa 228.000 beläuft. Aber statistisch gesehen liegen diese Zahlen immer noch innerhalb ihrer enormen Abweichung, daher behaupte ich nicht, dass dies wesentlich besser ist, aber ich glaube, dass dies besser sein sollte als die vorherige Version.
Der Code befindet sich am Ende dieses Beitrags. Es wurde so aktualisiert, dass es viel schneller als die Vorgängerversion ist und 1000 Läufe in 23 Sekunden erledigt.
Unten ist ein Probelauf und ein Probelabyrinth:
Vorherige Einsendungen
Endlich kann ich mit Sparrs Ergebnis mithalten! = D
Basierend auf meinen vorherigen Experimenten (siehe unten in diesem Beitrag) besteht die beste Strategie darin, einen doppelten Pfad zu haben und einen zu schließen, wenn der Betrunkene einen von ihnen erreicht Erhöhen Sie die Chance, dass er in einen längeren Pfad gerät.
Ausgehend von meiner
DOUBLE_PATH
Strategie baute ich eine andere, die das Labyrinth (meinDOUBLE_PATH
Labyrinth war leicht zu ändern) in Abhängigkeit von der Trunkenheiterbewegung verändert. Da er einen Pfad mit mehr als einer verfügbaren Option nimmt, werde ich die Pfade schließen, um nur zwei mögliche Optionen zu belassen (eine, von der er gekommen ist, eine, die nicht gereist ist).Dies klingt ähnlich wie das, was Sparr erreicht hat, wie das Ergebnis zeigt. Der Unterschied zu seinem ist zu gering, um als besser angesehen zu werden, aber ich würde sagen, dass mein Ansatz dynamischer ist als der von ihm, da mein Labyrinth modifizierbarer ist als der von Sparr =)
Das Ergebnis mit einem Musterlabyrinth:
Experimente Abschnitt
Das Beste ist die gleiche Strategie wie Stokastic. Ich bin stolz darauf, mit verschiedenen Strategien zu experimentieren und schöne Ergebnisse zu drucken :)
Jedes der unten abgebildeten Labyrinthe ist das letzte Labyrinth, nachdem der Betrunkene zu Hause angekommen ist. Aufgrund der Zufälligkeit der Betrunkenenbewegung und der Dinamizität des Gegners können sie sich daher von Lauf zu Lauf geringfügig unterscheiden.
Ich werde jede Strategie beschreiben:
Einzelner Pfad
Dies ist der einfachste Ansatz, bei dem ein einziger Pfad vom Eingang zum Ausgang erstellt wird.
Insel (Stufe 0)
Dies ist ein Ansatz, der versucht, den Betrunkenen auf einer fast einsamen Insel zu fangen. Funktioniert nicht so gut wie ich erwartet hatte, aber dies ist eine meiner ersten Ideen, also füge ich sie hinzu.
Es gibt zwei Wege, die zum Ausgang führen, und wenn der Säufer sich einem nähert, schließt der Gegner ihn und zwingt ihn, den anderen Ausgang zu finden (und wird möglicherweise wieder auf der Insel gefangen).
Doppelter Pfad
Dies ist die am häufigsten diskutierte Strategie, bei der zwei gleichlange Pfade zum Ausgang führen und einer davon geschlossen wird, wenn sich der Betrunkene einem von ihnen nähert.
Insel (Stufe 1)
Inspiriert von den vielen Pfaden der Insel und der hohen Anzahl von Spaziergängen auf einem Pfad verbinden wir die Insel mit dem Ausgang und machen ein Labyrinth auf der Insel, wodurch insgesamt drei Wege zum Ausgang entstehen und ähnlich wie im vorherigen Fall jeder der beiden Wege geschlossen wird Verlasse den Raum, wenn der Säufer näher kommt.
Dies funktioniert etwas besser als ein reiner Einzelpfad, besiegt jedoch den doppelten Pfad nicht.
Insel (Stufe 2)
Bei dem Versuch, die vorherige Idee zu erweitern, habe ich eine verschachtelte Insel mit insgesamt fünf Pfaden erstellt, aber es scheint nicht so gut zu funktionieren.
Insel (Stufe 3)
Wenn wir feststellen, dass der doppelte Pfad tatsächlich besser funktioniert als der einzelne Pfad, machen wir die Insel im doppelten Pfad!
Das Ergebnis ist eine Verbesserung gegenüber Island (Level 1), aber es schlägt immer noch nicht den doppelten Weg.
Zum Vergleich ergibt sich für den doppelten Weg der Inselgröße ein Mittelwert von 131.134,42 Zügen. Dies führt zu einer beachtlichen Anzahl von Zügen (ca. 40.000), die jedoch nicht ausreichen, um den Doppelpfad zu schlagen.
Insel (Stufe 4)
Wieder experimentieren mit verschachtelten Inseln, und wieder funktioniert es nicht so gut.
Fazit
Alles in allem beweist dies, dass ein einziger langer Weg von der aktuellen Position des Trunkenboldes zum Ausgang am besten funktioniert, was durch die Doppelpfadstrategie erreicht wird, da der Trunkenbold nach dem Schließen eines Ausgangs die maximal mögliche Entfernung zurücklegen muss, um zum Ausgang zu gelangen der Ausgang.
Dies deutet weiter darauf hin, dass die grundlegende Strategie immer noch ein doppelter Pfad sein sollte, und wir können nur ändern, wie dynamisch die Pfade erstellt werden, was von Sparr getan wurde. Ich glaube, seine Strategie ist der richtige Weg!
Code
quelle
227934 (20 x 20)
Mein dritter Versuch. Verwendet den gleichen allgemeinen Ansatz wie @stokastic mit zwei Pfaden zum Ausgang. Wenn der Wanderer das Ende eines Pfades erreicht, wird er geschlossen und muss zurückkehren, um zum Ende des anderen Pfades zu gelangen und diesen zu verlassen. Meine Verbesserung besteht darin, die Pfade zu generieren, während der Wanderer voranschreitet, sodass der Pfad, den er in der ersten Hälfte des Prozesses weiter verfolgt, länger als der andere Pfad wird.
Ausgabe (mit Zeit):
Beispiel meines Labyrinths mit ungefähr gleichlangen Hälften zum Pfad, wobei der linke / untere Pfad vom Ausgang abgeschnitten ist (unten rechts):
PS: Mir ist eine geringfügige Verbesserung dieses Algorithmus bekannt, bei der ein geschickterer Code erforderlich ist, um eine andere Form für die beiden Pfade zu generieren: Treppen statt Zick-Zack-Höhen.
quelle
135.488.307,9 für 98 x 98
199094.3 für 20x20
Ich habe eine Lösung implementiert, die zwei Wege zum Ziel schafft und genau einen davon schließt, sobald der Betrunkene es erreicht. Dies simuliert eine Pfadlänge, die mindestens das 1,5-fache der Länge eines einzelnen Pfads vom Anfang bis zum Ende beträgt. Nach 27 Läufen habe ich durchschnittlich 135 Millionen getroffen. Leider dauert ein Spaziergang mehrere Minuten, sodass ich ihn die nächsten Stunden laufen muss. Eine Einschränkung - mein Doppelpfad-Generator funktioniert nur, wenn die Größe des Diagramms die Form 4 * n + 2 hat, was bedeutet, dass ich 100 am nächsten kommen kann, 102 oder 98. Ich werde die Ergebnisse mit 98 veröffentlichen, was ich erwarte um die Zick-Zack-Methode noch zu übertreffen. Ich werde später an einem besseren Pfadsystem arbeiten. Derzeit werden Ergebnisse in Form von (Anzahl Schritte, aktueller Durchschnitt) nach jedem Spaziergang ausgegeben.
BEARBEITEN: Behoben, so dass Code jetzt für Diagrammgrößen funktioniert, die ein Vielfaches von 2 und nicht mehr 4 * n + 2 sind.
Code: (Fügen Sie dem Walker-Konstruktor in Zeile 187 das Argument 'True' hinzu, um das Diagramm als Turtle zu zeichnen).
Rohdaten: (aktuelle AnzahlSchritte, laufender Durchschnitt)
quelle
4-Wege-Ansatz, 213k
Der Einweg-Ansatz ist
und erzielt einen Durchschnitt von
N^2
.Der Zwei-Wege-Ansatz ist
Aber das erste Mal, wenn der Betrunkene den Endpunkt erreicht, ist er abgeschnitten:
Es erzielt einen Durchschnitt von
(N/2)^2 + N^2
.Der Vier-Wege-Ansatz verwendet zwei Schnitte:
Angenommen, die äußere Schleife ist lang
xN
und die innere Schleife ist lang(1-x)N
. Der Einfachheit halber normalisiere ich aufN=1
.Vom Start bis zum ersten Schnitt ergibt sich ein Durchschnitt von
(x/2)^2
. Vom ersten bis zum zweiten Schnitt stehen zwei Optionen zur Verfügung: Längex
und Länge1-x
. das ergibt einen Durchschnitt von(1-x)x^2 + x(1-x)^2 = x-x^2
. Schließlich gibt der verbleibende Pfad1
. Die Gesamtpunktzahl ist alsoN^2 (1 + x - 3/4 x^2)
.Anfangs ging ich davon aus, dass es optimal wäre, die verfügbaren Pfade bei jedem Schritt gleich lang zu halten, und mein ursprünglicher Ansatz verwendete
x = 1/2
eine Punktzahl von1.3125 N^2
. Aber nach der obigen Analyse stellt sich heraus, dass die optimale Aufteilung gegeben ist, wennx = 2/3
mit der Punktzahl1.3333 N^2
.mit Code
quelle
f
esc
in Ihrem Code um die Länge von bisN/2
, ob durche
(undd
) oder nicht, oder?N^2
nicht2^N
. Und ja, wenn Sie diese Dynamik optimieren, besteht die Herausforderung darin, sie unter Beibehaltung der Vierpfadeigenschaft zu dynamisieren. @ PeterTaylor: Schöne Bilder!Ich experimentierte damit, das Raster fast über alle
k
Zeilen hinweg aufzuteilen. Dies wandelt es effektiv in etwas um, das einem zufälligen Spaziergangk
durch einN * N/k
Gitter ähnelt . Am effektivsten ist es, jede Reihe in Scheiben zu schneiden, damit wir den Betrunkenen zum Zickzack zwingen.Für den 20x20 Fall (
SIZE=19
) habe ichmit Code
quelle
Für alle, die das Rad nicht neu erfinden wollen
Mach dir keine Sorgen! Ich werde es für dich neu erfinden :)
Das ist übrigens in Java.
Ich habe eine Walker-Klasse erstellt, die sich mit dem zufälligen Gehen befasst. Es enthält auch eine hilfreiche Methode, um festzustellen, ob ein Zug gültig ist (wenn er bereits ausgeführt wurde).
Ich gehe davon aus, dass alle von euch klugen Leuten herausfinden können, ob sie Zufallszahlen für den Konstruktor eingeben können. Ich habe es Ihnen überlassen, damit Sie bestimmte Fälle testen können. Rufen Sie einfach die Funktion walk () auf, um den Betrunkenen (zufällig) zum Laufen zu bringen (Sie haben es erraten!).
Ich werde die Funktion canComeHome () ein anderes Mal implementieren. Am besten, nachdem ich nachgeschlagen habe, wie ich das am besten mache.
quelle
previouslyTraveled.add(new int[]{x,y,move[0],move[1]})
solltex+move[0]
und seiny+move[1]
. DieWidth-1
undHeight-1
, und Ineffizienz bei der Überprüfung der gelöschten Pfade. Ich habe Ihren Code bearbeitet (mit zusätzlicher Funktion zum Drucken des Labyrinths). Fühlen Sie sich frei, ein Rollback durchzuführen, wenn Sie der Meinung sind, dass dies unangemessen ist.Edge
implementiert nicht richtigComparable<Edge>
. Wenn Sie möchten, dass Kanten als Gleiche verglichen werden, auch wenn Sie sie umkehren, müssen Sie die Umkehrung auch im ungleichen Fall berücksichtigen. Am einfachsten ist es, den Konstruktor so zu ändern, dass die geordneten Punkte erhalten bleiben.Comparable
?A
undB
die gleiche Kante umgekehrt undC
unterschiedlich ist, kann manA.compareTo(B) == B.compareTo(A) == 0
aberA.compareTo(C) < 0
und bekommenB.compareTo(C) > 0
.canComeHome()
)64,281
Aktualisierung seit Änderung des Rasters von 100x100 auf 20x20 (1000 Tests). Das Ergebnis bei 100 × 100 (100 Tests) betrug ungefähr 36 Millionen.
Während dies keinen 1D-Spaziergang schlagen wird, wollte ich mit einer Idee spielen, die ich hatte.
Die Grundidee ist, dass das Raster in quadratische Räume aufgeteilt ist, von denen jeweils nur ein Pfad nach Hause führt. Der offene Weg ist je nachdem , was der Betrunkene der Nähe von bekommt letzte , was bedeutet er jeden möglichen Ausgang zu erkunden hat, nur alle zu haben , aber einer von ihnen in seinem Gesicht schlug.
Nachdem ich mit den Raumgrößen gespielt hatte, kam ich zu dem gleichen Schluss wie Peter. Kleiner aufzuschneiden ist besser. Die besten Ergebnisse werden mit einer Raumgröße von 2 erzielt.
Der Code ist schlampig, stört das Chaos nicht. Sie können den
SHOW
Schalter umlegen , und bei jedemSHOW_INT
Schritt wird ein Bild der Pfade angezeigt, damit Sie es in Aktion sehen können. Ein abgeschlossener Lauf sieht ungefähr so aus:(Dies ist das Bild aus dem vorherigen 100x100-Raster. 20x20 ist genau so, aber auch kleiner. Der folgende Code wurde für neue Größen / Auflagen aktualisiert.)
quelle
188k, mit 2 Pfaden
Die besten Einträge scheinen alle den Ansatz zu verfolgen, zwei Pfade zu generieren und einen abzuschneiden, wenn sich der Betrunkene dem Ende des Pfades nähert. Ich glaube nicht, dass ich den Einstieg von justhalf schlagen kann, aber ich konnte nicht anders als mich zu fragen: Warum 2 Pfade? Warum nicht 3 oder 5 oder 20?
TL; DR : 2 Pfade scheinen optimal zu sein
Also habe ich ein Experiment gemacht. Basierend auf dem Framework von Stretch Maniac habe ich einen Eintrag geschrieben, um verschiedene Anzahlen von Pfaden zu testen. Sie können den
featureSize
Parameter anpassen , um die Anzahl der Pfade zu ändern. AfeatureSize
von 20 gibt 1 Pfad, 10 gibt 2 Pfade, 7 gibt 3, 5 gibt 4 und so weiter.Es gibt ein paar Optimierungen, die ich tun könnte, aber nicht, und es unterstützt keine der adaptiven Tricks, die nur die Hälfte verwendet.
Hier sind die Ergebnisse für verschiedene
featureSize
Werte:Und hier ist eine Karte mit 3 Pfaden:
quelle
N
es die Pfadlänge (das heißtn^2-1
), der einzelne Pfad erfordert im DurchschnittN^2
Bewegungen, während die drei Pfade(N/3)^2 + (2N/3)^2 + (2N/3)^2 = N^2
plus einige relativ kleine Werte, also drei, sind Paths hat keinen signifikanten Gewinn gegenüber Single Path, geschweige denn Double Path. (Die Berechnung basiert auf dem Wahrscheinlichkeitsergebnis, das angibt, dass zufällige Bewegungen auf einemN
N^2
131 KB (20 x 20)
Mein erster Versuch war, alle horizontalen Kanten außer der oberen und unteren Reihe zu entfernen. Jedes Mal, wenn der Geher den unteren Rand einer Spalte erreichte, entfernte ich die Kante vor ihm, bis er den unteren Rand jeder Spalte besucht hatte und schließlich in der Lage sein, den Ausgang zu erreichen. Dies führte zu durchschnittlich 1/8 so vielen Schritten wie @ PeterTaylors 1-D-Walk-Ansatz.
Als nächstes habe ich beschlossen, etwas Umständlicheres zu probieren. Ich habe das Labyrinth in eine Reihe verschachtelter hohler Chevrons aufgeteilt und fordere ihn auf, den Umfang jedes Chevrons mindestens 1,5-mal zu durchlaufen. Dies hat eine durchschnittliche Zeit von ca. 131k Schritten.
quelle
Nichts tun
Da sich der Mann willkürlich bewegt, könnte man meinen, dass das Entfernen eines Knotens langfristig nur die Chance erhöht, nach Hause zu kommen.
Schauen wir uns zunächst den eindimensionalen Fall an. Dies kann erreicht werden, indem Knoten entfernt werden, bis Sie einen verschnörkelten Pfad ohne Deadends oder Zyklen erhalten, der (fast) jeden Gitterpunkt besucht. In einem
N x N
Raster beträgt die maximale Länge eines solchen PfadesL = N*N - 2 + N%2
(98 für ein 10x10-Raster). Das Gehen entlang des Pfades kann durch eine Übergangsmatrix beschrieben werden, wie sie durch erzeugt wirdT1d
.(Die leichte Asymmetrie macht es schwierig, eine analytische Lösung zu finden, mit Ausnahme von sehr kleinen oder unendlichen Matrizen, aber wir erhalten eine numerische Lösung schneller, als es ohnehin nötig wäre, die Matrix zu diagonalisieren.)
Der Zustandsvektor hat
1
an der Startposition eine einzige und nachK
Schritten erhalten(T1d**K) * state
wir die Wahrscheinlichkeitsverteilung, in einem bestimmten Abstand vom Start zu sein (das entspricht einer Mittelung über alle2**K
möglichen Wege entlang des Pfades!).Ausführen der Simulation für
10*L**2
Schritte und Speichern des letzten Elements des Zustandsvektors nach jedem Schritt, wodurch wir die Wahrscheinlichkeit erhalten, nach einer bestimmten Gesamtanzahl von Schritten das Ziel erreicht zu haben - die kumulative Wahrscheinlichkeitsverteilungcd(t)
. Wenn wir es differenzieren, erhalten wir die Wahrscheinlichkeitp
, das Ziel genau zu einem bestimmten Zeitpunkt zu erreichen. Ermittlung der durchschnittlichen Integrationszeitt*p(t) dt
Die durchschnittliche Zeit bis zum Erreichen des Ziels ist proportional zu
L**2
einem Faktor, der sehr schnell auf 1 steigt. Die Standardabweichung ist mit etwa 79% der durchschnittlichen Zeit nahezu konstant.Diese Grafik zeigt die durchschnittliche Zeit bis zum Erreichen des Ziels für verschiedene Pfadlängen (entsprechend den Rastergrößen von 5 x 5 bis 15 x 15).
So sieht die Wahrscheinlichkeit aus, das Ziel zu erreichen. Die zweite Kurve sieht ausgefüllt aus, da die Position bei jedem ungeraden Zeitschritt ungerade ist und daher nicht am Ziel sein kann.
Daraus können wir ersehen, dass die ausgewogene Doppelpfadstrategie hier am besten funktioniert. Bei größeren Gittern, bei denen der Mehraufwand für die Erstellung von Pfaden im Vergleich zu ihrer Größe vernachlässigbar ist, ist es möglicherweise besser, die Anzahl der Pfade zu erhöhen, ähnlich wie Peter Taylor es beschrieben hat, aber die Längen im Gleichgewicht zu halten
Was ist, wenn wir überhaupt keine Knoten entfernen?
Dann hätten wir doppelt so viele begehbare Knoten plus vier mögliche Richtungen anstelle von zwei. Es scheint so, als würde es sehr unwahrscheinlich sein, jemals irgendwohin zu gelangen. Simulationen zeigen jedoch, dass der Mann nach nur 100 Schritten in einem 10x10-Raster mit ziemlicher Wahrscheinlichkeit sein Ziel erreicht. Daher ist es ein vergeblicher Versuch, ihn auf Inseln zu trappen, da Sie einen potenziell
N**2
langen kurvenreichen Pfad mit einer durchschnittlichen Fertigstellungszeit vonN**4
für handeln eine Insel, die inN**2
Schritten passiert wirdquelle