161 votes

Représentation des graphiques (structure de données) en Python

Comment peut-on représenter proprement un graphique en Python? (En partant de zéro, c'est-à-dire sans bibliothèques!)
Quelle structure de données (par exemple des dictionnaires/tuples/dict(tuples)) sera rapide mais aussi efficace en mémoire?
Il faudra pouvoir effectuer diverses opérations de graphiques dessus.

Comme mentionné, les différentes représentations de graphique pourraient aider. Comment les implémenter en Python?

Pour ce qui est des bibliothèques, cette question a de très bonnes réponses.

206voto

mVChr Points 26738

Même si c'est une question quelque peu ancienne, j'ai pensé donner une réponse pratique à quiconque tomberait dessus.

Disons que vous obtenez vos données d'entrée pour vos connexions sous forme d'une liste de tuples 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 efficace pour les graphiques en Python est un dictionnaire d'ensembles. Cela sera la structure sous-jacente pour notre classe Graph. Vous devez également savoir si ces connexions sont des arcs (dirigés, connectent dans un sens) ou des arêtes (non dirigés, connectent dans les deux sens). Nous gérerons cela en ajoutant un paramètre directed à la méthode Graph.__init__. Nous ajouterons également quelques autres méthodes utiles.

import pprint
from collections import defaultdict

class Graph(object):
    """ Structure de données de graphique, non dirigée par défaut. """

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

    def add_connections(self, connections):
        """ Ajouter des connexions (liste de paires de tuples) au graphique """

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

    def add(self, node1, node2):
        """ Ajouter une connexion entre le nœud1 et le nœud2 """

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

    def remove(self, node):
        """ Supprimer toutes les références au nœud """

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

    def is_connected(self, node1, node2):
        """ Le nœud1 est-il directement connecté au nœud2 """

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

    def find_path(self, node1, node2, path=[]):
        """ Trouver un chemin entre le nœud1 et le nœud2 (peut ne pas être le plus court) """

        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 laisse comme "exercice pour le lecteur" la création d'une méthode find_shortest_path et d'autres méthodes.

Voyons cela en action...

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

>>> g = Graph(connections)  # non dirigé
>>> pretty_print = pprint.PrettyPrinter()
>>> pretty_print.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')
>>> pretty_print.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')
>>> pretty_print.pprint(g._graph)
{'B': {'D', 'C'},
 'C': {'D', 'F', 'B'},
 'D': {'C', 'E', 'B'},
 'E': {'D', 'F'},
 'F': {'E', 'C'}}

>>> g.add('G', 'B')
>>> pretty_print.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']

50voto

jterrace Points 21939

NetworkX est une superbe bibliothèque de graphes Python. Vous aurez du mal à trouver quelque chose dont vous avez besoin et qu'il ne fait pas déjà.

Et c'est open source donc vous pouvez 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

10voto

Vineet Jain Points 851

Il y a deux excellentes bibliothèques graphiques NetworkX et igraph. Vous pouvez trouver les codes sources des deux bibliothèques sur GitHub. Vous pouvez toujours voir comment les fonctions sont écrites. Mais je préfère NetworkX car c'est facile à comprendre.
Voyez leurs codes pour savoir comment ils créent les fonctions. Vous obtiendrez plusieurs idées et pourrez ensuite choisir comment vous voulez créer un graphe en utilisant des structures de données.

9voto

pepr Points 4263

Tout d'abord, le choix entre les représentations classiques de liste et de matrice dépend de l'objectif (de ce que vous voulez faire avec la représentation). Les problèmes et algorithmes bien connus sont liés à ce choix. Le choix de la représentation abstraite dicte en quelque sorte la manière dont elle doit être implémentée.

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

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

Une liste Python est implémentée comme un tableau dynamique de références, un tuple Python est implémenté comme un tableau statique de références avec un contenu constant (la valeur des références ne peut pas être modifiée). C'est pourquoi ils peuvent être facilement indexés. De cette manière, la liste peut également être utilisée pour l'implémentation de matrices.

Une autre manière de représenter les matrices est les tableaux implémentés par le module standard array - plus contraignant en ce qui concerne le type stocké, une valeur homogène. Les éléments stockent la valeur directement. (La liste stocke les références aux objets valeurs). De cette manière, elle est plus efficace en termes de mémoire et l'accès à la valeur est également plus rapide.

Parfois, vous pouvez même trouver une représentation encore plus restreinte comme bytearray.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X