Pfade zwischen zwei Knoten eines Graphen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Wissbegierig
User
Beiträge: 1
Registriert: Montag 15. Februar 2016, 14:21
Wohnort: Erfurt
Kontaktdaten:

Hallo :)

ich bin neu im Forum und hoffe, dass ihr mir helfen könnt. Ich kenne mich leider nicht sonderlich mit Graphen aus (hab aber Vorkenntnisse) und hab daher nur etwas aus dem Internet kopiert und etwas editiert.

------------------------------------------------------------

Code: Alles auswählen

class Graph(object): #eine Graphenklasse

    class Knoten(object): #Knotenobjekt im Graph

        def __init__(self, name, connections=None):
            self.name = name
            self.connections = {}
            if connections is not None:
                self.connections.update(connections)
            
    def __init__(self, *args):
        self.Knotens = set(args) #Graph besteht aus den gegebenen Knoten
                
    def shortest_path(self, from_, to): #Algorithmus zum kürzesten Wege Problem
        P = self._algorithmus(from_)
        path, Knoten = [], to
        while not (Knoten == from_):
            if path.count(Knoten): break
            path.append(Knoten)
            Knoten = P[Knoten]
        return [from_] + list(reversed(path))
    
    def _algorithmus(self, start): #D ist der kürzeste gefundene Weg, P die Liste von den bekannten Knoten
        
        D, P = {},{}
        for Knoten in self.Knotens:
            D[Knoten.name], P[Knoten.name] = float("inf"), None
        D[start] = 0

        unseen_Knotens = list(self.Knotens)
        while unseen_Knotens:
            shortest = min(unseen_Knotens, key=lambda Knoten: D[Knoten.name]) """lambda zur Berechnung der Längen (hab das noch nicht ganz verstanden xD)"""
            unseen_Knotens.remove(shortest)
            for neighbour, distance in shortest.connections.items():
                if neighbour not in [Knoten.name for Knoten in unseen_Knotens]:
                    continue
                if D[shortest.name] + distance < D[neighbour]:
                    D[neighbour] = D[shortest.name] + distance
                    P[neighbour] = shortest.name           
        return P

#    def find_all_paths(self, from_, to, path=[]):
#        paths = []
#        Knotenliste = list(self.Knoten)
#        for Knoten in Knotenliste:
#            if Knoten not in path:
#                extended_paths = self.find_all_paths(Knoten, to, path)
#                for p in extended_paths:
#                    paths.append(p)
#        return paths
        

graph = Graph(
        Graph.Knoten('A', {'B': 3, 'G': 1, 'I': 2}),
        Graph.Knoten('B', {'C': 4, 'G': 5, 'A': 3}),
        Graph.Knoten('C', {'D': 5, 'F': 9, 'B': 4}),
        Graph.Knoten('D', {'E': 6, 'F': 3, 'C': 5}),
        Graph.Knoten('E', {'F': 2, 'H': 4, 'D': 6}),
        Graph.Knoten('F', {'G': 3, 'H': 5, 'D': 3, 'E': 2, 'C': 9}),
        Graph.Knoten('G', {'H': 6, 'B': 5, 'A': 1, 'F': 3}),
        Graph.Knoten('H', {'I': 4, 'J': 2, 'E': 4, 'F': 5, 'G': 6}),
        Graph.Knoten('I', {'J': 1, 'A': 2, 'H': 4}),
        Graph.Knoten('J', {'H': 2, 'J': 1}),
    )

x = graph.shortest_path('A', 'D')
-----------------------------------------------------------------

So, jetzt möchte ich eine Art Bogo-Sort machen, weil ich später einen Graphen erstelle, der so 400 Objekte hat und einen bestimmten Algorithmus habe (aber dann festgestellt, dass die Laufzeit expotentiell ist). Also möchte ich nun zwischen zwei Knoten alle bzw. eine nach obenbegrenzte Anzahl an Pfaden generieren (nach Zufallsprinzip und dann den Algorithmus anwenden). Ich hab leider nicht so viel Ahnung und hab das kurz probiert in der auskommentierten Funktion, aber die funktioniert nicht. Vielleicht kennt ihr auch ein bessere Darstellung für einen Graphen (die Weglängen sind leider unverzichtbar).

Kurz zusammengefasst: Zwischen zwei Knoten in einem Graphen sollen zufällig Pfade generiert werden (gleiche nicht wiederholen).

Danke im Voraus :)
BlackJack

@Wissbegierig: Standardalgorithmen und einen Graph-Datentyp braucht man sich nicht selber schreiben, dafür gibt es bereits die Networkx-Bibliothek.

Trotzdem ein paar Anmerkungen zum Quelltext: Der ist syntaktisch nicht korrekt wegen dem Zeichenkettenliteral mit dem Kommentar zu der ``lambda``-Funktion. Davon abgesehen das der Compiler an der Stelle meckert sind Zeichenketten an Stellen wo sie keinerlei Effekt haben keine Kommentare.

Kommentare sollten dem Leser einen Mehrwert liefern und nicht noch mal das offensichtlich sagen was man schon am Quelltext ablesen kann. Wer nicht verstanden hat das mit ``class Graph(object:)`` eine Graphenklasse definiert wird, dem wird auch der Kommentar ``#eine Graphenklasse`` nicht helfen. ;-) Auch sollten Kommentare nicht als Ersatz für aussagekräftige Namen herhalten müssen. Statt einbuchstabiger Namen die irgendwo in einem Kommentar erklärt werden, sollte man Namen wählen die selbsterklärend sind, so dass man beim Lesen des Codes weiss welche Bedeutung ein Namen hat, ohne den dazugehörigen Kommentar suchen zu müssen.

