Tre modi per memorizzare un grafico in memoria, vantaggi e svantaggi


90

Esistono tre modi per archiviare un grafico in memoria:

  1. Nodi come oggetti e bordi come puntatori
  2. Una matrice contenente tutti i pesi degli spigoli tra il nodo numerato x e il nodo y
  3. Un elenco di bordi tra i nodi numerati

So come scrivere tutti e tre, ma non sono sicuro di aver pensato a tutti i vantaggi e gli svantaggi di ciascuno.

Quali sono i vantaggi e gli svantaggi di ciascuno di questi modi di memorizzare un grafico in memoria?


3
Considererei la matrice solo se il grafico fosse molto connesso o molto piccolo. Per i grafici scarsamente connessi, l'approccio oggetto / puntatore o elenco di bordi fornirebbe entrambi un uso della memoria molto migliore. Sono curioso di sapere cosa ho trascurato oltre all'archiviazione. ;)
sarnold

2
Differiscono anche nella complessità temporale, la matrice è O (1) e le altre rappresentazioni possono variare ampiamente a seconda di ciò che stai cercando.
msw

1
Ricordo di aver letto un articolo qualche tempo fa che descriveva i vantaggi hardware dell'implementazione di un grafico come matrice su un elenco di puntatori. Non ricordo molto a riguardo tranne che, poiché hai a che fare con un blocco di memoria contiguo, in qualsiasi momento gran parte del tuo working set potrebbe benissimo essere nella cache L2. Un elenco di nodi / puntatori d'altra parte potrebbe essere sparato attraverso la memoria e potrebbe probabilmente richiedere un recupero che non raggiunge la cache. Non sono sicuro di essere d'accordo ma è un pensiero interessante.
nerraga

1
@ Dean J: solo una domanda sui "nodi come oggetti e bordi come rappresentazione di puntatori". Quale struttura dati usi per memorizzare i puntatori nell'oggetto? È un elenco?
Timofey

4
I nomi comuni sono: (1) equivalente a lista di adiacenza , (2) matrice di adiacenza , (3) lista di bordi .
Evgeni Sergeev

Risposte:


51

Un modo per analizzarli è in termini di memoria e complessità temporale (che dipende da come si desidera accedere al grafico).

Memorizzazione dei nodi come oggetti con puntatori l'uno all'altro

  • La complessità della memoria per questo approccio è O (n) perché hai tanti oggetti quanti nodi hai. Il numero di puntatori (ai nodi) richiesto è fino a O (n ^ 2) poiché ogni oggetto nodo può contenere puntatori per un massimo di n nodi.
  • La complessità temporale per questa struttura dati è O (n) per accedere a un dato nodo.

Memorizzazione di una matrice di pesi dei bordi

  • Questa sarebbe una complessità della memoria di O (n ^ 2) per la matrice.
  • Il vantaggio di questa struttura dati è che la complessità temporale per accedere a un dato nodo è O (1).

A seconda di quale algoritmo esegui sul grafico e di quanti nodi ci sono, dovrai scegliere una rappresentazione adeguata.


3
Credo che la complessità temporale per le ricerche nel modello oggetto / puntatore sia solo O (n) se si memorizzano anche i nodi in un array separato. Altrimenti avresti bisogno di attraversare il grafico alla ricerca del nodo desiderato, no? Attraversare ogni nodo (ma non necessariamente ogni lato) in un grafo arbitrario non può essere fatto in O (n), vero?
Barry Fruitman

@BarryFruitman Sono abbastanza sicuro che tu abbia ragione. BFS è O (V + E). Inoltre, se stai cercando un nodo che non è connesso agli altri nodi, non lo troverai mai.
WilderField

10

Un altro paio di cose da considerare:

  1. Il modello a matrice si presta più facilmente a grafici con bordi ponderati, memorizzando i pesi nella matrice. Il modello oggetto / puntatore dovrebbe memorizzare i pesi dei bordi in una matrice parallela, che richiede la sincronizzazione con la matrice del puntatore.

  2. Il modello oggetto / puntatore funziona meglio con i grafici diretti che con i grafici non orientati perché i puntatori dovrebbero essere mantenuti a coppie, cosa che può diventare non sincronizzata.


1
Vuoi dire che i puntatori dovrebbero essere mantenuti in coppia con grafici non orientati, giusto? Se è diretto, aggiungi semplicemente un vertice all'elenco di adiacenza di un particolare vertice, ma se è non orientato, devi aggiungerne uno all'elenco di adiacenza di entrambi i vertici?
FrostyStraw

@FrostyStraw Sì, esattamente.
Barry Fruitman

8

Il metodo oggetti e puntatori soffre di difficoltà di ricerca, come alcuni hanno notato, ma è abbastanza naturale per fare cose come costruire alberi di ricerca binari, dove c'è molta struttura extra.

Personalmente adoro le matrici di adiacenza perché rendono tutti i tipi di problemi molto più facili, utilizzando strumenti della teoria dei grafi algebrici. (La k-esima potenza della matrice di adiacenza fornisce il numero di cammini di lunghezza k dal vertice i al vertice j, per esempio. Aggiungi una matrice identità prima di prendere la k-esima potenza per ottenere il numero di cammini di lunghezza <= k. Prendi un rango n-1 minore del laplaciano per ottenere il numero di alberi spanning ... E così via.)

