Adloun

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

Définition 14.1Graphe

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 -&gt; j j -&gt; 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).

Exemple 14.2Un petit graphe non orienté

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é .

iRemarqueLemme des poignées de main

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) &lt;- 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)
Attention

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) &lt;- 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

Exemple 14.3Degré d'un sommet

(* 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)

Exercice 1 : Construire une matrice d'adjacence

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.

Exercice 2 : Degré (matrice)

É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.

Exercice 3 : Listes d'adjacence

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)

Exercice 4 : Nombre d'arêtes

É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 &gt; i.)

Exercice 5 : Matrice vers listes

É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).

Exercice 6 : Listes vers 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)

Exercice 7 : Voisins communs

É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.)

Exercice 8 : Graphe complet

É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.

Exercice 9 : Sommet isolé

É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.

Exercice 10 : Chemins de longueur 2

É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).

Synthèse du chapitre (à retenir)
  • 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.
Thème B — Propriétés.
Thème C — Représentations pondérées.
Thème D — Chemins par la matrice.

Continuer sur Adloun : animation, QCM, fiches, exercices