96 votes

Trois façons de stocker un graphique en mémoire, avantages et inconvénients

Il existe trois façons de stocker un graphique en mémoire :

  1. Nœuds en tant qu'objets et arêtes en tant que pointeurs
  2. Une matrice contenant tous les poids des bords entre le nœud numéroté x et le nœud y.
  3. Une liste d'arêtes entre des nœuds numérotés

Je sais comment écrire les trois, mais je ne suis pas sûr d'avoir pensé à tous les avantages et inconvénients de chacun.

Quels sont les avantages et les inconvénients de chacune de ces façons de stocker un graphique en mémoire ?

3 votes

Je ne considérerais la matrice que si le graphe était très connecté ou très petit. Pour les graphes faiblement connectés, les approches objet/pointeur ou liste d'arêtes donneraient toutes deux une bien meilleure utilisation de la mémoire. Je suis curieux de savoir ce que j'ai oublié de stocker ;)

2 votes

Ils diffèrent également en termes de complexité temporelle, la matrice est O(1), et les autres représentations peuvent varier considérablement en fonction de ce que vous recherchez.

1 votes

Je me souviens avoir lu un article il y a quelque temps décrivant les avantages matériels de l'implémentation d'un graphe sous forme de matrice par rapport à une liste de pointeurs. Je ne me souviens pas de grand-chose à ce sujet, si ce n'est que, comme vous avez affaire à un bloc de mémoire contigu, à tout moment, une grande partie de votre ensemble de travail peut très bien se trouver dans le cache L2. Par contre, une liste de nœuds/pointeurs peut être dispersée dans la mémoire et nécessitera probablement une récupération qui ne touchera pas le cache. Je ne suis pas sûr d'être d'accord mais c'est une idée intéressante.

53voto

f64 rainbow Points 176

L'une des façons de les analyser est en termes de complexité de la mémoire et du temps (qui dépend de la façon dont vous voulez accéder au graphique).

Stockage des nœuds en tant qu'objets avec des pointeurs les uns vers les autres

  • La complexité de la mémoire pour cette approche est O(n) car il y a autant d'objets que de nœuds. Le nombre de pointeurs (vers les nœuds) requis est de O(n^2) car chaque objet nœud peut contenir des pointeurs pour un maximum de nœuds.
  • La complexité temporelle de cette structure de données est O(n) pour l'accès à un nœud donné.

Stockage d'une matrice de poids d'arêtes

  • Cela représenterait une complexité mémoire de O(n^2) pour la matrice.
  • L'avantage de cette structure de données est que la complexité temporelle pour accéder à un nœud donné est O(1).

En fonction de l'algorithme que vous exécutez sur le graphe et du nombre de nœuds, vous devrez choisir une représentation appropriée.

3 votes

Je pense que la complexité temporelle des recherches dans le modèle objet/pointeur est seulement O(n) si vous stockez également les nœuds dans un tableau séparé. Sinon, il faudrait parcourir le graphe à la recherche du nœud souhaité, non ? Traverser chaque nœud (mais pas nécessairement chaque arête) dans un graphe arbitraire ne peut pas être fait en O(n), n'est-ce pas ?

0 votes

@BarryFruitman Je suis presque sûr que vous avez raison. BFS est O(V+E). De plus, si vous recherchez un nœud qui n'est pas connecté aux autres nœuds, vous ne le trouverez jamais.

11voto

Barry Fruitman Points 3401

Quelques éléments supplémentaires à prendre en compte :

  1. Le modèle matriciel se prête plus facilement aux graphes avec des bords pondérés, en stockant les poids dans la matrice. Le modèle objet/pointeur devrait stocker les poids des arêtes dans un tableau parallèle, ce qui nécessite une synchronisation avec le tableau de pointeurs.

  2. Le modèle objet/pointeur fonctionne mieux avec les graphes dirigés qu'avec les graphes non dirigés car les pointeurs devraient être maintenus par paires, ce qui peut devenir non synchronisé.

1 votes

Vous voulez dire que les pointeurs devraient être maintenus par paires avec des graphes non orientés, correct ? S'il est dirigé, il suffit d'ajouter un sommet à la liste d'adjacence d'un sommet particulier, mais s'il est non dirigé, il faut en ajouter un à la liste d'adjacence des deux sommets ?

0 votes

@FrostyStraw Oui, exactement.

9voto

sdenton4 Points 11

