RFA: Ein rekursiver Algorithmus für das metrische Traveling-Salesman-Problem
In diesem Artikel stelle ich einen rekursiven Algorithmus namens RFA für das metrische bzw. geometrische Traveling-Salesman-Problem (Problem des Handlungsreisenden, auch genannt Rundreiseproblem) vor. Idee & Umsetzung stammen von mir; bei meiner Recherche habe ich den Algorithmus nicht finden können. Ich gehe daher vorläufig davon aus, dass ich der erste damit bin.
Bei dem hier betrachteten Traveling-Salesman-Problem geht es darum, eine möglichst kostengünstige Rundreise durch eine gegebene Menge an “Städten” (ich werde bei der Terminologie “Stadt” bleiben) zu finden, wobei jede Stadt genau 1 Mal besucht werden muss. Jede Wegstrecke zwischen 2 Städten ist mit bestimmten Reisekosten verbunden. In dem von mir gemeinten Fall des metrischen TSP entsprechen die Reisekosten genau der Luftlinie zwischen den beiden Städten. Daher habe ich die Städte der Einfachheit halber als Punkte in der Ebene modelliert (mit einer X- und einer Y-Koordinate), womit bereits erste Experimente möglich sind.
Phase 1: Reduktion auf den Trivialfall des TSP
Mein Algorithmus greift darauf zurück, dass das Rundreiseproblem für 3 Städte trivial ist, weil es nur 1 mögliche Rundreise durch 3 Städte gibt (siehe Grafik auf der rechten Seite).
Bei 1-3 Städten kann der Algorithmus daher direkt die Lösung zurückgeben, weil sie offensichtlich ist.
Bei mehr als 3 Städten arbeitet der Algorithmus in 2 Phasen.
In der 1. Phase werden die gegebenen Städte solange gefaltet, bis nur noch 3 Städte übrig sind.
Es werden jeweils 2 Städte gefaltet. Das sieht so aus, dass eine neue fiktive Stadt erzeugt wird, deren Koordinate gleich dem Mittelpunkt/Zentrum der 2 gefalteten Städte ist. Die 2 gefalteten Städte werden dann durch die neue fiktive Stadt ersetzt, wobei sich die fiktive Stadt ihre “Vorbilder” (“Kind-Städte”) merkt. Dadurch wird das Falten zugleich umkehrbar.
Eine naive Implementierung sieht zum Beispiel so aus, dass in jedem Schritt zufällig eine Stadt ausgewählt und ihr nächster Nachbar berechnet wird. Diese beiden Städte werden dann gefaltet. Der Vorgang wird so lange wiederholt, bis wie gesagt nur noch 3 Städte übrig sind. Fiktive Städte können genauso gefaltet werden wie die Ursprungs-Städte. (Dadurch ergibt sich je nach Anzahl der Städte eine beliebig tiefe “Verschachtelung”.)
Phase 2: Entfalten der Städte mit optimaler Integration der Kind-Städte
Nach der 1. Phase sind wir also beim Trivialfall mit 3 Städten angekommen. Das ist auch schon unsere vorläufige Lösung bzw. die vorläufige Rundreise.
In der 2. Phase werden die fiktiven Städte wieder entfaltet, wobei die vorläufige Rundreise bei jedem Schritt erhalten wird.

