Bonjour Ă tous.
Dans cette note, j'ai décidé de lister les principales structures de données utilisées pour stocker des graphes en informatique, ainsi que de parler de quelques autres structures qui ont comme par magie « cristallisé » à mes yeux.
Alors, commençons. Mais pas par le dĂ©but â je pense que nous savons tous ce qu'est un graphe et quels types il existe (orientĂ©s, non orientĂ©s, pondĂ©rĂ©s, non pondĂ©rĂ©s, avec des arĂȘtes multiples et des boucles ou sans).
Alors, allons-y. Quelles sont donc les options de structures de données pour le « stockage de graphes » que nous avons.
1. Structures de données matricielles
1.1 Matrice d'adjacence. La matrice d'adjacence est une matrice oĂč les en-tĂȘtes des lignes et des colonnes correspondent aux numĂ©ros des sommets du graphe, et la valeur de chaque Ă©lĂ©ment a(i,j) est dĂ©terminĂ©e par la prĂ©sence ou l'absence d'arĂȘtes entre les sommets i et j (il est Ă©vident que pour un graphe non orientĂ©, cette matrice sera symĂ©trique, ou nous pouvons convenir que toutes les valeurs sont stockĂ©es uniquement au-dessus de la diagonale principale). Pour les graphes non pondĂ©rĂ©s, a(i,j) peut ĂȘtre dĂ©fini par le nombre d'arĂȘtes de i Ă j (si aucune arĂȘte n'existe, alors a(i,j)= 0), et pour les graphes pondĂ©rĂ©s, Ă©galement par le poids (le poids total) des arĂȘtes mentionnĂ©es.
1.2 Matrice d'incidence. Dans ce cas, notre graphe est Ă©galement stockĂ© dans un tableau, oĂč gĂ©nĂ©ralement les numĂ©ros des lignes correspondent aux numĂ©ros de ses sommets, et les numĂ©ros des colonnes aux arĂȘtes numĂ©rotĂ©es au prĂ©alable. Si un sommet et une arĂȘte sont incident aux autres, une valeur non nulle est enregistrĂ©e dans la cellule correspondante (pour les graphes non orientĂ©s, 1 est enregistrĂ© en cas d'incidence d'un sommet et d'une arĂȘte ; pour les graphes orientĂ©s, c'est « 1 » si l'arĂȘte « sort » du sommet et « -1 » si elle « entre » dans celui-ci (c'est assez facile Ă retenir, car le signe « moins » entre Ă©galement dans le nombre « -1 »)). Pour les graphes pondĂ©rĂ©s, encore une fois, au lieu de 1 et -1, on peut indiquer le poids total de l'arĂȘte.
2. Structures de données énumératives
2.1 Liste d'adjacence. Ici, tout semble assez simple. Ă chaque sommet d'un graphe, en gĂ©nĂ©ral, on peut associer n'importe quelle structure Ă©numĂ©rative (liste, vecteur, tableau, âŠ) dans laquelle seront stockĂ©s les numĂ©ros de tous les sommets adjacents. Pour les graphes orientĂ©s, nous n'inclurons dans cette liste que les sommets vers lesquels il existe une arĂȘte « dirigĂ©e » Ă partir du sommet de rĂ©fĂ©rence. Pour les graphes pondĂ©rĂ©s, la mise en Ćuvre sera plus complexe.
2.2 Liste des arĂȘtes. Une structure de donnĂ©es assez populaire. La liste des arĂȘtes, comme le suggĂšre le Capitaine Ăvidence, reprĂ©sente en effet la liste proprement dite des arĂȘtes du graphe, chacune Ă©tant dĂ©finie par un sommet de dĂ©part, un sommet d'arrivĂ©e (pour les graphes non orientĂ©s, l'ordre n'a pas d'importance, bien qu'il soit possible d'utiliser diffĂ©rentes rĂšgles, par exemple, indiquer les sommets dans l'ordre croissant) et un poids (uniquement pour les graphes pondĂ©rĂ©s).
Pour les listes-matrices mentionnées ci-dessus, vous pouvez consulter plus en détail (avec des illustrations) par exemple .
2.3 Tableau d'adjacence. Ce n'est pas une structure trĂšs courante. Essentiellement, il reprĂ©sente une forme « d'emballage » des listes d'adjacence dans une seule structure Ă©numĂ©rative (tableau, vecteur). Les premiers n (selon le nombre de sommets du graphe) Ă©lĂ©ments de ce tableau contiennent les index de dĂ©part du mĂȘme tableau, Ă partir desquels sont consĂ©cutivement enregistrĂ©s tous les sommets adjacents.
Voici l'explication la plus claire (pour moi) que j'ai trouvée :
3. Vecteur d'adjacence et tableau associatif d'adjacence
Il se trouve que l'auteur de ces lignes, n'Ă©tant pas un programmeur professionnel, mais ayant parfois eu affaire Ă des graphes, a le plus souvent manipulĂ© des listes d'arĂȘtes. En effet, c'est pratique lorsque le graphe compte des boucles multiples et des arĂȘtes. Et donc, pour faire Ă©voluer les listes d'arĂȘtes classiques, je vous propose de prĂȘter attention Ă leur « dĂ©veloppement/ramification/variation » en particulier : le vecteur d'adjacence et le tableau associatif d'adjacence.
3.1 Vecteur d'adjacence
Cas (a1) : graphe non pondéré
Nous allons appeler le vecteur d'adjacence pour un graphe non pondĂ©rĂ© un ensemble ordonnĂ© d'un nombre pair d'entiers (a[2i], a[2i+1],âŠ, oĂč i commence Ă 0), oĂč chaque paire de nombres a[2i], a[2i+1] dĂ©finit une arĂȘte du graphe entre les sommets a[2i] et a[2i+1].
Ce format d'enregistrement ne contient pas d'informations sur la direction du graphe (les deux options sont possibles). Lors de l'utilisation du format pour un graphe orientĂ©, il est supposĂ© que l'arĂȘte est dirigĂ©e de a[2i] vers a[2i+1]. Ici et plus loin : pour les graphes non orientĂ©s, si nĂ©cessaire, des exigences peuvent ĂȘtre appliquĂ©es concernant l'ordre d'enregistrement des sommets (par exemple, pour que le sommet avec la valeur assignĂ©e la plus basse soit en premier).
En C++, il est judicieux de représenter le vecteur d'adjacence à l'aide de std::vector, c'est pourquoi ce nom a été choisi pour cette structure de données.
Cas (a2) : graphe non pondĂ©rĂ©, poids des arĂȘtes entiĂšres
Par analogie avec le cas (a1), nous appellerons vecteur d'adjacence pour un graphe pondĂ©rĂ© avec des poids d'arĂȘtes entiers un ensemble ordonnĂ© (tableau dynamique) de nombres (a[3i], a[3i+1], a[3i+2],âŠ, oĂč i commence Ă 0), oĂč chaque « triplet » de nombres a[3i], a[3i+1], a[3i+2] dĂ©finit une arĂȘte du graphe entre les sommets numĂ©rotĂ©s a[3i] et a[3i+1] respectivement, et la valeur a[3i+2] est le poids de cette arĂȘte. Ce graphe peut ĂȘtre orientĂ© ou non.
Cas (b) : graphe non pondĂ©rĂ©, poids des arĂȘtes non entiers
Ătant donnĂ© qu'il n'est pas possible de stocker des Ă©lĂ©ments hĂ©tĂ©rogĂšnes dans un mĂȘme tableau (vecteur), une implĂ©mentation possible serait la suivante. Le graphe est stockĂ© dans une paire de vecteurs, oĂč le premier vecteur est le vecteur d'adjacence du graphe sans indication des poids, et le second vecteur contient les poids correspondants (implĂ©mentation possible pour C++ : std::pair). Ainsi, pour une arĂȘte dĂ©finie par une paire de sommets aux indices 2i, 2i+1 du premier vecteur, le poids sera Ă©gal Ă l'Ă©lĂ©ment Ă l'indice i du second vecteur.
Mais pourquoi est-ce nécessaire ?
Eh bien, pour l'auteur de ces lignes, pour résoudre un certain nombre de problÚmes, cela semble assez utile. Et d'un point de vue formel, cela présente les avantages suivants :
- Le vecteur d'adjacence, comme toute autre structure « Ă©numĂ©rative », est assez compact, occupe moins de mĂ©moire qu'une matrice d'adjacence (pour des graphes Ă©pars), et est relativement simple Ă mettre en Ćuvre.
- Les sommets d'un graphe peuvent en principe ĂȘtre marquĂ©s par des nombres nĂ©gatifs. Au cas oĂč ce genre de « dĂ©viation » serait nĂ©cessaire.
- Les graphes peuvent contenir des arĂȘtes multiples et des boucles multiples, chacune ayant des poids diffĂ©rents (positifs, nĂ©gatifs, voire nuls). Il n'y a pas de restrictions Ă cet Ă©gard.
- De plus, il est possible d'attribuer diffĂ©rentes propriĂ©tĂ©s aux arĂȘtes â mais Ă ce sujet, voir point 4.
Cependant, il faut reconnaĂźtre que cet "array de liste" ne prĂ©voit pas d'accĂšs rapide Ă l'arĂȘte. Et c'est ici qu'intervient le tableau associatif d'adjacence, comme expliquĂ© ci-dessous.
3.2 Tableau associatif d'adjacence
Donc, si l'accĂšs Ă une arĂȘte spĂ©cifique, Ă son poids et Ă d'autres propriĂ©tĂ©s est critique pour nous, et que les exigences en matiĂšre de mĂ©moire n'autorisent pas l'utilisation d'une matrice d'adjacence, rĂ©flĂ©chissons Ă comment nous pourrions modifier le vecteur d'adjacence pour rĂ©soudre ce problĂšme. Ainsi, la clĂ© est l'arĂȘte du graphe, qui peut ĂȘtre reprĂ©sentĂ©e sous la forme d'une paire ordonnĂ©e d'entiers. Ă quoi cela ressemble-t-il? Ne serait-ce pas une clĂ© dans le tableau associatif? Alors, pourquoi ne pas le rĂ©aliser? Imaginons que nous ayons un tableau associatif oĂč chaque clĂ© â une paire ordonnĂ©e d'entiers â est associĂ©e Ă une valeur â un entier ou un nombre rĂ©el dĂ©finissant le poids de l'arĂȘte. En C++, il serait judicieux de rĂ©aliser cette structure sur la base du conteneur std::map (std::map <std::pair , int> ou std::map <std::pair , double>), ou de std::multimap en cas de plusieurs arĂȘtes. VoilĂ , nous avons obtenu une structure pour stocker des graphes, qui occupe moins de mĂ©moire que les structures "matricielles", qui peut reprĂ©senter des graphes avec des boucles et des arĂȘtes multiples et qui n'a mĂȘme pas de exigences strictes sur la non-nĂ©gativitĂ© des numĂ©ros de sommet (je ne sais pas Ă qui cela pourrait ĂȘtre utile, mais bon).
4. Structures de données, qu'elles soient "saturées", manquent de quelque chose
Et en effet: lors de la rĂ©solution de certains problĂšmes, il peut ĂȘtre nĂ©cessaire d'attribuer certaines caractĂ©ristiques aux arĂȘtes du graphe et, par consĂ©quent, de les stocker. Si ces caractĂ©ristiques peuvent ĂȘtre clairement rĂ©duites Ă des entiers, il est possible de stocker ces "graphes avec caractĂ©ristiques supplĂ©mentaires" en utilisant des versions Ă©tendues du vecteur d'adjacence et du tableau associatif d'adjacence.
ConsidĂ©rons donc un graphe non pondĂ©rĂ©, pour chaque arĂȘte duquel il est nĂ©cessaire de stocker, par exemple, 2 propriĂ©tĂ©s supplĂ©mentaires dĂ©finies par des entiers. Dans ce cas, il est possible de dĂ©finir son vecteur d'adjacence comme un ensemble ordonnĂ© de « quartets » d'entiers (a[2i], a[2i+1], a[2i+2], a[2i+3]âŠ), oĂč a[2i+2] et a[2i+3] dĂ©termineront les propriĂ©tĂ©s de l'arĂȘte correspondante. Pour un graphe avec des poids d'arĂȘtes entiers, l'ordre, en gĂ©nĂ©ral, est similaire (la seule diffĂ©rence Ă©tant que les propriĂ©tĂ©s suivront le poids de l'arĂȘte et seront dĂ©finies par les Ă©lĂ©ments a[2i+3] et a[2i+4], et l'arĂȘte elle-mĂȘme sera dĂ©finie par 5 nombres ordonnĂ©s au lieu de 4). Et pour un graphe avec des poids d'arĂȘtes non entiers, les propriĂ©tĂ©s pourront ĂȘtre notĂ©es dans son composant non pondĂ©rĂ©.
Lors de l'utilisation d'un tableau associatif d'adjacence pour des graphes avec des poids d'arĂȘtes entiers, il est possible de dĂ©finir, comme valeur, non pas un nombre individuel, mais un tableau (vecteur) de nombres, qui dĂ©finit, en plus du poids de l'arĂȘte, toutes les autres propriĂ©tĂ©s nĂ©cessaires. Cependant, un inconvĂ©nient pour le cas des poids non entiers sera la nĂ©cessitĂ© de dĂ©finir une propriĂ©tĂ© par un nombre Ă virgule flottante (oui, c'est un inconvĂ©nient, mais si ces propriĂ©tĂ©s ne sont pas trop nombreuses et si elles ne sont pas dĂ©finies par des doubles trop « complexes », cela peut aller). Ainsi, en C++, les tableaux associatifs d'adjacence avancĂ©s peuvent ĂȘtre dĂ©finis de la maniĂšre suivante : std::map <std::pair , std::vector> ou std::map <std::pair , std::vector>, oĂč la premiĂšre valeur dans le « vecteur-valeur-par-clĂ© » sera le poids de l'arĂȘte, et ensuite se trouveront les dĂ©signations numĂ©riques de ses propriĂ©tĂ©s.
Littérature :
Sur les graphes et les algorithmes en général :
1. Cormen, Thomas H., Leiserson, Charles E., Rivest, Ronald L., Stein, Clifford. Algorithmes : construction et analyse, 2Ăšme Ă©dition : Trad. de l'anglais â Moscou : Ăditions Williams, 2011.
2. Harari, Frank. Théorie des graphes. Moscou : Mir, 1973.
Rapport de l'auteur sur ce vecteur et le tableau associatif d'adjacence :
3. Tchernoukhov S.A. Vecteur d'adjacence et tableau d'adjacence comme façons de reprĂ©senter et de stocker des graphes / S.A. Chernouhov. Vecteur d'adjacence et carte d'adjacence en tant que structures de donnĂ©es pour reprĂ©senter un graphe // Recueil d'articles de la confĂ©rence scientifique et pratique internationale « ProblĂšmes de mise en Ćuvre des rĂ©sultats des dĂ©veloppements innovants et voies de leur rĂ©solution » (Saratov, 14.09.2019). â Sterlitamak : AMI, 2019, p. 65-69
Sources Internet utiles sur le sujet :
4.
5.
Source : habr.com
