Graphentheorie: Alle Wege finden von A nach B, im ungerichteten Graphen

    • VB.NET
    • .NET (FX) 4.5–4.8

      Graphentheorie: Alle Wege finden von A nach B, im ungerichteten Graphen

      Ich hab einen Algo mir ausgedacht mit exorbitant besserer Laufzeit als das AFAIK übliche BruteForce (alle Wege durchprobieren).
      Wohlgemerkt - es geht nicht um den kürzesten Weg - das ist ja vom GottVater der Algorithmik: Dijkstra bereits abschliessend gelöst - (bzw. ich kannte sowas unter dem Namen "FloodFill").
      Sondern es geht um alle Wege!
      Auch nicht um alle Wege im Graphen, sondern um alle Wege von A nach B.
      Ich hab auch gegoogelt, fand aber nur Arbeiten, die BruteForce-Ansätze vorstellten.
      Beziehungsweise es wurde von "GanzZahl-Programmen" geredet, was ich nicht verstanden hab. Was aber als Programm auch eine miserable Laufzeit hatte.

      Was ist ein Graph?
      Ein Graph ist ein Dingens, bestehend aus sog. "Knoten" und "Kanten", und die Kanten verbinden die Knoten. Die Graphentheorie ist ein eigenes Teilgebiet in der Mathematik, von enormer Wichtigkeit (Wegfindung, allerlei OptimierungsProbleme).

      "Labeled Search"
      Mein Algo gehört zu den sog. "labeled Search-Algorithms", also an den zu durchsuchenden Daten werden während der Suche Zusatz-Informationen gespeichert, die für die weitere Suche hilfreich sind.
      Ich treibe dieses Labelling-Prinzip ziemlich weit, nämlich ich speichere
      am Knoten:
      • IsOnStack, also ob der Knoten im RekursionsStack des aktuellen (unfertigen) Suchvorgangs liegt
      • Known, ob der Knoten überhaupt einmal besucht wurde
      • Success, ob der Knoten auf einem der (gesuchten) Pfade von A nach B liegt
      an der Kante:
      • PassedState: ob die Kante in die eine, die andere oder beide Richtungen zum Ziel führt - oder nicht
      • SkipEdges: Der Algo erfordert, dass wenn mehrere Kanten eine unverzweigte Kette bilden, dass man die als eine einzige Kante behandeln kann
      Die "Erfindung"
      Jo, Kern meiner Erfindung ist, wie ich die Ergebnisse präsentiere: Nämlich nicht als eine (u.U. irrsinnig lange) Liste von Pfaden, sondern ich trage die Wege "einfach" als Pfeilung in den Graphen ein.
      Die Ausgabe dieser Wege ist dann selbst über eine (triviale) rekursive Suche: immer die Pfeile nach.
      85 Kanten
      So sehen z.B. die ca. 52000 Wege aus, die im gezeigten Graphen vom Knoten (1/1) nach (9/9) führen. :D
      Erst diese Ergebniss-Sicht hat mich auf die Lösungs-Idee gebracht: Ich muss nicht 52000 Wege suchen, sondern ich muss nur popelige 85 Kanten mit der richtigen Pfeilung ausstatten.
      Mathematisch gesprochen erzeuge ich also im ungerichteten GesamtGraphen einen gerichteten TeilGraphen. (Springt im Bildle nicht soo in Auge, dass die Pfeile nur ein Teilgraph sind, aber die ungepfeilten Kanten gehören ebenfalls zum GesamtGraph)

      Dazu gibts eine klein Anwendung, wo man selber Graphen basteln kann: Nämlich die Kästchen sind Knoten, und durch (mehrmals) draufklicken bestimmt man Start und Ziel (das grössere rote Rechteck).
      Zwischen die Knoten kann man auch klicksen, das bestimmt dann eine Kante. Die Kanten kann man sogar 5-fach klicksen, für:
      • Horizontal
      • Vertikal
      • AufwärtsDiagonal
      • AbwärtsDiagonal
      • Keine
      Ein Knoten kann also bei mir über bis zu 8 Kanten verknüpft sein, Und es sind auch keine Kanten-Überquerungen möglich. So hab ich mir halt die Bedingungen zurechtgemacht, um dagegen zu entwickeln. In RealWorld kommen natürlich deutlich komplexere Graphen vor.

      Herausforderungen
      Der Algorithmus selbst hat ein Problem: Er ist praktisch ziemlich kompliziert.
      Aber im Prinzip isser höchst einfach: Ich lehne es ab, einen Knoten mehrmals zu besuchen.
      Was muss ich eine Kante zweimal durchlaufen, wenn ich doch schon beim ersten Mal festgestellt hab, dass sie nicht zum Ziel führt?
      Oder dass sie zum Ziel führt und in welche Richtung.

      Der Algo schlägt sich v.a. mit zwei Problemen herum:
      1. Kreise
        Stösst die Suche auf einen Knoten im eigenen RekursionsStack, so liegt ein Kreis vor, und ein Weiter-Laufen würde zum StackOverflow führen.
        Ich laufe da natürlich nicht weiter, sondern kehre aus der Rekursion zurück. Und im Rekursions-Aufstieg drehe ich die Pfeilung um. So wird aus dem ursprünglichen "Rückweg-Teil" des Kreises eine verzweigte Parallel-Strecke
      2. Bidirektionale Kanten
        Manchmal durchlaufen verschiedene Wege dieselbe Kante in entgegengesetzter Richtungen. Die folgende Figur etwa kann auf 4 Wegen durchlaufen werden:

        Der eigentliche Such-Algo (der ja jeden Knoten nur einmal besucht) kann diese Konstellation nicht feststellen, und findet nur 3 Wege (immerhin!). Im Nachgang werden daher alle zielführenden Kanten (aufwändig) geprüft, ob die Bedingungen für Bidirektionalität vorliegen.
      Jo, wie gesagt: Dank der aufwändigen Belabelung latscht der Algo alle Knoten in einer rekursiven Tiefensuche nur einmal ab, und dann können alle Wege ausgegeben werden.

      Laufzeit
      Leider bin ich nicht imstande, einen Algorithmus mathematisch korrekt zu untersuchen und insbes. sein Laufzeitverhalten anzugeben.
      Aber das BeispielProgramm hat auch einen Button "DeepSrch" - damit ist die gewöhnliche rekursive Tiefensuche aller Wege von A nach B gemeint - als Referenz für die Korrektheit meines Algos.
      "DeepSrch" braucht bei mir für obige "85 Kanten" ca. 60 Sekunden, wo mein Algo nur "Flupp!" macht.
      Dateien

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()