Licence 3

Algorithmes de graphes : parcours et plus courts chemins

60 min15 exercicesSéquence 3.3Licence 3

Vidéo disponible dans la version Premium

Durée : 60 min

Algorithmes de graphes : parcours et plus courts chemins

1. Représentation des graphes

Un graphe G=(V,E)G=(V,E) est constitué d'un ensemble de sommets VV (taille n=Vn=|V|) et d'arêtes EE (taille m=Em=|E|), orientées ou non. Deux représentations usuelles : la matrice d'adjacence (tableau n×nn\times n, accès O(1)O(1) à une arête, mais O(n2)O(n^2) de mémoire — adaptée aux graphes denses) et la liste d'adjacence (pour chaque sommet, la liste de ses voisins, mémoire O(n+m)O(n+m) — adaptée aux graphes creux, cas le plus fréquent en pratique).

2. Parcours en profondeur (DFS)

Le parcours en profondeur (Depth-First Search) explore un graphe en allant aussi loin que possible avant de revenir en arrière (backtracking), à l'aide d'une pile (explicite ou via la récursion). Complexité : O(n+m)O(n+m) (chaque sommet et chaque arête est visité une fois).

DFS(G, s) :

marquer s comme visité

pour chaque voisin v de s non visité :

DFS(G, v)

Applications : détection de cycles, tri topologique d'un graphe orienté acyclique (DAG), recherche des composantes connexes.

3. Parcours en largeur (BFS)

Le parcours en largeur (Breadth-First Search) explore le graphe niveau par niveau à partir d'une source ss, à l'aide d'une file. Complexité O(n+m)O(n+m).

BFS(G, s) :

enfiler s, marquer s comme visité, distance(s)=0

tant que la file n'est pas vide :

u = défiler()

pour chaque voisin v de u non visité :

marquer v, distance(v) = distance(u)+1, enfiler v

Propriété clé : dans un graphe non pondéré, le BFS calcule exactement les plus courts chemins (en nombre d'arêtes) depuis ss vers tous les autres sommets atteignables.

4. Algorithme de Dijkstra (plus courts chemins, poids positifs)

Pour un graphe pondéré par des poids strictement positifs, l'algorithme de Dijkstra calcule les plus courts chemins depuis une source ss vers tous les autres sommets, en utilisant une file de priorité (tas-min) sur les distances provisoires :

Dijkstra(G, s) :

dist(s) = 0, dist(v) = +infini pour v ≠ s

file de priorité Q = {tous les sommets, priorité = dist}

tant que Q n'est pas vide :

u = extraire le sommet de Q de plus petite distance

pour chaque voisin v de u (arête de poids w) :

si dist(u) + w < dist(v) :

dist(v) = dist(u) + w (relaxation)

mettre à jour la priorité de v dans Q

Complexité : O((n+m)logn)O((n+m)\log n) avec une implémentation par tas binaire (chaque extraction et chaque mise à jour de priorité coûte O(logn)O(\log n), et il y a O(m)O(m) relaxations au total).

Exemple résolu. Graphe orienté : ABA\to B (poids 44), ACA\to C (poids 11), CBC\to B (poids 22), BDB\to D (poids 11), CDC\to D (poids 55). Depuis AA : dist(A)=0\text{dist}(A)=0. On extrait AA, on relâche BB (dist(B)=4\text{dist}(B)=4) et CC (dist(C)=1\text{dist}(C)=1). On extrait CC (distance minimale 11), on relâche BB via CC : dist(C)+2=3<4\text{dist}(C)+2=3<4, donc dist(B)=3\text{dist}(B)=3 ; on relâche DD via CC : dist(D)=1+5=6\text{dist}(D)=1+5=6. On extrait BB (distance 33), on relâche DD via BB : 3+1=4<63+1=4<6, donc dist(D)=4\text{dist}(D)=4. Résultat final : dist(A,B,C,D)=(0,3,1,4)\text{dist}(A,B,C,D)=(0,3,1,4).

5. Pourquoi Dijkstra échoue avec des poids négatifs

Dijkstra suppose qu'une fois un sommet extrait de la file (sa distance finalisée), aucune amélioration future n'est possible — ce qui n'est vrai que si tous les poids restants sont positifs (toute relaxation ultérieure ne peut qu'augmenter une distance). Avec des poids négatifs, un chemin découvert plus tard pourrait être plus court, invalidant cette hypothèse. Pour des graphes avec poids négatifs (sans cycle de poids négatif), on utilise l'algorithme de Bellman-Ford (O(nm)O(nm), plus lent mais correct dans ce cas).

6. Algorithme de Floyd-Warshall (tous les plus courts chemins)

Pour calculer les plus courts chemins entre toutes les paires de sommets, l'algorithme de Floyd-Warshall utilise une programmation dynamique en O(n3)O(n^3) :

dk[i][j]=min(dk1[i][j], dk1[i][k]+dk1[k][j])d_k[i][j] = \min\big(d_{k-1}[i][j],\ d_{k-1}[i][k]+d_{k-1}[k][j]\big)

dk[i][j]d_k[i][j] est la plus courte distance de ii à jj en n'utilisant que les sommets intermédiaires {1,,k}\{1,\dots,k\}. C'est avantageux par rapport à nn exécutions de Dijkstra (O(n(n+m)logn)O(n(n+m)\log n)) lorsque le graphe est dense (mm proche de n2n^2).

7. Récapitulatif


AlgorithmeProblème résoluComplexité
|---|---|---|



DFS / BFSparcours, plus courts chemins (non pondéré)O(n+m)O(n+m)
Dijkstraplus courts chemins depuis une source (poids 0\geq0)O((n+m)logn)O((n+m)\log n)
Bellman-Fordplus courts chemins depuis une source (poids quelconques, sans cycle négatif)O(nm)O(nm)
Floyd-Warshallplus courts chemins entre toutes les pairesO(n3)O(n^3)

Exercices

Quelle structure de données utilise naturellement le parcours en largeur (BFS) ?

Vrai ou faux : dans un graphe non pondéré, le BFS depuis une source ss calcule les plus courts chemins (en nombre d'arêtes) vers tous les sommets atteignables.

Quelle structure de données est utilisée par l'algorithme de Dijkstra pour sélectionner efficacement le prochain sommet à traiter ?

Vrai ou faux : l'algorithme de Dijkstra fonctionne correctement même si certaines arêtes ont un poids négatif.

Quelle est la complexité de l'algorithme de Floyd-Warshall pour calculer les plus courts chemins entre toutes les paires de sommets ?

Suivez votre progression

Connectez-vous pour sauvegarder votre avancement et gagner des XP.

Se connecter