TSP - Fiktive Stadt (blau) mit Kind-Städten (rot) in einer vorläufigen Rundreise zu Beginn von Phase 2
Betrachten wir die Grafik auf der rechten Seite. Nehmen wir an die beiden rot markierten Städte seien die Kind-Städte der blau markierten fiktiven Stadt. (Wie man auch gut sehen kann befindet sich die blaue Stadt genau im Zentrum ihrer beiden Kind-Städte.)
Wenn die fiktive Stadt nun in Phase 2 durch ihre Kinder ersetzt wird, dann gibt es genau 2 Möglichkeiten, wie man die beiden Kind-Städte in die gegebene Rundreise integrieren kann. Dazu muss man sich die blaue Stadt wegdenken, denn diese existiert dann ja nicht mehr.
Auf dem nächsten Bild sind die beiden Möglichkeiten eingezeichnet. Die grüne Variante ist offensichtlich die kürzere und damit auch die bessere Wahl.
Halten wir fest: Beim Entfalten muss der Algorithmus nach jedem Schritt entscheiden, wie er die neuen Städte in die bestehende Rundreise integriert. Dabei gilt es zwischen genau 2 Alternativen zu wählen… und das ist relativ leicht.
Phase 2 endet, wenn keine fiktiven Städte mehr übrig sind, die noch entfaltet werden könnten. Die Rundreise ist damit “fertig” und kann vom Algorithmus zurückgegeben werden.
Namensgebung: RFA
Weil der Algorithmus auf dem Falten & Entfalten der Städte beruht taufe ich ihn RFA: Rekursiver-Falt-Algorithmus bzw. recursive-fold-algorithm.
Beispiele
Die Rundreise auf der linken Seite besteht aus 100 Städten und wurde im Bruchteil einer Sekunde berechnet.
Die Rundreise auf der rechten Seite besteht aus 500 Städten und wurde in nur etwas mehr als 1 Sekunde berechnet.
Wie man an den beiden Beispielen auch gut sehen kann arbeitet der RFA keineswegs optimal, denn an der einen oder anderen Stelle überschneiden sich die Rundreisen sogar (das sicherste Zeichen dafür, dass eine Lösung suboptimal ist).
Nichtsdestotrotz handelt es sich in beiden Fällen um sehr gute Näherungslösungen, die zudem in kürzester Zeit berechnet wurden.
Performanz & Güte des RFA
Der RFA befindet sich, was die Laufzeit betrifft, in der Komplexitätsklasse O(n²)1 (Faustregel: Verdoppelt man die Anzahl der Städte, so braucht der Algorithmus 4 Mal so lang). Die Speicherkomplexität liegt dagegen bei O(n).
In allen mir bekannten Fällen stellten die vom Algorithmus erzeugten Rundreisen eine gute bis sehr gute Näherung an die optimale Lösung dar.
Dank seiner überragend hohen Geschwindigkeit eignet sich der RFA in meinen Augen besonders für die Berechnung von Näherungslösungen bei sehr großen Probleminstanzen mit vielen tausenden Städten. Für eine Rundreise durch 10000 Städte *klick* benötigt meine eigene, sehr simple Python-Implementierung beispielsweise ca. 7 Minuten und 20 Sekunden. Gemessen an der Qualität der erhaltenen Näherungslösung ist das ziemlich schnell.
Ausgehend von dieser Näherungslösung kann die Rundreise dann Stück für Stück durch weitere Algorithmen dem Optimum näher gebracht werden.
Zum Vergleich möchte ich außerdem an meinen evolutionären Algorithmus für das TSP erinnern (habe ich damals ebenfalls in Python implementiert). Dieser brauchte bei Städtezahlen jenseits der 100 bereits ewig für eine Näherungslösung. (Für weitere Details siehe Projekte -> Lernleistung.)
Testlauf mit TSP-Instanzen der TSPLIB
Für TSP – Testläufe gibt es die sogenannte TSPLIB. Sie enthält dutzende von TSP-Instanzen mit den unterschiedlichsten Eigenschaften. Von kleinen bis sehr großen Städte-Zahlen ist alles dabei. Das besondere ist, dass in der TSPLIB in vielen Fällen die optimale Lösung bereits enthalten ist. Damit kann man die Ergebnisse von TSP-Algorithmen überprüfen oder ihre Güte in der Praxis abschätzen.
Bisher habe ich meinen Algorithmus zwar hoch gelobt, aber ich bin glaubwürdige Aussagen schuldig geblieben. Daher habe ich ihn auf einige metrische TSP-Instanzen der TSPLIB angesetzt (Quelle):
| Instanz-Bezeichnung (enthält Städte-Anzahl) |
Gesamtlänge der optimalen Rundreise (Quelle) |
Lösung des RFA | Faktor2 | Laufzeit |
|---|---|---|---|---|
| a280 | 2579 | 3372 | 130.75% | 0.482s |
| berlin52 | 7542 | 8334 | 110.5% | 0.022s |
| bier127 | 118282 | 139535 | 117.97% | 0.106s |
| brd14051 | 469385 | 573675 | 122.22% | 15m 27s |
| ch150 | 6528 | 7830 | 119.94% | 0.159s |
| d18512 | 645238 | 779639 | 120.83% | 26m 19s |
| eil51 | 426 | 524 | 123.0% | 0.023s |
| pr76 | 108159 | 126086 | 116.57% | 0.049s |
| pr107 | 44303 | 46578 | 105.14% | 0.078s |
| pr439 | 107217 | 132462 | 123.55% | 1.079s |
| pr1002 | 259045 | 323954 | 125.06% | 5.157s |
| rat99 | 1211 | 1374 | 113.46% | 0.068s |
| rat783 | 8806 | 10548 | 119.78% | 3.171s |
Im Schnitt sind die von meinem Algorithmus berechneten Rundreisen 119.14% länger als die jeweilige optimale Rundreise.
Damit schlägt sich der RFA bei deutlich besserer Laufzeitkomplexität mindestens genausogut wie die Christofides-Heuristik bzw. der Christofides-Näherungsalgorithmus (mit einer Laufzeitkomplexität von O(n³)). Die Christofides-Heuristik hat nämlich für metrische Rundreiseprobleme eine feste Gütegarantie von 50%, d.h. sie garantiert einen Faktor von höchstens 150%, d.h. die Lösungen sind maximal 1,5fach so lang wie die optimale Lösung. (Sie ist damit der bisher beste Algorithmus mit einer festen Gütegarantie.)
Zwar kann ich (noch?) nicht beweisen, dass der RFA ebenfalls über eine Gütegarantie verfügt. Aber in der Praxis zeigt sich, dass der Faktor deutlich unter 150% bleibt.
Übertragbarkeit auf nicht-metrische Rundreiseprobleme
Der RFA ist zwar auch übertragbar auf nicht-metrische Rundreiseprobleme (das Falten von 2 “Städten” muss man dann entsprechend neu erfinden), allerdings erhoffe ich mir davon keine so guten Resultate. Der RFA beruht nunmal hauptsächlich auf der Dreiecksungleichung (denn für je 2 “Städte” muss eine Art fiktive Stadt im Zentrum der 2 Städte konstruierbar sein). Je stärker diese verletzt wird, desto schlechter werden die Ergebnisse vermutlich sein. Daher eignet sich der RFA auch nur bedingt für asymmetrische TSP-Instanzen (also wenn die Reisekosten für A -> B nicht immer auch den Reisekosten B -> A entsprechen (bspw. bergauf vs. bergab)).
Ein dahingehender Test meinerseits steht aber noch aus.
Verbesserungsmöglichkeiten
Abgesehen von der Art der Implementierung (bis jetzt nur in Python; viel schneller wäre C++) kann man den Algorithmus selbst, d.h. seine Laufzeitkomplexität, definitiv noch weiter verbessern.
Ich sehe großen Spielraum in Phase 1 beim Falten der Städte. Die wesentliche Rechenleistung in Phase 1 besteht darin, zu einer gegebenen Stadt ihren nächsten Nachbarn zu finden. Das kann man deutlich beschleunigen, indem man die Städte zu Beginn von Phase 1 partitioniert (die “Landkarte” wird auf bestimmte Weise in Quadrate aufgeteilt). Dadurch muss man beim Suchen des nächsten Nachbars nicht mehr alle Städte berücksichtigen, sondern nur noch diejenigen Städte, die sich in den umliegenden/benachbarten Partitionen befinden. Beim Falten von 2 Städten muss dann natürlich sichergestellt werden, dass die neu erzeugte fiktive Stadt ihrerseits in die korrekte Partition eingeordnet wird.
Die Güte des Algorithmus kann man womöglich deutlich verbessern, indem man eine Methode entwickelt, die auf intelligente Weise die Städte faltet. In meiner naiven Implementierung geschieht die Auswahl der zu faltenden Städte nämlich zufällig. An dieser Stelle kann man gewiss noch Verbesserungen erzielen, indem man die Reihenfolge des Faltens nicht nur dem Zufall überlässt.
In Phase 2 sehe ich wenig Potenzial zur Verbesserung, weil das Entfalten – wie beschrieben – quasi optimal erfolgt. Die Reihenfolge des Entfaltens spielt jedoch mMn. wenigstens eine untergeordnete Rolle, deshalb behalte ich mir eine nähere Untersuchung von Phase 2 vor.
Zusammenfassung
Der von mir entwickelte Algorithmus für das metrische TSP hat eine Laufzeitkomplexität von O(n²) und eine Speicherkomplexität von O(n). Weil er auf dem Falten & Entfalten der Städte beruht und das TSP dabei (zumindest zwischenzeitlich) auf den Trivialfall reduziert, nenne ich ihn RFA: Rekursiver-Falt-Algorithmus.
Obwohl meine Python-Implementierung ausgesprochen naiv vorgeht und ich mir kaum Mühe gegeben habe effizienten Code zu schreiben, bewältigt sie TSP-Instanzen der TSPLIB in sehr kurzer Zeit. Die Länge der dabei gefundenen Lösungen lag im Durchschnitt nur ca. 19% über der jeweils optimalen Lösung (also Faktor 119%; der höchste Faktor betrug 130%).
Daher kann der RFA durchaus mit der Christofides-Heuristik – der bisher beste Algorithmus mit einer festen Gütegarantie für das metrische TSP – konkurrieren, nicht zuletzt weil diese eine deutlich schlechtere Laufzeitkomplexität von O(n³) besitzt (und überdies auch noch sehr viel komplizierter ist). Eventuell kann ich beweisen, dass auch für den RFA eine feste Gütegarantie gilt. In der Praxis schlägt er sich auf alle Fälle sehr gut.
Der RFA ist zwar grundsätzlich auch übertragbar auf nicht-metrische Rundreiseprobleme, allerdings hängt die Güte des RFA sehr wahrscheinlich von der Dreiecksungleichung ab. Je stärker diese verletzt wird, desto schlechter werden die Ergebnisse vermutlich sein.
Dank seiner überragenden Geschwindigkeit eignet sich der Algorithmus in meinen Augen besonders für die Berechnung von Näherungslösungen für sehr große TSP-Instanzen mit vielen Tausend Städten.
Download meiner Python-Implementierung
RFA_demo.zip (2.2 MB)
Der Quelltext befindet sich im Ordner “src” (die Dateien sind UTF-8-kodiert). Die im Artikel verwendeten TSPLIB-Challenges (und viele weitere) befinden sich im Ordner “TSPLIB”.
Die auszuführende Datei ist src/RFA_demo.py. Ich habe Python 2.6 verwendet, es sollte aber auch mit Python 2.5 laufen.
In der Datei RFA_demo.py kann man auch einige Einstellungen verändern. Ich habe die entsprechenden Stellen deutlich hervorgehoben und jede Einstellung in Deutsch beschrieben (ansonsten ist alles in Englisch gehalten). Sogar Laien haben also eine faire Chance die Demonstration auf ihrem PC auszuführen.
Viel Spaß damit!
- Einen Beweis bleibe ich vorerst schuldig. Allerdings ist O(n²) naheliegend, weil der wesentliche Aufwand darin besteht während des Faltens (Phase 1) immer und immer wieder den nächsten Nachbarn zu einer Stadt zu finden. Dort sehe ich übrigens auch schon die ersten vielversprechenden Verbesserungsmöglichkeiten mittels geschickter Partitionierung der Städte (mehr dazu unter “Verbesserungsmöglichkeiten”). ↩
- Der Faktor berechnet sich als <RFA-Lösung> / <Optimale-Lösung> und gibt somit die relative Länge der RFA-Lösung zur Länge der optimalen Lösung an. ↩









