web-dev-qa-db-fra.com

Représentation de graphes (structure de données) dans Python

Comment représenter proprement un graphe dans Python ? (Partir de zéro c'est-à-dire pas de bibliothèques!)
Quelle structure de données (par exemple, dicts/tuples/dict (tuples)) sera rapide mais utilisera également beaucoup de mémoire?
On doit pouvoir faire divers graphes opérations dessus.

Comme indiqué, les divers représentations graphiques pourraient aider. Comment fait-on pour les implémenter en Python?

En ce qui concerne les bibliothèques, cette question a de très bonnes réponses.

88
shad0w_wa1k3r

Même si cette question est un peu ancienne, je pensais pouvoir donner une réponse pratique à quiconque tomberait dessus.

Supposons que vous obteniez vos données d'entrée pour vos connexions sous forme de liste de n-uplets comme ceci:

[('A', 'B'), ('B', 'C'), ('B', 'D'), ('C', 'D'), ('E', 'F'), ('F', 'C')]

La structure de données que j'ai trouvée la plus utile et la plus efficace pour les graphiques de Python est un dict of sets. Ce sera la structure sous-jacente de notre Graph class. Vous devez également savoir si ces connexions sont des arcs (dirigées, connectez-vous dans un sens) ou des arêtes (non dirigées, connectez les deux manières). Nous allons gérer cela en ajoutant un paramètre directed au paramètre Graph.__init__ Nous ajouterons également d'autres méthodes utiles.

from collections import defaultdict


class Graph(object):
    """ Graph data structure, undirected by default. """

    def __init__(self, connections, directed=False):
        self._graph = defaultdict(set)
        self._directed = directed
        self.add_connections(connections)

    def add_connections(self, connections):
        """ Add connections (list of Tuple pairs) to graph """

        for node1, node2 in connections:
            self.add(node1, node2)

    def add(self, node1, node2):
        """ Add connection between node1 and node2 """

        self._graph[node1].add(node2)
        if not self._directed:
            self._graph[node2].add(node1)

    def remove(self, node):
        """ Remove all references to node """

        for n, cxns in self._graph.iteritems():
            try:
                cxns.remove(node)
            except KeyError:
                pass
        try:
            del self._graph[node]
        except KeyError:
            pass

    def is_connected(self, node1, node2):
        """ Is node1 directly connected to node2 """

        return node1 in self._graph and node2 in self._graph[node1]

    def find_path(self, node1, node2, path=[]):
        """ Find any path between node1 and node2 (may not be shortest) """

        path = path + [node1]
        if node1 == node2:
            return path
        if node1 not in self._graph:
            return None
        for node in self._graph[node1]:
            if node not in path:
                new_path = self.find_path(node, node2, path)
                if new_path:
                    return new_path
        return None

    def __str__(self):
        return '{}({})'.format(self.__class__.__name__, dict(self._graph))

Je vais le laisser comme un "exercice pour le lecteur" pour créer un find_shortest_path Et d'autres méthodes.

Voyons cela en action si ...

>>> connections = [('A', 'B'), ('B', 'C'), ('B', 'D'),
                   ('C', 'D'), ('E', 'F'), ('F', 'C')]
>>> g = Graph(connections, directed=True)
>>> pprint(g._graph)
{'A': {'B'},
 'B': {'D', 'C'},
 'C': {'D'},
 'E': {'F'},
 'F': {'C'}}

>>> g = Graph(connections)  # undirected
>>> pprint(g._graph)
{'A': {'B'},
 'B': {'D', 'A', 'C'},
 'C': {'D', 'F', 'B'},
 'D': {'C', 'B'},
 'E': {'F'},
 'F': {'E', 'C'}}

>>> g.add('E', 'D')
>>> pprint(g._graph)
{'A': {'B'},
 'B': {'D', 'A', 'C'},
 'C': {'D', 'F', 'B'},
 'D': {'C', 'E', 'B'},
 'E': {'D', 'F'},
 'F': {'E', 'C'}}

>>> g.remove('A')
>>> pprint(g._graph)
{'B': {'D', 'C'},
 'C': {'D', 'F', 'B'},
 'D': {'C', 'E', 'B'},
 'E': {'D', 'F'},
 'F': {'E', 'C'}}

>>> g.add('G', 'B')
>>> pprint(g._graph)
{'B': {'D', 'G', 'C'},
 'C': {'D', 'F', 'B'},
 'D': {'C', 'E', 'B'},
 'E': {'D', 'F'},
 'F': {'E', 'C'},
 'G': {'B'}}

>>> g.find_path('G', 'E')
['G', 'B', 'D', 'C', 'F', 'E']
122
mVChr

NetworkX est une bibliothèque de graphes géniale Python. Vous aurez du mal à trouver quelque chose dont vous avez besoin et dont vous n'avez pas déjà besoin.

Et c'est open source pour que vous puissiez voir comment ils ont implémenté leurs algorithmes. Vous pouvez également ajouter des algorithmes supplémentaires.

https://github.com/networkx/networkx/tree/master/networkx/algorithms

27
jterrace

Tout d’abord, le choix des représentations classiques liste vs matrice dépend de l’objet (de ce que vous voulez faire de la représentation). Les problèmes connus et les algorithmes sont liés au choix. Le choix de la représentation abstraite dicte la manière dont elle doit être mise en œuvre.

Deuxièmement, la question est de savoir si les sommets et les arêtes ne doivent être exprimés qu'en termes d'existence ou s'ils contiennent des informations supplémentaires.

À partir de Python types de données intégrés, toute valeur contenue ailleurs est exprimée en tant que référence (masquée) à l'objet cible. S'il s'agit d'une variable (c'est-à-dire, référence nommée), alors le nom et la référence sont toujours stockés dans un dictionnaire (interne) Si vous n'avez pas besoin de noms, la référence peut alors être stockée dans votre propre conteneur - ici probablement liste Python sera toujours utilisé pour le liste en tant qu'abstraction.

La liste Python est implémentée en tant que tableau dynamique de références, Python Tuple est implémenté en tant que tableau statique de références avec un contenu constant (la valeur des références ne peut pas être modifiée). De ce fait, elles peuvent être facilement indexées. De cette façon, la liste peut également être utilisée pour la mise en œuvre de matrices.

Une autre façon de représenter les matrices sont les tableaux implémentés par le module standard array - plus contraint par rapport au type stocké, valeur homogène. Les éléments stockent la valeur directement. (La liste stocke les références aux objets de valeur à la place). De cette façon, la mémoire est plus efficace et l'accès à la valeur est plus rapide.

Parfois, vous pouvez trouver utile une représentation encore plus restreinte, telle que bytearray.

7
pepr

Il existe deux excellentes bibliothèques de graphes NetworkX et igraph . Vous pouvez trouver les deux codes sources de la bibliothèque sur GitHub. Vous pouvez toujours voir comment les fonctions sont écrites. Mais je préfère NetworkX car il est facile à comprendre.
Voir leurs codes pour savoir comment ils réalisent les fonctions. Vous obtiendrez plusieurs idées et pourrez ensuite choisir comment vous voulez créer un graphique à l'aide de structures de données.

6
Vineet Jain