Ma tutti dicono che le matrici di adiacenza costano memoria! Hanno ragione solo a metà: puoi aggirare questo problema usando matrici sparse quando il tuo grafico ha pochi bordi. Le strutture di dati a matrici sparse svolgono esattamente il lavoro di mantenere un elenco di adiacenze, ma hanno comunque a disposizione l'intera gamma di operazioni con matrici standard, offrendoti il ​​meglio di entrambi i mondi.


7

Penso che il tuo primo esempio sia un po 'ambiguo: nodi come oggetti e bordi come puntatori. Puoi tenerne traccia memorizzando solo un puntatore a qualche nodo radice, nel qual caso l'accesso a un dato nodo potrebbe essere inefficiente (supponiamo che tu voglia il nodo 4 - se l'oggetto nodo non è fornito, potresti doverlo cercare) . In questo caso, perderai anche porzioni del grafico che non sono raggiungibili dal nodo radice. Penso che questo sia il caso che f64 rainbow assume quando dice che la complessità temporale per accedere a un dato nodo è O (n).

Altrimenti, potresti anche mantenere un array (o hashmap) pieno di puntatori a ciascun nodo. Ciò consente a O (1) di accedere a un dato nodo, ma aumenta leggermente l'utilizzo della memoria. Se n è il numero di nodi ed e è il numero di archi, la complessità spaziale di questo approccio sarebbe O (n + e).

La complessità spaziale per l'approccio a matrice sarebbe lungo le linee di O (n ^ 2) (supponendo che gli archi siano unidirezionali). Se il tuo grafico è scarso, avrai molte celle vuote nella tua matrice. Ma se il tuo grafico è completamente connesso (e = n ^ 2), questo si confronta favorevolmente con il primo approccio. Come dice RG, potresti anche avere meno cache miss con questo approccio se allochi la matrice come un blocco di memoria, il che potrebbe rendere più veloce il seguire molti bordi attorno al grafico.

Il terzo approccio è probabilmente il più efficiente in termini di spazio per la maggior parte dei casi - O (e) - ma renderebbe la ricerca di tutti i bordi di un dato nodo un compito O (e). Non riesco a pensare a un caso in cui questo sarebbe molto utile.


L'elenco degli spigoli è naturale per l'algoritmo di Kruskal ("per ogni spigolo, cerca in union-find"). Inoltre, Skiena (2a ed., Pagina 157) parla di edge list come struttura di dati di base per i grafici nella sua libreria Combinatorica (che è una libreria generica di molti algoritmi). Cita che una delle ragioni di ciò sono i vincoli imposti dal modello computazionale di Mathematica, che è l'ambiente in cui vive Combinatorica.
Evgeni Sergeev


4

C'è un'altra opzione: i nodi come oggetti, anche i bordi come oggetti, ogni bordo essendo allo stesso tempo in due elenchi doppiamente collegati: l'elenco di tutti i bordi che escono dallo stesso nodo e l'elenco di tutti i bordi che vanno nello stesso nodo .

struct Node {
    ... node payload ...
    Edge *first_in;    // All incoming edges
    Edge *first_out;   // All outgoing edges
};

struct Edge {
    ... edge payload ...
    Node *from, *to;
    Edge *prev_in_from, *next_in_from; // dlist of same "from"
    Edge *prev_in_to, *next_in_to;     // dlist of same "to"
};

L'overhead della memoria è grande (2 puntatori per nodo e 6 puntatori per bordo) ma ottieni

  • O (1) inserimento del nodo
  • O (1) inserimento del bordo (puntatori dati ai nodi "da" e "a")
  • O (1) eliminazione del bordo (dato il puntatore)
  • O (deg (n)) cancellazione del nodo (dato il puntatore)
  • O (deg (n)) trovare i vicini di un nodo

La struttura può anche rappresentare un grafico piuttosto generale: multigrafo orientato con loop (cioè puoi avere più bordi distinti tra gli stessi due nodi inclusi più loop distinti - bordi che vanno da x a x).

Una spiegazione più dettagliata di questo approccio è disponibile qui .


3

Ok, quindi se i bordi non hanno pesi, la matrice può essere un array binario e l'uso di operatori binari può far andare le cose molto, molto velocemente in quel caso.

Se il grafico è scarso, il metodo oggetto / puntatore sembra molto più efficiente. Tenere l'oggetto / i puntatori in una struttura dati appositamente per convincerli a formare un singolo blocco di memoria potrebbe anche essere un buon piano o qualsiasi altro metodo per farli stare insieme.

L'elenco delle adiacenze - semplicemente un elenco di nodi connessi - sembra di gran lunga il più efficiente in termini di memoria, ma probabilmente anche il più lento.

Invertire un grafo orientato è facile con la rappresentazione a matrice e facile con l'elenco di adiacenza, ma non così eccezionale con la rappresentazione dell'oggetto / puntatore.

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.