Rappresentare grafici (struttura dati) in Python


105

Come si può rappresentare in modo ordinato un grafico in Python ? (Partendo da zero, cioè nessuna libreria!)
Quale struttura dati (es. Dicts / tuples / dict (tuple)) sarà veloce ma anche efficiente in termini di memoria?
Uno deve essere in grado di eseguire varie operazioni sui grafici su di esso.

Come sottolineato, le varie rappresentazioni grafiche potrebbero aiutare. Come si fa a implementarli in Python?

Per quanto riguarda le biblioteche, questa domanda ha risposte abbastanza buone.



1
Per l'implementazione di un grafico guarda l'articolo di Wikipedia che elenca le implementazioni comuni e la loro efficienza sia in memoria che in velocità: en.wikipedia.org/wiki/…
Kassym Dorsel

Potresti provare GitHub.com/thePastor/pangaia. Ha bisogno di una piccola riscrittura per usare il defaultdict della libreria standard (che non era disponibile quando il codice è stato scritto). Utilizza una struttura dati ricorsiva per renderlo più elegante rispetto ad altre implementazioni.
medico l'

1
Per i grafici diretti , questo saggio da python.org suggerisce una dictdi lists. Fondamentalmente qualcosa di simile {<parent>: [<child>, ...], ...}.
djvg

È possibile implementare l'utilizzo del dizionario come elenco di adiacenza con chiavi come nodi e valori come elenco di nodi adiacenti per ciascuna chiave.
Shahrukh khan

Risposte:


140

Anche se questa è una domanda un po 'vecchia, ho pensato di dare una risposta pratica per chiunque si imbattesse in questo.

Supponiamo che tu ottenga i dati di input per le tue connessioni come un elenco di tuple in questo modo:

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

La struttura dati che ho trovato più utile ed efficiente per i grafici in Python è un dettato di insiemi . Questa sarà la struttura sottostante per la nostra Graphclasse. Devi anche sapere se queste connessioni sono archi (diretti, si connettono in un modo) o bordi (non orientati, si connettono in entrambi i modi). Lo gestiremo aggiungendo un directedparametro al Graph.__init__metodo. Aggiungeremo anche altri metodi utili.

import pprint
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.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):
        """ 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))

Lo lascerò come "esercizio per il lettore" per creare find_shortest_pathae altri metodi.

Vediamolo in azione però ...

>>> 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)  # undirected
>>> 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']

6
Anche se questa domanda è molto vecchia, penso che questo sia esattamente il tipo di risposta che mi aspettavo in quel momento. L'esempio aiuta davvero a spiegare come si potrebbe procedere all'implementazione, mantenendola davvero semplice. Si possono trovare implementazioni da diverse librerie open source, ma la spiegazione non sarebbe alla pari. Grazie!
shad0w_wa1k3r

2
che tipo di modifica è necessaria per aggiungere peso ai bordi?
pshirishreddy

3
@pshirishreddy Domanda interessante! Non ci avevo pensato, ma il mio istinto sarebbe stato quello di usare heapqlib per accumulare elenchi di tuple invece di set. Ad esempio il grafico sarebbe un dict di cumuli come: _graph = {'A': heapify([(0.3, 'D'), (0.5, 'B'), (0.75, 'A'), (0.9, 'C')])}(nota: in realtà non lo useresti in heapifyquesto modo, leggi la guida per la libreria), quindi potresti usare le heapqfunzioni per inserire e ottenere i bordi pesati.
mVChr

@mVChr che significherebbe un logaccesso temporale. Ma come estendere il dizionario che hai usato per mappare sia nodeID che weight?
orezvani

Bello ! La funzione viene chiamata in modo ricorsivo e sembra essere un DFS poiché continua a espandere i nodi. Per il percorso più breve possiamo confrontare la lunghezza dei percorsi e restituire solo il più breve alla fine.
Jwalant Bhatt

36

NetworkX è una fantastica libreria di grafici Python. Ti sarà difficile trovare qualcosa di cui hai bisogno che già non fa.

Ed è open source, quindi puoi vedere come hanno implementato i loro algoritmi. Puoi anche aggiungere ulteriori algoritmi.

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


7
Ecco perché NetworkX è una risorsa fantastica. È open source, quindi puoi vedere come hanno implementato i loro algoritmi. Puoi anche aggiungere ulteriori algoritmi.
jterrace

2
Circa 2000 righe di codice per il graph.py --> class Graph. E tutto quello che voglio vedere è come usano __iter__.
T.Woody

8

In primo luogo, la scelta delle classiche rappresentazioni di lista rispetto a matrice dipende dallo scopo (da cosa si vuole fare con la rappresentazione). I noti problemi e algoritmi sono legati alla scelta. La scelta del tipo di rappresentazione astratta determina come dovrebbe essere implementata.

In secondo luogo, la domanda è se i vertici e gli spigoli debbano essere espressi solo in termini di esistenza o se portino qualche informazione extra.

Dal punto di vista dei tipi di dati incorporati in Python, qualsiasi valore contenuto altrove viene espresso come un riferimento (nascosto) all'oggetto di destinazione. Se si tratta di una variabile (cioè di riferimento denominato), il nome e il riferimento vengono sempre memorizzati in un dizionario (interno). Se non hai bisogno di nomi, il riferimento può essere memorizzato nel tuo contenitore - qui probabilmente l' elenco Python sarà sempre usato per l' elenco come astrazione.

L'elenco Python è implementato come un array dinamico di riferimenti, la tupla Python è implementato come array statico di riferimenti con contenuto costante (il valore dei riferimenti non può essere modificato). Per questo motivo possono essere facilmente indicizzati. In questo modo l'elenco può essere utilizzato anche per l'implementazione di matrici.

Un altro modo per rappresentare le matrici sono gli array implementati dal modulo standard array- più vincolati rispetto al tipo memorizzato, valore omogeneo. Gli elementi memorizzano direttamente il valore. (L'elenco memorizza invece i riferimenti agli oggetti valore). In questo modo è più efficiente la memoria e anche l'accesso al valore è più veloce.

A volte, potresti trovare utile una rappresentazione ancora più ristretta come bytearray.


7

Ci sono due eccellenti librerie grafiche NetworkX e igraph . Puoi trovare entrambi i codici sorgente della libreria su GitHub. Puoi sempre vedere come sono scritte le funzioni. Ma preferisco NetworkX perché è facile da capire.
Guarda i loro codici per sapere come fanno le funzioni. Otterrai più idee e poi potrai scegliere come creare un grafico utilizzando strutture dati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.