Python kennt keine inneren Klassen. Eine Klasse die in einer anderen Klasse definiert wird, ist einfach nur ein weiteres Klassenattribut. Mit einem gravierenden Nachteil: Exemplare dieser Klasse sind nicht serialisierbar. Also streng genommen klappt das serialisieren noch, aber deserialisieren kann man diese Objekte nicht mehr weil zu den Objekten nur Klassen- nur und Modulname gespeichert werden, und in dem Modul muss sich dann auch eine entsprechende Klasse befinden, damit der Deserialisierer aus den Daten wieder ein Objekt erstellen kann.

”*-Magie” bei Funktionssignaturen sollte man sich gut überlegen. Wenn man stattdessen eine Liste (oder Werte eines anderen Sequenztypen) übergeben kann, sollte man das bevorzugen.

Der Methodenname `_algorithmus()` ist ziemlich nichtssagend und wird spätestens dann zum Problem wenn man mehr als einen Algorithmus als Methode implementieren möchte.

``not (a == b)`` lässt sich direkter als ``a != b`` ausdrücken.

``path.count(node)`` als Bedingung ist ineffizient weil die komplette Liste durchgegangen wird um die vorkommen von `node` zu zählen, obwohl man hier nur wissen möchte ob `node` überhaupt in `path` vorkommt. Dafür gibt es den ``in``-Operator.

An der Stelle frage ich mich ob der Algorithmus überhaupt funktioniert, denn Du brichst die Schleife ab wenn ein Knoten schon im Pfad ist, also wenn im Graph ein Kreis existiert und dann geht der Code einfach davon aus das es zum Ausgangsknoten zu diesem Kreis eine Verbindung geben muss. Das muss doch aber gar nicht stimmen‽

``continue`` kann Programme sehr unübersichtlich machen und kann ein Hindernis beim refaktorisieren oder erweitern des Codes sein. Man kommt grundsätzlich ohne ``continue`` aus, und wenn das nicht einen wirklich grossen Vorteil bietet, sollte man auf diesen unbedingten Sprung, der sich nicht in der Quelltextstruktur wiederspiegelt, verzichten.

`unseen_Knotens` ist eigentlich semantisch eine Menge und keine Liste. Ausserdem ist es wirklich unpraktisch das Knoten mal durch ein Knoten-Objekt und mal durch den Namen des Knotens repräsentiert werden. Das macht das ganze unübersichtlich und ineffizient weil man einen Namen nicht mehr effizient in einer Menge von Knotenobjekten suchen kann.

Ich lande dann ungefähr bei so etwas:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf8
from __future__ import absolute_import, division, print_function


class Node(object):

    def __init__(self, name, connections=None):
        self.name = name
        self.connections = {}
        if connections is not None:
            self.connections.update(connections)


class Graph(object):
           
    def __init__(self, nodes):
        self.nodes = set(nodes)

    def _algorithmus(self, source):
        name2distance = dict.fromkeys(
            (n.name for n in self.nodes), float('inf')
        )
        name2neighbour = dict.fromkeys((n.name for n in self.nodes))
        name2distance[source] = 0
        unseen_nodes = set(self.nodes)
        while unseen_nodes:
            # 
            # ``lambda`` zur Berechnung der Längen (hab das noch nicht
            # ganz verstanden xD)
            # 
            nearest = min(unseen_nodes, key=lambda n: name2distance[n.name])
            unseen_nodes.remove(nearest)
            for neighbour, distance in nearest.connections.items():
                # 
                # FIXME Dieser Test ist ineffizient.  Hier sollte man
                #   `Node`-Objekte so implementieren, dass man sie
                #   direkt als Elemente in Mengen und als Schlüssel in
                #   Wörterbüchern verwenden kann und nicht einen Knoten
                #   mal als Namen (Zeichenkette) und mal als
                #   Knotenobjekt repräsentiert.
                # 
                if neighbour in (n.name for n in unseen_nodes):
                    distance = name2distance[nearest.name]
                    if distance + distance < name2distance[neighbour]:
                        name2distance[neighbour] = distance + distance
                        name2neighbour[neighbour] = nearest.name
        return name2neighbour
            
    def shortest_path(self, source, target):
        edges = self._algorithmus(source)
        path, node = list(), target
        while node != source:
            if node in path:
                # 
                # XXX Ist das hier korrekt?  Weiter unten wird einfach
                #   davon ausgegangen das es von `source` zu dem Pfad
                #   eine Verbindung geben muss.  Ist das tatsächlich
                #   grundsätzlich der Fall?
                # 
                break
            path.append(node)
            node = edges[node]
        return [source] + list(reversed(path))
   
    # def find_all_paths(self, source, target, path=[]):
    #     paths = list()
    #     for node in self.nodes:
    #         if node not in path:
    #             paths.extend(self.find_all_paths(node, target, path))
    #     return paths
    

def main():
    graph = Graph(
        [
            Node('A', {'B': 3, 'G': 1, 'I': 2}),
            Node('B', {'C': 4, 'G': 5, 'A': 3}),
            Node('C', {'D': 5, 'F': 9, 'B': 4}),
            Node('D', {'E': 6, 'F': 3, 'C': 5}),
            Node('E', {'F': 2, 'H': 4, 'D': 6}),
            Node('F', {'G': 3, 'H': 5, 'D': 3, 'E': 2, 'C': 9}),
            Node('G', {'H': 6, 'B': 5, 'A': 1, 'F': 3}),
            Node('H', {'I': 4, 'J': 2, 'E': 4, 'F': 5, 'G': 6}),
            Node('I', {'J': 1, 'A': 2, 'H': 4}),
            Node('J', {'H': 2, 'J': 1}),
        ]
    )
    path = graph.shortest_path('A', 'D')
    print(path)


if __name__ == '__main__':
    main()
Antworten