Graphes : modélisation et représentations
Cours complet · OCaml (option informatique), chapitre 14 · prépas MPSI et MP, option informatique
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
<i class="fa-solid fa-compass mr-2" style="color:#9A563B"></i>14.1 Introduction et motivation
Réseaux routiers, liens entre pages web, amis d'un réseau social, dépendances entre tâches : une foule de problèmes se modélisent par un graphe — des sommets reliés par des arêtes. Avant d'explorer un graphe (parcours, plus courts chemins, aux chapitres suivants), il faut savoir le représenter en machine. Ce chapitre installe le vocabulaire et les deux représentations fondamentales en OCaml : la matrice d'adjacence et les listes d'adjacence, leurs forces respectives, et comment passer de l'une à l'autre.
On y mobilise tout l'arsenal des chapitres précédents : tableaux (chapitre 5) pour la matrice, listes (chapitre 2) pour les voisinages, et le raisonnement de complexité pour choisir la bonne structure selon la densité du graphe.
14.2 Vocabulaire
Un graphe est la donnée d'un ensemble de sommets et d'un ensemble d'arêtes reliant des paires de sommets. On numérote les sommets de 0 à n-1. Le graphe est :
- non orienté si les arêtes n'ont pas de sens (
{i, j}) ; - orienté si les arcs ont un sens (
i -> jj -> i).
Les voisins d'un sommet sont ceux qui lui sont reliés ; le degré d'un sommet est son nombre de voisins. Un graphe peut être pondéré (chaque arête porte un poids : distance, coût).
On considère sommets ( à ) et les arêtes {0,1}, {0,2}, {1,2}, {2,3}, {3,4}. Le sommet a pour voisins , , : son degré est . Le sommet n'a que le voisin : degré .
Dans un graphe non orienté, chaque arête contribue au degré de chacune de ses deux extrémités : la somme des degrés vaut donc le double du nombre d'arêtes. Conséquence : cette somme est toujours paire.
14.3 La matrice d'adjacence
On code le graphe par une matrice carrée nn de booléens : m.(i).(j) vaut true s'il y a une arête de i vers j.
let matrice_vide n = Array.make_matrix n n false
let ajoute_arete m i j = (* graphe NON orienté : symétrique *)
m.(i).(j) <- true;
m.(j).(i) <- true
Pour un graphe orienté, on ne pose que m.(i).(j) <- true. Pour un graphe pondéré, on remplace les booléens par des entiers (le poids, une valeur convenue signalant l'absence d'arête).
Complexité : Matrice d'adjacence
Tester l'existence d'une arête m.(i).(j) est en . Mais l'espace occupé est , quel que soit le nombre d'arêtes, et lister les voisins d'un sommet demande de parcourir toute sa ligne (). Idéal pour les graphes denses (beaucoup d'arêtes) ou quand on teste souvent des arêtes.
14.4 Les listes d'adjacence
On code le graphe par un tableau de listes : adj.(i) est la liste des voisins de i.
let listes_vides n = Array.make n []
let ajoute_arc adj i j = adj.(i) <- j :: adj.(i) (* orienté : i -> j *)
let ajoute_arete adj i j = (* non orienté *)
adj.(i) <- j :: adj.(i);
adj.(j) <- i :: adj.(j)
Array.make n [] place la même liste vide dans toutes les cases — mais contrairement au piège des tableaux de tableaux (chapitre 5), il n'y a aucun problème ici : [] est immuable, et adj.(i) <- j :: adj.(i) remplace la case par une liste neuve sans modifier les autres. Le partage d'une valeur immuable est sans danger ; c'est la mutation d'une valeur partagée qui posait problème.
Complexité : Listes d'adjacence
Lister les voisins de i est en — optimal. L'espace est , proportionnel à la taille réelle du graphe. En revanche, tester une arête précise demande de parcourir la liste (). Idéal pour les graphes creux (peu d'arêtes) et les parcours.
14.5 Degrés et conversions
(* matrice *)
let degre_mat m i =
let d = ref 0 in
for j = 0 to Array.length m - 1 do
if m.(i).(j) then d := !d + 1
done;
!d
(* listes : c'est la longueur de la liste des voisins *)
let degre_adj adj i = List.length adj.(i)
Avec la matrice, on compte les true de la ligne i () ; avec les listes, c'est directement List.length ().
On passe d'une représentation à l'autre par un double parcours :
let vers_listes m =
let n = Array.length m in
let adj = Array.make n [] in
for i = 0 to n - 1 do
for j = 0 to n - 1 do
if m.(i).(j) then adj.(i) <- j :: adj.(i)
done
done;
adj
let vers_matrice adj =
let n = Array.length adj in
let m = Array.make_matrix n n false in
for i = 0 to n - 1 do
List.iter (fun j -> m.(i).(j) <- true) adj.(i)
done;
m
(L'ordre des voisins dans vers_listes est décroissant, ce qui est sans importance.)
<i class="fa-solid fa-dumbbell mr-2" style="color:#2E7559"></i>14.6 Exercices résolus
Niveau (application directe du cours)
Construire la matrice du graphe non orienté à sommets d'arêtes {0,1}, {0,2}, {1,2}, {2,3}, {3,4}, et tester si 0 et 3 sont voisins.
Démonstration
let g = matrice_vide 5
let () =
ajoute_arete g 0 1; ajoute_arete g 0 2; ajoute_arete g 1 2;
ajoute_arete g 2 3; ajoute_arete g 3 4
(* g.(0).(3) vaut false : 0 et 3 ne sont pas voisins *)
ajoute_arete pose la symétrie (g.(i).(j) et g.(j).(i)). g.(0).(3) est false, g.(2).(3) est true.
Écrire degre_mat m i et donner le degré de chaque sommet du graphe de l'exercice 1.
Démonstration
let degre_mat m i =
let d = ref 0 in
for j = 0 to Array.length m - 1 do
if m.(i).(j) then d := !d + 1
done;
!d
Degrés : sommet , , , , . Somme : cinq arêtes, conforme au lemme des poignées de main.
Construire le même graphe en listes d'adjacence, et donner le degré du sommet .
Démonstration
let g = listes_vides 5
let () =
ajoute_arete g 0 1; ajoute_arete g 0 2; ajoute_arete g 1 2;
ajoute_arete g 2 3; ajoute_arete g 3 4
(* degre_adj g 2 = List.length g.(2) = 3 *)
g.(2) contient [3; 1; 0] (ordre d'insertion inversé) : trois voisins, donc degré , comme avec la matrice.
Niveau (raisonnement intermédiaire)
Écrire nb_aretes m (graphe non orienté représenté par matrice).
Démonstration
let nb_aretes m =
let n = Array.length m in
let c = ref 0 in
for i = 0 to n - 1 do
for j = 0 to n - 1 do
if m.(i).(j) then c := !c + 1
done
done;
!c / 2
Chaque arête {i,j} apparaît deux fois dans la matrice symétrique (m.(i).(j) et m.(j).(i)) : on compte les true et l'on divise par . (On pourrait aussi ne compter que la moitié supérieure, j > i.)
Écrire vers_listes m.
Démonstration
let vers_listes m =
let n = Array.length m in
let adj = Array.make n [] in
for i = 0 to n - 1 do
for j = 0 to n - 1 do
if m.(i).(j) then adj.(i) <- j :: adj.(i)
done
done;
adj
Pour chaque ligne i, on ajoute en tête de adj.(i) chaque colonne j marquée. Coût (on lit toute la matrice).
Écrire vers_matrice adj.
Démonstration
let vers_matrice adj =
let n = Array.length adj in
let m = Array.make_matrix n n false in
for i = 0 to n - 1 do
List.iter (fun j -> m.(i).(j) <- true) adj.(i)
done;
m
On parcourt chaque liste de voisins et on coche la case correspondante. Coût pour lire les listes, plus pour allouer la matrice.
Niveau (approfondissement)
Écrire voisins_communs m i j : le nombre de sommets voisins à la fois de i et de j.
Démonstration
let voisins_communs m i j =
let n = Array.length m in
let c = ref 0 in
for k = 0 to n - 1 do
if m.(i).(k) && m.(j).(k) then c := !c + 1
done;
!c
On compte les sommets k adjacents simultanément à i et à j. La matrice rend ce test direct ( par sommet), d'où un coût . (En listes, il faudrait croiser deux listes, plus laborieux.)
Écrire est_complet m : toute paire de sommets distincts est-elle reliée ?
Démonstration
let est_complet m =
let n = Array.length m in
let ok = ref true in
for i = 0 to n - 1 do
for j = 0 to n - 1 do
if i <> j && not m.(i).(j) then ok := false
done
done;
!ok
On vérifie qu'aucune paire (i, j) distincte n'est dépourvue d'arête. Un graphe complet à n sommets a arêtes ; sa matrice n'a que des true hors diagonale.
Écrire isole m : existe-t-il un sommet de degré ? Renvoyer son numéro en option.
Démonstration
let isole m =
let n = Array.length m in
let rec cherche i =
if i = n then None
else if degre_mat m i = 0 then Some i
else cherche (i + 1)
in
cherche 0
On parcourt les sommets et l'on renvoie le premier de degré nul (Some i), ou None si chacun a au moins un voisin. Un sommet isolé est inatteignable : sa présence change la connexité du graphe.
Écrire chemins_deux m renvoyant une matrice r telle que r.(i).(j) soit le nombre de chemins de longueur exactement de i à j.
Démonstration
let chemins_deux m =
let n = Array.length m in
let r = Array.make_matrix n n 0 in
for i = 0 to n - 1 do
for j = 0 to n - 1 do
let c = ref 0 in
for k = 0 to n - 1 do
if m.(i).(k) && m.(k).(j) then c := !c + 1
done;
r.(i).(j) <- !c
done
done;
r
Un chemin de longueur de i à j passe par un sommet intermédiaire k adjacent aux deux : on compte ces k. C'est exactement le carré de la matrice d'adjacence () : plus généralement, (M^p).(i).(j) compte les chemins de longueur p. Coût (un produit matriciel).
- Graphe : sommets numérotés
0..n-1, arêtes (non orienté) ou arcs (orienté), éventuellement pondérés. Degré nombre de voisins ; somme des degrés (poignées de main). - Matrice d'adjacence (
bool array array) : test d'arête , espace , voisins . Pour graphes denses. - Listes d'adjacence (
int list array) : voisins , espace , test d'arête . Pour graphes creux et les parcours.Array.make n []est sûr ([]immuable, on remplace). - Conversions matrice listes par double parcours.
- Le carré (et plus généralement les puissances) de la matrice d'adjacence compte les chemins d'une longueur donnée.
14.7 Exercices d'entraînement
Légende : application directe, raisonnement intermédiaire, approfondissement ; signale un classique incontournable. La numérotation prolonge celle des dix exercices résolus.
Thème A — Construction et degrés.
- [11.] Écrire
degres_tous m: le tableau des degrés de tous les sommets. - [12.] Vérifier sur un graphe que la somme des degrés est paire et vaut
2 * nb_aretes m. - [13.] Écrire les versions orientées :
degre_sortantetdegre_entrantd'un sommet (matrice).
Thème B — Propriétés.
- [14.]
est_symetrique m: la matrice représente-t-elle un graphe non orienté ? - [15.]
a_boucle m: existe-t-il une arête d'un sommet vers lui-même (m.(i).(i)) ? - [16.]
degre_max adj: le degré maximal (listes d'adjacence).
Thème C — Représentations pondérées.
- [17.] Représenter un graphe pondéré par
int array array(poids, et une valeur convenue pour l'absence d'arête) ; écrirepoids m i j. - [18.] Représenter un graphe pondéré par listes :
(int * int) list array(voisin, poids) ; écrirevoisins adj i. - [19.]
matrice_grille p q: la matrice d'adjacence d'une grille (chaque case reliée à ses voisines orthogonales).
Thème D — Chemins par la matrice.
- [20.] Écrire
produit a b(produit de deux matrices d'entiers) et en déduirechemins_deuxparproduit m m. - [21.]
chemins_p m p: nombre de chemins de longueurp(élevermà la puissancep). - [22.] Discuter : pour un réseau social de membres ayant chacun amis, quelle représentation choisir et pourquoi ?