Le modèle des graphes
Cours complet · informatique (MP2I/MPI), chapitre 13 · MP2I et MPI
Travailler ce chapitre sur Adloun Exercices corrigés de ce chapitre
13.1 Un modèle, et presque tout devient un graphe
Le programme range les graphes parmi les « structures de données relationnelles », et le mot est juste : là où l'arbre impose une hiérarchie, le graphe ne dit rien de plus que « ces objets sont reliés ». C'est cette pauvreté qui fait sa portée.
Il demande de « mettre en avant des applications importantes et si possible modernes : réseau de transport, graphe du web, réseaux sociaux, bio-informatique », et de « préciser autant que possible la taille typique de tels graphes ». Ces ordres de grandeur ne sont pas anecdotiques — ils décident de l'algorithme qu'on a le droit d'écrire.
| Graphe | Sommets | Arcs | Ce qu'un donnerait |
|---|---|---|---|
| Métro d'une ville | instantané | ||
| Réseau routier français | opérations : des jours | ||
| Graphe du web | inconcevable | ||
| Réseau social mondial | inconcevable |
Sur un graphe de sommets, un algorithme en est inutilisable, et un algorithme en tourne en quelques secondes. C'est pourquoi la représentation choisie — matrice ou listes — n'est pas un détail d'écriture : elle change la complexité de tout ce qu'on bâtira dessus.
13.2 Définitions
Un graphe est la donnée d'un ensemble fini de sommets (ou nœuds) et d'un ensemble de liens.
- Si les liens sont des couples ordonnés, on parle d'arcs et le graphe est orienté.
- Si ce sont des paires non ordonnées, on parle d'arêtes et le graphe est non orienté.
On note et . Une boucle est un arc ou une arête d'un sommet vers lui-même. Le programme précise : « on n'évoque pas les multi-arcs » — entre deux sommets, il y a au plus un lien dans chaque sens.
Dans un graphe non orienté, le degré est le nombre d'arêtes incidentes à . Dans un graphe orienté, le programme fixe les notations :
Dans un graphe non orienté, . Dans un graphe orienté, .
Démonstration
Chaque arête est comptée une fois dans et une fois dans : la somme des degrés compte donc chaque arête deux fois. Chaque arc , lui, est compté une seule fois dans et une seule dans .
Un chemin de à est une suite de sommets où chaque est un arc (ou une arête). Sa longueur est , le nombre d'arcs — et non de sommets.
Un cycle est un chemin de longueur non nulle dont l'origine et l'extrémité coïncident. Dans un graphe non orienté, on exige de plus qu'il n'emprunte pas deux fois la même arête, faute de quoi tout aller-retour serait un cycle.
- Un graphe non orienté est connexe si tout couple de sommets est relié par un chemin. Ses composantes connexes sont ses parties connexes maximales.
- Un graphe orienté est fortement connexe si pour tout couple il existe un chemin de à et un de à .
Le graphe orienté est connexe si l'on oublie les orientations, et n'est pas fortement connexe : on ne revient jamais de vers . La différence est celle d'une ville aux rues à sens unique : deux quartiers peuvent sembler voisins sur la carte et n'être joignables que par un long détour, voire pas du tout. La recherche des composantes fortement connexes fait l'objet du chapitre chap:graphes-avances.
- Un graphe orienté acyclique — un dag — est un graphe orienté sans cycle. Il modélise toute relation de dépendance : tâches à ordonner, cellules d'un tableur, calcul d'un programme. Le lien avec les ordres bien fondés du chapitre chap:induction est exact.
- Un arbre est un graphe non orienté connexe et acyclique. Une forêt est un graphe acyclique, c'est-à-dire une réunion disjointe d'arbres.
- Un graphe est biparti si l'on peut partager en deux parties et telles que toute arête relie un sommet de à un sommet de .
Le chapitre chap:arbres définissait l'arbre par induction, avec une racine. Ici il est défini comme un graphe connexe acyclique, sans racine privilégiée. Les deux coïncident dès qu'on désigne un sommet comme racine : la structure inductive est alors déterminée. Un arbre au sens des graphes possède exactement arêtes — une de moins que de sommets — et c'est une caractérisation utile.
Un graphe est biparti si et seulement s'il ne contient aucun cycle de longueur impaire.
Démonstration (Sens direct)
Soit biparti de parties et . Un chemin alterne nécessairement entre et : après un nombre pair d'arêtes on est revenu du même côté, après un nombre impair on est de l'autre. Un cycle revient à son origine, donc du même côté : sa longueur est paire.
La réciproque se démontre par un parcours qui colorie alternativement — c'est exactement l'algorithme de bicolorabilité que le programme cite au chapitre chap:parcours. La caractérisation est donc effective : elle donne un test en .
On peut attacher une valeur aux arcs : une pondération numérique (distance, coût, capacité) ou une étiquette quelconque. Le programme motive cet ajout « par des exemples concrets : graphe de distance, automate fini, diagramme de décision binaire » — et l'automate fini du chapitre chap:automates est exactement cela : un graphe orienté dont les arcs portent des lettres.
13.3 Représenter un graphe
Tout ce qui précède est mathématique. Le choix de la représentation, lui, est informatique — et il décide de la complexité de tous les algorithmes à venir.
13.3.1 Matrice d'adjacence
est la matrice définie par s'il existe un arc de vers , et sinon. Pour un graphe pondéré, on y range le poids, avec une valeur convenue pour l'absence d'arc.
/* Matrice d'adjacence : un tableau statique à deux dimensions. */
#define N_MAX 100
int m[N_MAX][N_MAX]; /* m[u][v] vaut 1 s'il existe un arc u -> v */
13.3.2 Listes d'adjacence
À chaque sommet on associe la liste de ses voisins sortants. La place occupée est alors proportionnelle au nombre d'arcs, et non à son carré.
(* Le graphe ci-dessus, en listes d'adjacence. *)
let g = [| [1; 2]; [3]; []; [2] |]
Méthode : Les listes d'adjacence en C, sans allocation dynamique
Le programme donne une indication précise : « la présentation en C s'effectue à travers des tableaux statiques. Pour la représentation en liste d'adjacence, on peut considérer un tableau à deux dimensions dont les lignes représentent chaque liste avec une sentinelle ou un indicateur de taille en premier indice. »
#define N_MAX 100
#define DEG_MAX 50
/* voisins[u][0] est le NOMBRE de voisins de u ;
voisins[u][1..voisins[u][0]] sont ces voisins. */
int voisins[N_MAX][DEG_MAX + 1];
/* Ajoute l'arc u -> v. Précondition : voisins[u][0] < DEG_MAX. */
void ajouter_arc(int u, int v) {
assert(voisins[u][0] < DEG_MAX);
voisins[u][0] = voisins[u][0] + 1;
voisins[u][voisins[u][0]] = v;
}
/* Parcourir les voisins de u : */
for (int k = 1; k <= voisins[u][0]; k = k + 1) {
int v = voisins[u][k];
/* ... */
}
Aucun malloc, aucun free, aucun pointeur fou : c'est le procédé à retenir pour les épreuves écrites, où l'allocation dynamique est un risque inutile.
13.3.3 Le choix, et il est décisif
| Matrice | Listes | |
|---|---|---|
| Mémoire | ||
| Tester « l'arc existe-t-il ? » | ||
| Parcourir les voisins de | ||
| Parcourir tous les arcs | ||
| Ajouter un arc |
La ligne décisive est la troisième. Un parcours de graphe visite les voisins de chaque sommet : il coûte avec une matrice et avec des listes. Sur le réseau routier français — sommets, arcs — cela fait contre : des jours contre une seconde.
Un graphe est creux quand , dense quand approche . La règle :
- creux — presque tous les graphes réels — : listes d'adjacence ;
- dense, ou si l'on teste sans cesse l'existence d'un arc : matrice.
Le réseau routier a un degré moyen de : sa matrice occuperait cases pour arcs, soit un remplissage de . La matrice n'y est pas seulement lente, elle ne tient pas en mémoire.
Le programme le dit pour les deux algorithmes de plus courts chemins : « on présente l'algorithme de Dijkstra … en lien avec la représentation de graphes par listes d'adjacences. On présente l'algorithme de Floyd-Warshall en lien avec la représentation de graphes par matrice d'adjacence. » Ce n'est pas une convention d'exposition : Dijkstra parcourt des voisins, Floyd-Warshall consulte des couples. Chacun est écrit pour la structure qui répond à sa question — chapitre chap:parcours.
Si est la matrice d'adjacence d'un graphe non pondéré, le coefficient de compte les chemins de longueur exactement de à .
Démonstration (Par récurrence sur )
Vrai pour par définition. Si c'est vrai pour , alors
et cette somme compte, pour chaque , les chemins de longueur de à prolongés par l'arc lorsqu'il existe. Tout chemin de longueur est obtenu ainsi, une seule fois, en désignant son avant-dernier sommet.
13.4 Ce qu'il faut retenir
- , avec et ; degrés et ; .
- Connexe (non orienté) et fortement connexe (orienté) sont deux notions très différentes.
- Trois familles : dag pour les dépendances, arbre pour les hiérarchies ( arêtes), biparti pour les appariements — et biparti équivaut à « aucun cycle impair ».
- Matrice : de mémoire, test d'arc en . Listes : , parcours des voisins en .
- Les graphes réels sont creux. Sauf raison contraire, on prend les listes — et le choix se fait avant d'écrire l'algorithme, pas après.