Cette page suit toujours le même plan : le vocabulaire, la définition, quelques usages concrets, un exemple unique (repris dans toute la page), puis les deux façons courantes d'implémenter un graphe en Python — la liste d'adjacence et la matrice d'adjacence — avant la conversion entre les deux et la complexité.
src/graphes.py, dans le sous-dossier src à côté de cette page, contient tout le code présenté ici (les deux implémentations, les fonctions de conversion, et tous les tests). Après l'avoir téléchargé, on l'exécute dans un terminal, dans le dossier où il a été enregistré, avec python3 graphes.py (Python doit être installé sur l'ordinateur). Le fichier affiche « OK » pour chaque test s'il n'y a pas d'erreur.
Chaque mot ci-dessous est utilisé exactement dans ce sens dans toute la page.
| Mot | Définition précise |
|---|---|
| Graphe | Une structure formée de deux ensembles : un ensemble V de sommets, et un ensemble E de liaisons entre certains de ces sommets. |
| Sommet | Un élément du graphe (aussi appelé nœud, ou vertex en anglais). |
| Arc | Une liaison orientée entre deux sommets : elle a une extrémité initiale (le départ) et une extrémité terminale (l'arrivée), qui ne jouent pas le même rôle. |
| Arête | Une liaison non orientée entre deux sommets : contrairement à un arc, les deux sommets jouent le même rôle, il n'y a pas de sens de parcours. |
| Graphe orienté | Un graphe dont les liaisons sont des arcs : le sens compte. On dit que B est un successeur de A s'il existe un arc de A vers B. |
| Graphe non orienté | Un graphe dont les liaisons sont des arêtes : le sens ne compte pas. On dit que A et B sont voisins s'il existe une arête entre eux (et alors B est voisin de A si et seulement si A est voisin de B). |
| Poids (ou coefficient) | Une valeur numérique attachée à un arc ou une arête, par exemple une distance ou une probabilité. |
| Graphe pondéré | Un graphe dont chaque arc (ou arête) porte un poids. |
| Diagramme sagittal | La représentation dessinée d'un graphe : les sommets sont des points ou des cercles, les arcs des flèches, les arêtes des segments. |
| Liste d'adjacence (ou de successeurs) | Une implémentation d'un graphe où chaque sommet est associé à la liste des sommets qui lui sont reliés. |
| Matrice d'adjacence | Une implémentation d'un graphe sous la forme d'un tableau à double entrée : une ligne et une colonne par sommet. |
| Matrice symétrique | Une matrice où, pour tout i et tout j, le terme ligne i colonne j est égal au terme ligne j colonne i. La matrice d'adjacence d'un graphe est symétrique si et seulement si le graphe est non orienté. |
V de sommets, et un ensemble E de liaisons entre certains des sommets de V.
Chaque liaison possède une extrémité initiale et une extrémité terminale. Lorsque cette distinction compte, on parle d'arc et le graphe est dit orienté ; lorsqu'elle ne compte pas, on parle d'arête et le graphe est dit non orienté.
Un graphe permet de représenter des relations entre des objets. Par exemple, un réseau de transport pondéré, où chaque arête porte la distance (en km) entre deux villes :
Un réseau social se représente par un graphe orienté (la relation « suit » n'est pas forcément réciproque), et un réseau informatique (ordinateurs, imprimante, routeurs reliés par des câbles) se représente naturellement par un graphe non orienté, où chaque sommet est un appareil et chaque arête une connexion physique.
On reprend le même graphe à 8 sommets dans toute la suite de la page : sa version orientée (avec des arcs) pour la section sur la liste d'adjacence, et sa version non orientée (les mêmes sommets, mais reliés par des arêtes) pour la section sur la matrice d'adjacence — pour bien voir la différence entre les deux notions sur un exemple identique par ailleurs.
V = {1, 2, 3, 4, 5, 6, 7, 8}
E = {(1,2), (1,5), (1,8), (2,3), (3,1), (3,4), (4,8), (5,6), (7,6), (8,7)}
Les mêmes 8 sommets, mais avec des arêtes (sans sens de parcours) plutôt que des arcs :
E = {{1,2}, {1,3}, {1,5}, {1,8}, {2,3}, {3,4}, {4,8}, {5,6}, {6,7}, {7,8}}
On utilise un dictionnaire Python : chaque sommet est une clé, et la valeur associée est la liste de ses successeurs (pour un graphe orienté) ou de ses voisins (pour un graphe non orienté).
| Opération | Effet |
|---|---|
creer_graphe() | crée un graphe vide |
est_dans(G, v) | renvoie True si v est un sommet du graphe G, sinon False |
ajouter_sommet(G, v) | ajoute un sommet v au graphe G |
supprimer_sommet(G, v) | supprime le sommet v du graphe G |
ajouter_arc(G, v1, v2) | ajoute un arc de v1 vers v2 |
supprimer_arc(G, v1, v2) | supprime l'arc de v1 vers v2 |
successeurs(G, v) | renvoie la liste des sommets extrémités terminales des arcs d'extrémité initiale v |
ajouter_sommet peut nécessiter de préciser directement ses arcs, ou bien laisser la liste vide et ajouter les arcs séparément ensuite avec ajouter_arc — c'est ce deuxième choix qui est fait ci-dessous.
def creer_graphe():
return {}
def est_dans(G, v):
return v in G
def ajouter_sommet(G, v):
G[v] = []
def supprimer_sommet(G, v):
del G[v]
for sommet in G:
if v in G[sommet]:
G[sommet].remove(v)
def ajouter_arc(G, v1, v2):
G[v1].append(v2)
def supprimer_arc(G, v1, v2):
G[v1].remove(v2)
def successeurs(G, v):
if est_dans(G, v):
return G[v]
else:
return []
(sommet, poids) dans chaque liste, par exemple {'Paris': [('Brest', 592), ('Lyon', 546)], ...}. La fonction successeurs devient alors return [sommet for sommet, poids in G[v]] si on veut uniquement les noms, ou return G[v] si on veut garder les poids.
1 à 8 : chacun démarre avec une liste de successeurs vide.G = creer_graphe()for v in [1, 2, 3, 4, 5, 6, 7, 8]: ajouter_sommet(G, v)ajouter_arc(G, 1, 2)ajouter_arc(G, 1, 5)ajouter_arc(G, 1, 8)ajouter_arc(G, 2, 3)ajouter_arc(G, 3, 1)ajouter_arc(G, 3, 4)ajouter_arc(G, 4, 8)ajouter_arc(G, 5, 6)ajouter_arc(G, 7, 6)ajouter_arc(G, 8, 7)
On retrouve, une fois toutes ces instructions exécutées, exactement le graphe de la section 4.1 : G[1] == [2, 5, 8], G[2] == [3], G[3] == [1, 4], G[4] == [8], G[5] == [6], G[6] == [], G[7] == [6], G[8] == [7].
On utilise cette fois une matrice carrée (un tableau à double entrée) : les lignes représentent les sommets de départ, les colonnes les sommets d'arrivée. Une entrée nulle indique qu'il n'y a pas de liaison entre ces deux sommets ; une valeur non nulle indique l'existence d'un arc (ou d'une arête), avec une éventuelle pondération.
Sur un petit graphe à 3 sommets A, B, C, avec des arcs pondérés (y compris des boucles, un arc d'un sommet vers lui-même) : A→A (0,1), A→B (0,5), A→C (0,4), B→B (0,35), B→C (0,65), C→A (0,8), C→C (0,2). On dispose les sommets dans l'ordre alphabétique en ligne et en colonne, et on part d'une ligne i vers une colonne j :
| A | B | C | |
|---|---|---|---|
| A | 0.1 | 0.5 | 0.4 |
| B | 0 | 0.35 | 0.65 |
| C | 0.8 | 0 | 0.2 |
En Python, une matrice se représente comme un tableau de tableaux (une liste de listes) :
graph_proba = [[0.1, 0.5, 0.4],
[0, 0.35, 0.65],
[0.8, 0, 0.2]]
Cette matrice n'est pas symétrique (par exemple, ligne A colonne C vaut 0.4, mais ligne C colonne A vaut 0.8) : c'est normal, ce graphe est orienté.
On applique maintenant la même idée à la version non orientée de l'exemple de la section 4.2. Comme le graphe est non orienté, chaque arête {i, j} remplit deux cases symétriques : ligne i colonne j, et ligne j colonne i.
| Arête | Case remplie à 1 |
|---|---|
| {1,2} | ligne 1 col 2, et ligne 2 col 1 |
| {1,3} | ligne 1 col 3, et ligne 3 col 1 |
| {1,5} | ligne 1 col 5, et ligne 5 col 1 |
| {1,8} | ligne 1 col 8, et ligne 8 col 1 |
| {2,3} | ligne 2 col 3, et ligne 3 col 2 |
| {3,4} | ligne 3 col 4, et ligne 4 col 3 |
| {4,8} | ligne 4 col 8, et ligne 8 col 4 |
| {5,6} | ligne 5 col 6, et ligne 6 col 5 |
| {6,7} | ligne 6 col 7, et ligne 7 col 6 |
| {7,8} | ligne 7 col 8, et ligne 8 col 7 |
Toutes les autres cases restent à 0. On obtient la matrice suivante (les sommets 1 à 8 sont dans l'ordre, en ligne comme en colonne) :
i colonne j vaut toujours la même chose que la case ligne j colonne i. C'est le cas si et seulement si le graphe est non orienté — un simple coup d'œil à la matrice de la section 6.1 (non symétrique) suffit à voir qu'elle représente un graphe orienté.
def creer_matrice(n):
return [[0] * n for _ in range(n)]
def est_dans_matrice(matrice, i):
return 0 <= i < len(matrice)
def ajouter_arc_matrice(matrice, i, j, poids=1):
matrice[i][j] = poids
def supprimer_arc_matrice(matrice, i, j):
matrice[i][j] = 0
def successeurs_matrice(matrice, i):
n = len(matrice)
return [j for j in range(n) if matrice[i][j] != 0]
Ici, les sommets sont numérotés de 0 à n-1 (l'indice dans la matrice), plutôt que d'avoir un nom quelconque comme avec un dictionnaire : c'est la principale différence pratique entre les deux implémentations.
On peut convertir une matrice d'adjacence en liste d'adjacence (par sommets numérotés), et inversement.
def matrice_vers_listes_succ(matrice):
listes_succ = {i: [] for i in range(len(matrice))} # Initialisation des listes de successeurs
for i in range(len(matrice)):
for j in range(len(matrice[i])):
if matrice[i][j] == 1: # S'il y a une arête de i vers j
listes_succ[i].append(j)
return listes_succ
def listes_succ_to_matrice(listes_succ):
n = len(listes_succ) # Nombre de sommets
matrice = [[0] * n for _ in range(n)] # Initialisation de la matrice
for i in listes_succ:
for j in listes_succ[i]:
matrice[i][j] = 1 # Ajout des arêtes
return matrice
Par exemple, avec la petite matrice [[0, 1, 0], [0, 0, 1], [1, 0, 0]] (un cycle 0 → 1 → 2 → 0), matrice_vers_listes_succ renvoie {0: [1], 1: [2], 2: [0]}.
if matrice[i][j] == 1 en if matrice[i][j] != 0, et de conserver le poids : listes_succ[i].append((j, matrice[i][j])).
n désigne le nombre de sommets du graphe, et m le nombre d'arcs (ou d'arêtes).
| Opération | Liste d'adjacence (dictionnaire) | Matrice d'adjacence |
|---|---|---|
est_dans(G, v) | O(1) | O(1) (test d'indice) |
ajouter_sommet(G, v) | O(1) | O(n) : il faut agrandir toutes les lignes d'une case, et ajouter une ligne |
supprimer_sommet(G, v) | O(n + m) : il faut retirer v de la liste de chaque autre sommet | O(n²) : il faut décaler toute une ligne et toute une colonne |
ajouter_arc / supprimer_arc | O(1) (ajout) à O(n) (suppression, à cause de list.remove) | O(1) : on modifie une seule case |
successeurs(G, v) | O(1) : la liste existe déjà | O(n) : il faut parcourir toute la ligne |
En résumé : la liste d'adjacence est en général plus efficace pour ajouter ou retirer des sommets, et pour obtenir directement les successeurs d'un sommet. La matrice, elle, permet de tester en O(1) si un arc précis existe entre deux sommets donnés, et son inconvénient (le nombre de cases, n²) devient négligeable quand le graphe est très dense (beaucoup d'arcs par rapport au nombre de sommets).
V) et les liaisons entre eux (E).i colonne j indique s'il existe un arc de i vers j (et son poids éventuel).