Wie kann ich ALLE Zyklen in einem gerichteten Graphen von / zu einem bestimmten Knoten finden (durchlaufen)?
Zum Beispiel möchte ich so etwas:
A->B->A
A->B->C->A
aber nicht: B-> C-> B.
algorithm
graph-theory
graph-algorithm
user7305
quelle
quelle
Antworten:
Ich habe diese Seite in meiner Suche gefunden und da Zyklen nicht mit stark verbundenen Komponenten identisch sind, habe ich weiter gesucht und schließlich einen effizienten Algorithmus gefunden, der alle (elementaren) Zyklen eines gerichteten Graphen auflistet. Es ist von Donald B. Johnson und das Papier kann unter dem folgenden Link gefunden werden:
http://www.cs.tufts.edu/comp/150GA/homeworks/hw1/Johnson%2075.PDF
Eine Java-Implementierung finden Sie in:
http://normalisiert.de/code/java/elementaryCycles.zip
EIN Mathematica- Demonstration des Johnson-Algorithmus finden Sie hier . Die Implementierung kann von rechts heruntergeladen werden ( "Autorcode herunterladen" ).
Hinweis: Tatsächlich gibt es viele Algorithmen für dieses Problem. Einige von ihnen sind in diesem Artikel aufgeführt:
http://dx.doi.org/10.1137/0205007
Laut dem Artikel ist Johnsons Algorithmus der schnellste.
quelle
A->B->C->A
elementar?simple_cycle
in networkx implementiert.Die Tiefensuche mit Backtracking sollte hier funktionieren. Behalten Sie ein Array von Booleschen Werten bei, um zu verfolgen, ob Sie zuvor einen Knoten besucht haben. Wenn Ihnen die neuen Knoten ausgehen (ohne einen Knoten zu treffen, den Sie bereits getroffen haben), gehen Sie einfach zurück und versuchen Sie es mit einem anderen Zweig.
Das DFS ist einfach zu implementieren, wenn Sie eine Adjazenzliste zur Darstellung des Diagramms haben. Zum Beispiel gibt adj [A] = {B, C} an, dass B und C die Kinder von A sind.
Zum Beispiel Pseudocode unten. "start" ist der Knoten, von dem aus Sie starten.
Rufen Sie die obige Funktion mit dem Startknoten auf:
quelle
if (node == start):
- was istnode and start
in dem ersten Aufrufstart
). Es beginnt an diesem Scheitelpunkt und führt eine DFS durch, bis es wieder zu diesem Scheitelpunkt zurückkehrt. Dann weiß es, dass es einen Zyklus gefunden hat. Die Zyklen werden jedoch nicht ausgegeben, sondern nur gezählt (aber es sollte nicht allzu schwierig sein, sie zu ändern, um dies zu tun).start
. Sie müssen die besuchten Flaggen nicht wirklich löschen, da jede besuchte Flagge aufgrund von gelöscht wirdvisited[node]=NO;
. Aber denken Sie daran, dass Sie, wenn Sie einen Zyklus habenA->B->C->A
, diesen dreimal erkennen, ebensostart
wie drei davon. Eine Idee, um dies zu verhindern, besteht darin, ein anderes besuchtes Array zu haben, in dem jeder Knoten festgelegt ist, derstart
zu einem bestimmten Zeitpunkt der Knoten war, und diese dann nicht erneut aufzurufen.Zuallererst - Sie möchten nicht wirklich versuchen, buchstäblich alle Zyklen zu finden, denn wenn es 1 gibt, gibt es unendlich viele davon. Zum Beispiel ABA, ABABA usw. Oder es kann möglich sein, 2 Zyklen zu einem 8-ähnlichen Zyklus usw. usw. zusammenzufügen. Der sinnvolle Ansatz besteht darin, nach allen sogenannten einfachen Zyklen zu suchen - nach solchen, die sich nur kreuzen im Start- / Endpunkt. Wenn Sie möchten, können Sie dann Kombinationen einfacher Zyklen generieren.
Einer der Basisalgorithmen zum Auffinden aller einfachen Zyklen in einem gerichteten Diagramm ist folgender: Führen Sie eine Tiefen-Erst-Durchquerung aller einfachen Pfade (diejenigen, die sich nicht kreuzen) im Diagramm durch. Jedes Mal, wenn der aktuelle Knoten einen Nachfolger auf dem Stapel hat, wird ein einfacher Zyklus entdeckt. Es besteht aus den Elementen auf dem Stapel, die mit dem identifizierten Nachfolger beginnen und mit der Oberseite des Stapels enden. Die Tiefenüberquerung aller einfachen Pfade ähnelt der Tiefensuche, Sie markieren / zeichnen jedoch keine anderen besuchten Knoten als die derzeit auf dem Stapel befindlichen als Stopppunkte auf.
Der obige Brute-Force-Algorithmus ist furchtbar ineffizient und erzeugt zusätzlich mehrere Kopien der Zyklen. Es ist jedoch der Ausgangspunkt mehrerer praktischer Algorithmen, die verschiedene Verbesserungen anwenden, um die Leistung zu verbessern und Zyklusduplikationen zu vermeiden. Ich war überrascht, vor einiger Zeit herauszufinden, dass diese Algorithmen in Lehrbüchern und im Internet nicht ohne weiteres verfügbar sind. Also habe ich einige Nachforschungen angestellt und 4 solcher Algorithmen und 1 Algorithmus für Zyklen in ungerichteten Graphen in einer Open-Source-Java-Bibliothek hier implementiert: http://code.google.com/p/niographs/ .
Übrigens, da ich ungerichtete Graphen erwähnt habe: Der Algorithmus für diese ist unterschiedlich. Erstellen Sie einen Spannbaum, und dann bildet jede Kante, die nicht Teil des Baums ist, zusammen mit einigen Kanten im Baum einen einfachen Zyklus. Die so gefundenen Zyklen bilden eine sogenannte Zyklusbasis. Alle einfachen Zyklen können dann gefunden werden, indem 2 oder mehr verschiedene Basiszyklen kombiniert werden. Weitere Informationen finden Sie beispielsweise unter: http://dspace.mit.edu/bitstream/handle/1721.1/68106/FTL_R_1982_07.pdf .
quelle
jgrapht
die in verwendet wirdhttp://code.google.com/p/niographs/
, können Sie ein Beispiel von github.com/jgrapht/jgrapht/wiki/DirectedGraphDemoDie einfachste Wahl, die ich gefunden habe, um dieses Problem zu lösen, war die Verwendung der aufgerufenen Python-Bibliothek
networkx
.Es implementiert den Johnson-Algorithmus, der in der besten Antwort auf diese Frage erwähnt wird, ist jedoch recht einfach auszuführen.
Kurz gesagt, Sie benötigen Folgendes:
Antwort: [['a', 'b', 'd', 'e'], ['a', 'b', 'c']]
quelle
nx.DiGraph({'a': ['b'], 'b': ['c','d'], 'c': ['a'], 'd': ['e'], 'e':['a']})
Zu klären:
Stark verbundene Komponenten finden alle Untergraphen, die mindestens einen Zyklus enthalten, nicht alle möglichen Zyklen im Diagramm. Wenn Sie beispielsweise alle stark verbundenen Komponenten nehmen und jede einzelne zu einem Knoten zusammenfassen / gruppieren / zusammenführen (dh einen Knoten pro Komponente), erhalten Sie einen Baum ohne Zyklen (tatsächlich eine DAG). Jede Komponente (im Grunde genommen ein Untergraph mit mindestens einem Zyklus) kann intern viel mehr mögliche Zyklen enthalten, sodass SCC NICHT alle möglichen Zyklen findet, sondern alle möglichen Gruppen mit mindestens einem Zyklus und wenn Sie gruppieren ihnen, dann hat der Graph keine Zyklen.
Um alle einfachen Zyklen in einem Diagramm zu finden, wie bereits erwähnt, ist Johnsons Algorithmus ein Kandidat.
quelle
Ich habe dies einmal als Interviewfrage erhalten. Ich vermute, dass Ihnen dies passiert ist und Sie hierher kommen, um Hilfe zu erhalten. Teilen Sie das Problem in drei Fragen auf und es wird einfacher.
Problem 1) Verwenden Sie das Iteratormuster, um Routenergebnisse zu iterieren. Ein guter Ort, um die Logik zu setzen, um die nächste Route zu erhalten, ist wahrscheinlich der "moveNext" Ihres Iterators. Um eine gültige Route zu finden, hängt dies von Ihrer Datenstruktur ab. Für mich war es eine SQL-Tabelle voller gültiger Routenmöglichkeiten, daher musste ich eine Abfrage erstellen, um die gültigen Ziele unter Angabe einer Quelle zu erhalten.
Problem 2) Schieben Sie jeden Knoten, sobald Sie ihn finden, in eine Sammlung, sobald Sie ihn erhalten. Dies bedeutet, dass Sie sehr leicht feststellen können, ob Sie sich über einen Punkt "verdoppeln", indem Sie die Sammlung abfragen, die Sie im laufenden Betrieb erstellen.
Problem 3) Wenn Sie zu irgendeinem Zeitpunkt sehen, dass Sie sich verdoppeln, können Sie Dinge aus der Sammlung entfernen und "sichern". Versuchen Sie dann von diesem Punkt an, erneut "vorwärts" zu gehen.
Hack: Wenn Sie SQL Server 2008 verwenden, gibt es einige neue "Hierarchie" -Dinge, mit denen Sie dies schnell lösen können, wenn Sie Ihre Daten in einem Baum strukturieren.
quelle
Die DFS-basierten Varianten mit Hinterkanten finden zwar Zyklen, in vielen Fällen handelt es sich jedoch NICHT um minimale Zyklen. Im Allgemeinen gibt Ihnen DFS das Flag, dass es einen Zyklus gibt, aber es ist nicht gut genug, um tatsächlich Zyklen zu finden. Stellen Sie sich zum Beispiel 5 verschiedene Zyklen vor, die sich zwei Kanten teilen. Es gibt keine einfache Möglichkeit, Zyklen nur mit DFS zu identifizieren (einschließlich Backtracking-Varianten).
Johnsons Algorithmus liefert in der Tat alle einzigartigen einfachen Zyklen und weist eine gute zeitliche und räumliche Komplexität auf.
Wenn Sie jedoch nur MINIMALE Zyklen finden möchten (was bedeutet, dass möglicherweise mehr als ein Zyklus durch einen beliebigen Scheitelpunkt verläuft und wir daran interessiert sind, minimale Zyklen zu finden) UND Ihr Diagramm nicht sehr groß ist, können Sie versuchen, die folgende einfache Methode zu verwenden. Es ist sehr einfach, aber im Vergleich zu Johnson ziemlich langsam.
Eine der absolut einfachsten Möglichkeiten, MINIMAL-Zyklen zu finden, ist die Verwendung des Floyd-Algorithmus, um mithilfe der Adjazenzmatrix minimale Pfade zwischen allen Scheitelpunkten zu finden. Dieser Algorithmus ist bei weitem nicht so optimal wie der von Johnson, aber er ist so einfach und seine innere Schleife ist so eng, dass es für kleinere Graphen (<= 50-100 Knoten) absolut sinnvoll ist, ihn zu verwenden. Die zeitliche Komplexität ist O (n ^ 3), die räumliche Komplexität O (n ^ 2), wenn Sie die übergeordnete Verfolgung verwenden, und O (1), wenn Sie dies nicht tun. Lassen Sie uns zunächst die Antwort auf die Frage finden, ob es einen Zyklus gibt. Der Algorithmus ist kinderleicht. Unten ist ein Ausschnitt in Scala.
Ursprünglich arbeitet dieser Algorithmus mit einem Graphen mit gewichteten Kanten, um alle kürzesten Pfade zwischen allen Knotenpaaren zu finden (daher das Argument für Gewichte). Damit es richtig funktioniert, müssen Sie 1 angeben, wenn sich zwischen den Knoten eine gerichtete Kante befindet, oder NO_EDGE. Nachdem der Algorithmus ausgeführt wurde, können Sie die Hauptdiagonale überprüfen, wenn Werte kleiner als NO_EDGE vorhanden sind, als dieser Knoten an einem Zyklus mit einer Länge teilnimmt, die dem Wert entspricht. Jeder andere Knoten desselben Zyklus hat denselben Wert (auf der Hauptdiagonale).
Um den Zyklus selbst zu rekonstruieren, müssen wir eine leicht modifizierte Version des Algorithmus mit übergeordnetem Tracking verwenden.
Die Elternmatrix sollte anfänglich den Quellscheitelpunktindex in einer Randzelle enthalten, wenn sich zwischen den Scheitelpunkten eine Kante befindet, andernfalls -1. Nach der Rückkehr der Funktion haben Sie für jede Kante einen Verweis auf den übergeordneten Knoten im kürzesten Pfadbaum. Und dann ist es einfach, tatsächliche Zyklen wiederherzustellen.
Alles in allem haben wir das folgende Programm, um alle minimalen Zyklen zu finden
und eine kleine Hauptmethode, um das Ergebnis zu testen
und die Ausgabe ist
quelle
Im Fall eines ungerichteten Graphen bietet ein kürzlich veröffentlichtes Papier ( Optimale Auflistung von Zyklen und St-Pfaden in ungerichteten Graphen ) eine asymptotisch optimale Lösung. Sie können es hier lesen http://arxiv.org/abs/1205.2766 oder hier http://dl.acm.org/citation.cfm?id=2627951 Ich weiß, es beantwortet Ihre Frage nicht, aber seit dem Titel von In Ihrer Frage wird die Richtung nicht erwähnt. Möglicherweise ist sie für die Google-Suche hilfreich
quelle
Beginnen Sie am Knoten X und suchen Sie nach allen untergeordneten Knoten (übergeordnete und untergeordnete Knoten sind äquivalent, wenn sie nicht gerichtet sind). Markieren Sie diese untergeordneten Knoten als Kinder von X. Markieren Sie von einem solchen untergeordneten Knoten A aus Kinder von A, X ', wobei X' als 2 Schritte entfernt markiert ist.). Wenn Sie später X drücken und es als untergeordnetes Element von X '' markieren, bedeutet dies, dass sich X in einem Zyklus mit 3 Knoten befindet. Das Zurückverfolgen zu seinem übergeordneten Element ist einfach (wie es ist, unterstützt der Algorithmus dies nicht, sodass Sie feststellen würden, welches übergeordnete Element X 'hat).
Hinweis: Wenn der Graph ungerichtet ist oder bidirektionale Kanten aufweist, wird dieser Algorithmus komplizierter, vorausgesetzt, Sie möchten dieselbe Kante nicht zweimal für einen Zyklus durchlaufen.
quelle
Wenn Sie alle Elementarschaltungen in einem Diagramm finden möchten, können Sie den EC-Algorithmus von JAMES C. TIERNAN verwenden, der seit 1970 auf einem Papier zu finden ist.
Der sehr originelle EC-Algorithmus, wie ich ihn in PHP implementiert habe (ich hoffe, es gibt keine Fehler, wird unten gezeigt). Es kann auch Schleifen finden, wenn es welche gibt. Die Schaltungen in dieser Implementierung (die versucht, das Original zu klonen) sind die Nicht-Null-Elemente. Null steht hier für Nichtexistenz (Null, wie wir es kennen).
Abgesehen davon folgt unten eine andere Implementierung, die dem Algorithmus mehr Unabhängigkeit verleiht. Dies bedeutet, dass die Knoten von jedem Ort aus starten können, selbst von negativen Zahlen, z. B. -4, -3, -2 usw.
In beiden Fällen müssen die Knoten sequentiell sein.
Möglicherweise müssen Sie das Originalpapier, James C. Tiernan Elementary Circuit Algorithm , studieren
Dann ist dies die andere Implementierung, die unabhängiger vom Diagramm ist, ohne goto und ohne Array-Werte. Stattdessen werden Array-Schlüssel verwendet. Der Pfad, das Diagramm und die Schaltkreise werden als Array-Schlüssel gespeichert (verwenden Sie Array-Werte, wenn Sie möchten, ändern Sie einfach die erforderlichen Linien). Das Beispieldiagramm beginnt bei -4, um seine Unabhängigkeit zu zeigen.
Ich habe die EU analysiert und dokumentiert, aber leider ist die Dokumentation in Griechisch.
quelle
Es gibt zwei Schritte (Algorithmen), um alle Zyklen in einer DAG zu finden.
Der erste Schritt besteht darin, den Tarjan-Algorithmus zu verwenden, um den Satz stark verbundener Komponenten zu finden.
Der zweite Schritt besteht darin, Zyklen (Pfade) innerhalb der verbundenen Komponenten zu finden. Mein Vorschlag ist, eine modifizierte Version des Hierholzer-Algorithmus zu verwenden.
Die Idee ist:
Hier ist der Link zu einer Java-Implementierung mit einem Testfall:
http://stones333.blogspot.com/2013/12/find-cycles-in-directed-graph-dag.html
quelle
http://www.me.utexas.edu/~bard/IP/Handouts/cycles.pdf
quelle
Ich bin über den folgenden Algorithmus gestolpert, der effizienter zu sein scheint als Johnsons Algorithmus (zumindest für größere Diagramme). Ich bin mir jedoch nicht sicher über die Leistung im Vergleich zu Tarjans Algorithmus.
Außerdem habe ich es bisher nur auf Dreiecke überprüft. Bei Interesse lesen Sie bitte "Arboricity and Subgraph Listing Algorithms" von Norishige Chiba und Takao Nishizeki ( http://dx.doi.org/10.1137/0214017 ).
quelle
Javascript-Lösung mit disjunkten verknüpften Listen. Kann aktualisiert werden, um festgelegte Gesamtstrukturen für schnellere Laufzeiten zu trennen.
quelle
DFS vom Startknoten s, verfolgen Sie den DFS-Pfad während des Durchlaufs und zeichnen Sie den Pfad auf, wenn Sie eine Kante vom Knoten v im Pfad zu s finden. (v, s) ist eine Hinterkante im DFS-Baum und zeigt somit einen Zyklus an, der s enthält.
quelle
Weitere Informationen zu Ihrer Frage zum Permutationszyklus finden Sie hier: https://www.codechef.com/problems/PCYCLE
Sie können diesen Code ausprobieren (geben Sie die Größe und die Ziffernnummer ein):
quelle
DFS c ++ - Version für den Pseudocode in der Antwort im zweiten Stock:
quelle