La méthode des objets et des pointeurs souffre de la difficulté de la recherche, comme certains l'ont noté, mais elle est assez naturelle pour faire des choses comme construire des arbres de recherche binaires, où il y a beaucoup de structure supplémentaire.

Personnellement, j'adore les matrices d'adjacence car elles facilitent grandement toutes sortes de problèmes, en utilisant des outils issus de la théorie algébrique des graphes. (La puissance k de la matrice d'adjacence donne le nombre de chemins de longueur k du sommet i au sommet j, par exemple. Ajoutez une matrice d'identité avant de prendre la puissance k pour obtenir le nombre de chemins de longueur <=k. Prendre une mineure de rang n-1 du Laplacien pour obtenir le nombre d'arbres spanning... Et ainsi de suite).

Mais tout le monde dit que les matrices d'adjacence sont coûteuses en mémoire ! Ils n'ont qu'à moitié raison : Vous pouvez contourner ce problème en utilisant des matrices éparses lorsque votre graphe a peu d'arêtes. Les structures de données de matrices éparses font exactement le même travail que la simple conservation d'une liste d'adjacence, mais disposent de toute la gamme des opérations matricielles standard, ce qui vous donne le meilleur des deux mondes.

7voto

ajduff574 Points 1080

Je pense que votre premier exemple est un peu ambigu : les nœuds sont des objets et les bords des pointeurs. Vous pourriez garder la trace de ces derniers en stockant uniquement un pointeur vers un nœud racine, auquel cas l'accès à un nœud donné peut être inefficace (disons que vous voulez le nœud 4 - si l'objet nœud n'est pas fourni, vous devrez peut-être le chercher). Dans ce cas, vous perdriez également des portions du graphe qui ne sont pas accessibles à partir du nœud racine. Je pense que c'est le cas que f64 rainbow suppose quand il dit que la complexité temporelle pour accéder à un noeud donné est O(n).

Sinon, vous pourriez également conserver un tableau (ou hashmap) rempli de pointeurs vers chaque nœud. Cela permet un accès O(1) à un nœud donné, mais augmente un peu l'utilisation de la mémoire. Si n est le nombre de nœuds et e le nombre d'arêtes, la complexité spatiale de cette approche serait de O(n + e).

La complexité spatiale de l'approche matricielle serait de l'ordre de O(n^2) (en supposant que les bords sont unidirectionnels). Si votre graphe est clairsemé, vous aurez beaucoup de cellules vides dans votre matrice. Mais si votre graphe est entièrement connecté (e = n^2), cela se compare favorablement avec la première approche. Comme le dit RG, vous pouvez également avoir moins de ratés de cache avec cette approche si vous allouez la matrice comme un seul morceau de mémoire, ce qui pourrait rendre le suivi de beaucoup d'arêtes autour du graphe plus rapide.

La troisième approche est probablement la plus efficace en termes d'espace pour la plupart des cas - O(e) - mais elle rendrait la recherche de tous les bords d'un nœud donné une tâche O(e). Je ne peux pas penser à un cas où cela serait très utile.

1 votes

La liste des bords est naturelle pour L'algorithme de Kruskal ("pour chaque arête, faire une recherche dans union-find"). De même, Skiena (2e édition, page 157) parle des listes d'arêtes comme de la structure de données de base pour les graphes dans sa bibliothèque Combinatorica (qui est une bibliothèque de l'université d'Oxford). polyvalent bibliothèque de nombreux algorithmes). Il mentionne toutefois que l'une des raisons de cette situation sont les contraintes imposées par le modèle de calcul de Mathematica, qui est l'environnement dans lequel vit Combinatorica.

3voto

Dean J Points 10987

D'accord, donc si les arêtes n'ont pas de poids, la matrice peut être un tableau binaire, et l'utilisation d'opérateurs binaires peut faire avancer les choses très, très vite dans ce cas.

Si le graphe est clairsemé, la méthode objet/pointeur semble beaucoup plus efficace. Maintenir les objets/pointeurs dans une structure de données afin de les rassembler en un seul morceau de mémoire pourrait également être un bon plan, ou toute autre méthode pour les faire rester ensemble.

La liste d'adjacence - simplement une liste de nœuds connectés - semble de loin la plus efficace en termes de mémoire, mais probablement aussi la plus lente.

Inverser un graphe dirigé, c'est facile avec la représentation matricielle, et facile avec la liste d'adjacence, mais pas si bien avec la représentation objet/pointeur.

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