Structures de données relationnelles : les graphes 1. Présentation
Source : http://spikedmath.com/382.html
Les arbres font partie de la grande famille des graphes. Plus précisément, un arbre est un « graphe acyclique orienté possédant une seule racine tel que tous les nœuds sauf la racine ont un unique prédécesseur (parent) ».
Quelques exemples de graphes
F A
B E
C D
A E
B C D
Exemple 1 - Graphe G1 non orienté simple Exemple 2 - Graphe G2 non orienté, deux boucles, un sommet isolé
10
5 14
2 8
11 18
A
D 300 280
B 150 C 310
80 E
110 F
60
70 90 G 260 H
50 190
100 I 40
Exemple 3 - Graphe G3 orienté Exemple 4 - Graphe G4 non orienté pondéré
Vocabulaire
Les nœuds d’un graphe sont aussi appelés sommets. Un nœud contient une donnée (ou étiquette).
Une arête relie deux sommets, qui sont alors voisins ou adjacents.
Les arêtes peuvent être orientées. Le graphe est alors orienté et les arêtes sont appelées arcs. On dit que l’arc (x, y) part du sommet x et arrive au sommet y.
Dans un graphe pondéré, chaque arête porte une valuation, aussi appelée poids ou coût.
Une boucle est une arête reliant un sommet à lui-même.
Des arêtes multiples sont deux arêtes, ou plus, qui joignent les mêmes sommets.
Dans un graphe non orienté et non pondéré, le degré d’un sommet est le nombre d’arêtes incidentes à ce sommet, c’est-à-dire ayant ce sommet pour extrémités, les boucles étant comptées deux fois. Dans un graphe pondéré, on ajoute les poids des arêtes, et dans un graphe orienté, on distingue le degré entrant du degré sortant.
Un sommet de degré zéro est dit isolé.
Dans un graphe orienté, l’ensemble des successeurs d’un sommet x est l’ensemble des sommets y tels qu’il existe un arc allant de x vers y. L’ensemble des prédécesseurs d’un sommet x est
l’ensemble des sommets y tels qu’il existe un arc allant de y vers x.
Un graphe simple est un graphe ne contenant ni boucle, ni arêtes multiples.
Un chemin est une succession de sommets reliés par des arêtes, ou des arcs.
Un cycle est un chemin dont les deux extrémités sont identiques et passant par des arêtes toutes distinctes. Ainsi, si A et B sont reliés par une seule arête (non orientée), A-B-A n’est pas un cycle.
Application
Les graphes sont utilisés :
• pour représenter une relation entre un ensemble d’objets homogènes : réseaux routiers, réseaux électriques, Internet, réseaux sociaux…
• dans des algorithmes : recherche du plus court chemin (protocole de routage ou GPS routier), ordonnancement de tâches …
2. Définitions
Rappel - Notations mathématiques
Étant donné deux éléments distincts x et y,
• la paire {x, y} est l’ensemble contenant x et y, sans ordre, utilisé par exemple pour des solutions d’équations : {2 ; 3} = {3 ; 2} ;
• le couple (x, y) est la liste ordonnée (x, y) utilisée par exemple pour des coordonnées : (2 ; 3) ≠ (3 ; 2).
Définitions
Un graphe est un couple (S, A) dans lequel :
• S est un ensemble {s1, s2, …, sn} de sommets
• A est un ensemble {a1, a2, …, am} d’arêtes reliant ces sommets
Dans un graphe non orienté, les arêtes sont des paires de sommets : chaque arête ai s’écrit sous la forme {x, y} avec x, y ∈ S.
Dans un graphe orienté, les arêtes, généralement appelées arcs, sont des couples de sommets : chaque arc ai s’écrit (x, y) avec x, y ∈ S.
Dans un graphe non orienté pondéré, une arête va être sous la forme ({x, y}, p) avec x, y des sommets et p un nombre.
Remarque : On utilise aussi (V, E) au lieu de (S, A) pour Vertex et Edge.
Exemple
Graphe de l’exemple 2 : V2 = {A, B, C, D, E} et E2 = {{A, A}, {A, B}, {A, C}, {B, C}, {B, D}, {C, D}, {D, D}}
Exercice
1. Écrire les ensembles V et E correspondant aux graphes des exemples 1 et 3.
2. Représenter le graphe décrit par les ensembles V et E : V = {A, B, C, D, E, F}
E = {(A, B), (A, C), (B, C), (B, E), (C, A), (C, D), (D, A), (D, E), (E, F), (F, D)}
3. Représentation en machine
On trouve principalement deux représentations des graphes en machine. La matrice d’adjacence utilise les tableaux à deux dimensions ; la liste des successeurs utilise les dictionnaires.
Exemples
Graphe G1 - dictionnaire G1 = {A : [B, C],
B : [A, C, E, F], C : [A, B, D], D : [C, E], E : [B, D, F], F : [B, E]}
Graphe G1 - matrice d’adjacence On numérote les sommets dans l’ordre alphabétique.
⎝
⎜⎜
⎛
0 1 1 0 0 0 1 0 1 0 1 1 1 1 0 1 0 0 0 0 1 0 1 0 0 1 0 1 0 1 0 1 0 0 1 0⎠
⎟⎟
⎞
Le 1 ligne 4 colonne 3 signifie qu’une arête relie le sommet 4 (D) au sommet 3 (C).
Liste des successeurs Matrice d’adjacence
Un dictionnaire de listes permet de représenter le graphe.
Les clés du dictionnaire sont les sommets ; la valeur associée à un sommet est la liste des successeurs de ce sommet. On peut aussi utiliser la liste de prédécesseurs.
Ces deux listes sont identiques dans un graphe non orienté : liste des voisins.
Dans le cas d’un graphe pondéré, les listes sont remplacées par des dictionnaires. Le graphe est alors un dictionnaire de
dictionnaires : le dictionnaire de successeurs a pour clés les étiquettes des sommets successeurs et pour valeurs les valuations des arêtes associées.
Les sommets sont numérotés de 0 à n – 1.
Les arêtes sont représentées par une matrice M, un tableau 2D.
Un coefficient non nul ligne i colonne j correspond à un arc ou une arête allant du sommet i vers le sommet j.
Ce coefficient vaut 0 ou 1, ou est égal à la valuation de l’arête dans le cas d’un graphe pondéré.
Avec un graphe non orienté, ce tableau est symétrique : M[i, j] = M[j, i].
Les étiquettes des sommets peuvent être données dans une liste. Le graphe est alors le couple (lst_sommets, matrice_adjacence).
Graphe G3 - dictionnaire Avec liste des successeurs
G3 = {2 : [11, 18], 5 : [8], 8 : [2, 14], 10 : [5, 14], 11 : [], 14 : [8, 18], 18 : [11, 14]}
Avec liste des prédécesseurs G3 = {2 : [8], 5 : [10], 8 : [5, 14],
10 : [], 11 : [2, 18], 14 : [8, 10, 18], 18 : [2, 14]}
Graphe G3 - matrice d’adjacence
Les étiquettes des sommets sont dans une liste : [2, 5, 8, 10, 11, 14, 18].
⎝
⎜⎜
⎜⎛
0 0 0 0 1 0 1 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 1 1 0⎠
⎟⎟
⎟⎞
Graphe G4 - dictionnaire
G4 = {A : {B : 300, C : 310, D : 280}, B : {A : 300, C : 80},
C : {A : 310, B : 80, E : 150}, D : {A : 280, {F : 110},
E : {C : 150, F : 60, G : 90, H : 190}, F : {D : 110, E : 60, G : 70, H : 260}, G : {E : 90, F : 70, H : 50, I : 100}, H : {E : 190, F : 260, G : 50, I : 40}, I : {G : 100, H : 40}}
Graphe G4 - matrice d’adjacence Sommets : [A, B, C, D, E, F, G, H, I]
⎝
⎜⎜
⎜⎜
⎜⎛
0 300 310 280 0 0 0 0 0
300 0 80 0 0 0 0 0 0
310 80 0 150 0 0 0 0 0
280 0 0 0 0 110 0 0 0
0 0 150 0 0 60 0 190 0
0 0 0 110 60 0 70 260 0
0 0 0 0 90 70 0 50 100
0 0 0 0 190 260 50 0 40
0 0 0 0 0 0 100 40 0 ⎠
⎟⎟
⎟⎟
⎟⎞
Complexité
Dans un graphe G à n sommets et m arêtes, la complexité en mémoire est en O(n2) avec une matrice d’adjacence et en O(m + n) avec une liste de successeurs.
La complexité en temps dépend aussi de la représentation. Tester si un sommet est isolé est immédiat avec une liste de successeurs et en O(n) avec une matrice d’adjacence. Tester si deux sommets sont adjacents est immédiat avec la matrice d’adjacence, mais nécessite un parcours d’une liste de successeurs avec la représentation par dictionnaire de listes.
4. Interface et implémentation
On peut travailler sur les graphes directement avec les fonctions et opérateurs des tableaux ou des dictionnaires, ou écrire des fonctions spécifiques :
• creer_graphe(s : liste de Sommets) -> Graphe: renvoie le graphe (S, Δ) où Δ est une liste vide d’arêtes
• ajouter_arete(g : Graphe, s1 : Sommet, s2 : Somme) -> graphe: à partir du graphe g = (S, A) et de deux sommets s1 et s2 appartenant à S renvoie le graphe (S, A ∪ {(s1, s2)})
• sommets(g : Graphe) -> liste de Sommets : à partir du graphe (S, A) renvoie S
• voisins(g : Graphe, s : Sommets) -> liste de Sommets: renvoie la liste des sommets voisins de s dans le graphe g. Cette fonction est remplacée par une fonction successeurs et/ou predecesseurs dans le cas d’un graphe orienté.
On peut rajouter : ajouter_sommet, supprimer_sommet, sont_adjacents, supprimer_arete, retourner_valeur_sommet, fixer_valeur_sommet et, dans le cas d’un graphe pondéré, retourner_valeur_arete et fixer_valeur_arete.
Implémentation objet
L’utilisation d’une classe graphe permet d’encapsuler dans un même objet la liste des sommets avec leurs valeurs, la matrice d’adjacence, et d’intégrer les méthodes les plus utiles (voisins…).
Remarques
R1 - Dans l’implémentation de gauche, les sommets sont désignés par leurs étiquettes (ou valeurs). À droite, ils sont désignés par leurs indices dans la liste sommets. On peut désigner les sommets par leurs étiquettes en modifiant les méthodes ajouter_arete et voisins :
def ajouter_arete(self, s1, s2):
i = self.sommets.index(s1) j = self.sommets.index(s2) self.aretes[i][j] = 1
self.aretes[j][i] = 1
def voisins(self, s):
i = self.sommets.index(s)
return [sommets[j] for j in range(self.n) if self.aretes[i][j] == 1]
R2 - On peut créer une classe sommet (éventuellement arête) pour attribuer des propriétés supplémentaires à ces objets : couleur d’un sommet, statut « visité » lors d’un parcours…
R3 - Choix du nom sommets pour le dictionnaire des sommets discutable : self._sommets, self.graphe ou self.dico_graph plutôt ? puis sommets au lieu de get_sommets ?
Liste des successeurs – Version objet Matrice d’adjacence – Version objet class Graphe:
"Graphe orienté non pondéré"
def __init__(self, sommets):
self.sommets = {s: [] for s in sommets}
self.n = len(sommets) def ajouter_arc(self, s1, s2):
self.sommets[s1].append(s2) def get_sommets(self):
return list(self.sommets.keys()) def successeurs(self, s):
return self.sommets[s]
class Graphe:
"Graphe non orienté non pondéré"
def __init__(self, sommets):
self.sommets = [s for s in sommets]
self.n = n = len(sommets)
self.aretes = [[0]*n for i in range(n)]
def ajouter_arete(self, s1, s2):
self.aretes[s1][s2] = 1 self.aretes[s2][s1] = 1 def get_sommets(self):
return self.sommets def voisins(self, s):
return [v for v in range(self.n) if self.aretes[s][v] == 1]