Ich arbeite an einer Implementierung des Dijkstras-Algorithmus, um den kürzesten Weg zwischen miteinander verbundenen Knoten in einem Streckennetz abzurufen. Ich habe die Implentation funktioniert. Es gibt alle kürzesten Pfade zu allen Knoten zurück, wenn ich den Startknoten an den Algorithmus übergebe.
Meine Frage: Wie kann man alle möglichen Pfade von Knoten A abrufen, um Knoten G zu sagen, oder sogar alle möglichen Pfade von Knoten A und zurück zu Knoten A.
Antworten:
Das Finden aller möglichen Pfade ist ein schwieriges Problem, da es eine exponentielle Anzahl einfacher Pfade gibt. Selbst das Finden des k-ten kürzesten Pfades (oder des längsten Pfades) ist NP-schwer .
Eine mögliche Lösung, um alle Pfade (oder alle Pfade bis zu einer bestimmten Länge) von
s
bis zu finden,t
ist BFS , ohne einenvisited
Satz beizubehalten , oder für die gewichtete Version. Möglicherweise möchten Sie eine einheitliche Kostensuche verwendenBeachten Sie, dass auch in jedem Diagramm mit Zyklen [es ist keine DAG ] unendlich viele Pfade zwischen
s
bis vorhanden sein könnent
.quelle
visited
Satz verwenden (wie der ursprüngliche Algorithmus vorschlägt), da sonst nur ein Teil der Pfade angezeigt wird. Außerdem sollten Sie Pfade auf eine bestimmte Länge beschränken, um Endlosschleifen zu vermeiden [wenn der Graph Zyklen hat ...]. Viel Glück!k
kürzesten einfachen Pfad zwischen zwei Knoten. Da es höchstens(3/2)n!
solche Pfade gibt, können Sie binär suchen und herausfinden, ob es einen einfachen Längenpfad gibtn
. Dalog{(3/2)n!}
es sich um ein Polynom handeltn
, ist sowohl die Codierung der Anzahl als auch der Anzahl der erforderlichen Wiederholungen in der Eingabegröße polynomisch. Da der Algorithmus für seine Eingabe auch in Polynomzeit ausgeführt wird, ist die Gesamtlaufzeit Polynom, und die Antwort lautet, ob zwischen den Knoten ein Hamilton-Pfad besteht. Wenn also ein solcher Algorithmus existiert, ist P = NP.(3/2)n!
.Ich habe eine Version implementiert, in der im Grunde alle möglichen Pfade von einem Knoten zum anderen gefunden werden, aber keine möglichen "Zyklen" gezählt werden (das von mir verwendete Diagramm ist zyklisch). Grundsätzlich wird also kein Knoten zweimal auf demselben Pfad angezeigt. Und wenn der Graph azyklisch wäre, könnte man sagen, dass er alle möglichen Pfade zwischen den beiden Knoten zu finden scheint. Es scheint gut zu funktionieren, und für meine Grafikgröße von ~ 150 läuft es fast sofort auf meinem Computer, obwohl ich sicher bin, dass die Laufzeit ungefähr exponentiell sein muss und es daher langsam langsam wird Grafik wird größer.
Hier ist ein Java-Code, der zeigt, was ich implementiert habe. Ich bin sicher, dass es auch effizientere oder elegantere Wege geben muss.
quelle
Ich werde Ihnen eine (etwas kleine) Version (obwohl verständlich, denke ich) eines wissenschaftlichen Beweises geben, dass Sie dies nicht in einer realisierbaren Zeitspanne tun können.
Was ich beweisen werde, ist, dass die zeitliche Komplexität, um alle einfachen Pfade zwischen zwei ausgewählten und unterschiedlichen Knoten (z. B.
s
undt
) in einem beliebigen Graphen aufzulisten,G
kein Polynom ist. Beachten Sie, dass die Kantenkosten unwichtig sind, da wir uns nur um die Anzahl der Pfade zwischen diesen Knoten kümmern.Sicher, wenn das Diagramm einige gut ausgewählte Eigenschaften hat, kann dies einfach sein. Ich denke jedoch über den allgemeinen Fall nach.
Angenommen, wir haben einen Polynomalgorithmus, der alle einfachen Pfade zwischen
s
und auflistett
.Wenn
G
eine Verbindung besteht, ist die Liste nicht leer. Wenn diesG
nicht der Fall ists
undt
sich in verschiedenen Komponenten befindet, ist es wirklich einfach, alle Pfade zwischen ihnen aufzulisten, da es keine gibt! Wenn sie sich in derselben Komponente befinden, können wir so tun, als ob der gesamte Graph nur aus dieser Komponente besteht. Nehmen wir also an, dass diesG
tatsächlich verbunden ist.Die Anzahl der aufgelisteten Pfade muss dann polynomisch sein, sonst könnte der Algorithmus mir nicht alle zurückgeben. Wenn es alle aufzählt, muss es mir das längste geben, also ist es da drin. Mit der Liste der Pfade kann ein einfaches Verfahren angewendet werden, um mich auf den längsten Pfad hinzuweisen.
Wir können zeigen (obwohl ich mir keine zusammenhängende Art vorstellen kann, es auszudrücken), dass dieser längste Pfad alle Eckpunkte von durchlaufen muss
G
. Wir haben also gerade einen Hamilton-Pfad mit einer Polynomprozedur gefunden! Dies ist jedoch ein bekanntes NP-hartes Problem.Wir können dann schließen, dass dieser Polynomalgorithmus, von dem wir dachten, dass er existiert, sehr unwahrscheinlich ist , es sei denn, P = NP .
quelle
G
" durchlaufen muss, nicht unbedingt gilt. Ist das richtig?n-1
, dann gibt es. Wenn dies nicht der Fall ist, könnte es keinen solchen Pfad geben, sonst wäre er länger als Ihr bekanntester längster.Hier ist ein Algorithmus, der alle Pfade von s nach t unter Verwendung der Modifikation von DFS findet und druckt. Auch die dynamische Programmierung kann verwendet werden, um die Anzahl aller möglichen Pfade zu ermitteln. Der Pseudocode sieht folgendermaßen aus:
quelle
find_paths [s, t, d, k]
Diese Frage ist jetzt ein bisschen alt ... aber ich werde meinen Hut in den Ring werfen.
Ich persönlich finde einen Algorithmus der Form
find_paths[s, t, d, k]
nützlich, wobei:Verwenden Sie die Unendlichkeitsform Ihrer Programmiersprache für
d
undk
geben Sie alle Pfade§.Offensichtlich § wenn Sie einen gerichteten Graphen verwenden und Sie alle ungerichtete Pfade zwischen
s
undt
Sie werden diese in beide Richtungen laufen müssen:Hilfsfunktion
Ich persönlich mag Rekursion, obwohl es manchmal schwierig sein kann, lassen Sie uns trotzdem zuerst unsere Hilfsfunktion definieren:
Hauptfunktion
Damit ist die Kernfunktion trivial:
Lassen Sie uns zunächst einige Dinge beachten:
[]
Ist eine nicht initialisierte Liste, ersetzen Sie diese durch die Entsprechung für die Programmiersprache Ihrer Wahlpaths_found
wird als Referenz übergeben . Es ist klar, dass die Rekursionsfunktion nichts zurückgibt. Behandeln Sie dies angemessen.graph
wird irgendeine Form vonhashed
Struktur angenommen. Es gibt eine Vielzahl von Möglichkeiten, ein Diagramm zu implementieren. In beidengraph[vertex]
Fällen erhalten Sie eine Liste benachbarter Scheitelpunkte in einem gerichteten Diagramm - passen Sie sie entsprechend an.quelle
Wenn Sie Ihre Pfade tatsächlich vom kürzesten zum längsten Pfad ordnen möchten, ist es weitaus besser, einen modifizierten A * - oder Dijkstra-Algorithmus zu verwenden . Mit einer geringfügigen Änderung gibt der Algorithmus so viele der möglichen Pfade zurück, wie Sie möchten, und zwar in der Reihenfolge des kürzesten Pfades zuerst. Wenn Sie also wirklich alle möglichen Pfade vom kürzesten zum längsten geordnet haben möchten, ist dies der richtige Weg.
Wenn Sie eine A * -basierte Implementierung wünschen, die alle vom kürzesten zum längsten geordneten Pfade zurückgeben kann, wird dies im Folgenden erreicht. Es hat mehrere Vorteile. Zunächst einmal ist es effizient beim Sortieren vom kürzesten zum längsten. Außerdem wird jeder zusätzliche Pfad nur bei Bedarf berechnet. Wenn Sie also vorzeitig anhalten, weil Sie nicht jeden einzelnen Pfad benötigen, sparen Sie Verarbeitungszeit. Außerdem werden Daten für nachfolgende Pfade jedes Mal wiederverwendet, wenn der nächste Pfad berechnet wird, um die Effizienz zu steigern. Wenn Sie einen gewünschten Pfad finden, können Sie ihn vorzeitig abbrechen, um Rechenzeit zu sparen. Insgesamt sollte dies der effizienteste Algorithmus sein, wenn Sie nach Pfadlänge sortieren möchten.
import java.util.*; public class AstarSearch { private final Map<Integer, Set<Neighbor>> adjacency; private final int destination; private final NavigableSet<Step> pending = new TreeSet<>(); public AstarSearch(Map<Integer, Set<Neighbor>> adjacency, int source, int destination) { this.adjacency = adjacency; this.destination = destination; this.pending.add(new Step(source, null, 0)); } public List<Integer> nextShortestPath() { Step current = this.pending.pollFirst(); while( current != null) { if( current.getId() == this.destination ) return current.generatePath(); for (Neighbor neighbor : this.adjacency.get(current.id)) { if(!current.seen(neighbor.getId())) { final Step nextStep = new Step(neighbor.getId(), current, current.cost + neighbor.cost + predictCost(neighbor.id, this.destination)); this.pending.add(nextStep); } } current = this.pending.pollFirst(); } return null; } protected int predictCost(int source, int destination) { return 0; //Behaves identical to Dijkstra's algorithm, override to make it A* } private static class Step implements Comparable<Step> { final int id; final Step parent; final int cost; public Step(int id, Step parent, int cost) { this.id = id; this.parent = parent; this.cost = cost; } public int getId() { return id; } public Step getParent() { return parent; } public int getCost() { return cost; } public boolean seen(int node) { if(this.id == node) return true; else if(parent == null) return false; else return this.parent.seen(node); } public List<Integer> generatePath() { final List<Integer> path; if(this.parent != null) path = this.parent.generatePath(); else path = new ArrayList<>(); path.add(this.id); return path; } @Override public int compareTo(Step step) { if(step == null) return 1; if( this.cost != step.cost) return Integer.compare(this.cost, step.cost); if( this.id != step.id ) return Integer.compare(this.id, step.id); if( this.parent != null ) this.parent.compareTo(step.parent); if(step.parent == null) return 0; return -1; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Step step = (Step) o; return id == step.id && cost == step.cost && Objects.equals(parent, step.parent); } @Override public int hashCode() { return Objects.hash(id, parent, cost); } } /******************************************************* * Everything below here just sets up your adjacency * * It will just be helpful for you to be able to test * * It isnt part of the actual A* search algorithm * ********************************************************/ private static class Neighbor { final int id; final int cost; public Neighbor(int id, int cost) { this.id = id; this.cost = cost; } public int getId() { return id; } public int getCost() { return cost; } } public static void main(String[] args) { final Map<Integer, Set<Neighbor>> adjacency = createAdjacency(); final AstarSearch search = new AstarSearch(adjacency, 1, 4); System.out.println("printing all paths from shortest to longest..."); List<Integer> path = search.nextShortestPath(); while(path != null) { System.out.println(path); path = search.nextShortestPath(); } } private static Map<Integer, Set<Neighbor>> createAdjacency() { final Map<Integer, Set<Neighbor>> adjacency = new HashMap<>(); //This sets up the adjacencies. In this case all adjacencies have a cost of 1, but they dont need to. addAdjacency(adjacency, 1,2,1,5,1); //{1 | 2,5} addAdjacency(adjacency, 2,1,1,3,1,4,1,5,1); //{2 | 1,3,4,5} addAdjacency(adjacency, 3,2,1,5,1); //{3 | 2,5} addAdjacency(adjacency, 4,2,1); //{4 | 2} addAdjacency(adjacency, 5,1,1,2,1,3,1); //{5 | 1,2,3} return Collections.unmodifiableMap(adjacency); } private static void addAdjacency(Map<Integer, Set<Neighbor>> adjacency, int source, Integer... dests) { if( dests.length % 2 != 0) throw new IllegalArgumentException("dests must have an equal number of arguments, each pair is the id and cost for that traversal"); final Set<Neighbor> destinations = new HashSet<>(); for(int i = 0; i < dests.length; i+=2) destinations.add(new Neighbor(dests[i], dests[i+1])); adjacency.put(source, Collections.unmodifiableSet(destinations)); } }
Die Ausgabe des obigen Codes lautet wie folgt:
Beachten Sie, dass bei jedem Aufruf
nextShortestPath()
bei Bedarf der nächst kürzere Pfad für Sie generiert wird. Es werden nur die zusätzlichen erforderlichen Schritte berechnet und keine alten Pfade zweimal durchlaufen. Wenn Sie entscheiden, dass Sie nicht alle Pfade benötigen und die Ausführung vorzeitig beenden, haben Sie sich außerdem erhebliche Rechenzeit gespart. Sie berechnen nur bis zur Anzahl der benötigten Pfade und nicht mehr.Schließlich sollte angemerkt werden, dass die A * - und Dijkstra-Algorithmen einige geringfügige Einschränkungen aufweisen, obwohl ich nicht glaube, dass dies Auswirkungen auf Sie haben würde. Es funktioniert nämlich nicht richtig in einem Diagramm mit negativen Gewichten.
Hier ist ein Link zu JDoodle, über den Sie den Code selbst im Browser ausführen und sehen können, wie er funktioniert. Sie können das Diagramm auch ändern, um anzuzeigen, dass es auch in anderen Diagrammen funktioniert: http://jdoodle.com/a/ukx
quelle
Die folgenden Funktionen (modifiziertes BFS mit einer rekursiven Pfadfindungsfunktion zwischen zwei Knoten) erledigen die Aufgabe für einen azyklischen Graphen :
Zum Beispiel mit dem folgenden Diagramm ( DAG )
G
vonWenn wir alle Pfade zwischen den Knoten
'A'
und'F'
(unter Verwendung der oben definierten Funktionen alsfind_all_paths(find_all_parents(G, 'A'), 'A', 'F')
) finden möchten , werden die folgenden Pfade zurückgegeben:quelle
Normalerweise möchten Sie das nicht, da es in nichttrivialen Diagrammen eine exponentielle Anzahl davon gibt. Wenn Sie wirklich alle (einfachen) Pfade oder alle (einfachen) Zyklen erhalten möchten, finden Sie nur einen (indem Sie durch die Grafik gehen) und kehren dann zu einem anderen zurück.
quelle
Ich denke, was Sie wollen, ist eine Form des Ford-Fulkerson-Algorithmus, der auf BFS basiert. Es wird verwendet, um den maximalen Fluss eines Netzwerks zu berechnen, indem alle Erweiterungspfade zwischen zwei Knoten ermittelt werden.
http://en.wikipedia.org/wiki/Ford%E2%80%93Fulkerson_algorithm
quelle
Ich nehme an, Sie möchten 'einfache' Pfade finden (ein Pfad ist einfach, wenn kein Knoten mehr als einmal darin erscheint, außer vielleicht dem ersten und dem letzten).
Da das Problem NP-schwer ist, möchten Sie möglicherweise eine Variante der Tiefensuche durchführen.
Generieren Sie grundsätzlich alle möglichen Pfade aus A und prüfen Sie, ob sie in G enden.
quelle
Es gibt einen schönen Artikel, der Ihre Frage beantworten kann / nur er druckt die Pfade, anstatt sie zu sammeln /. Bitte beachten Sie, dass Sie mit den C ++ / Python-Beispielen in der Online-IDE experimentieren können.
http://www.geeksforgeeks.org/find-paths-given-source-destination/
quelle