Fiche récapitulative générée pour impression / export PDF.
Licence 3 · Informatique L3 — Algorithmique, structures de données et graphes
Algorithmes de graphes : parcours et plus courts chemins
Algorithmes de graphes : parcours et plus courts chemins
1. Représentation des graphes
Un graphe est constitué d'un ensemble de sommets (taille ) et d'arêtes (taille ), orientées ou non. Deux représentations usuelles : la matrice d'adjacence (tableau , accès à une arête, mais de mémoire — adaptée aux graphes denses) et la liste d'adjacence (pour chaque sommet, la liste de ses voisins, mémoire — 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é : (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 , à l'aide d'une file. Complexité .
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 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 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é : avec une implémentation par tas binaire (chaque extraction et chaque mise à jour de priorité coûte , et il y a relaxations au total).
Exemple résolu. Graphe orienté : (poids ), (poids ), (poids ), (poids ), (poids ). Depuis : . On extrait , on relâche () et (). On extrait (distance minimale ), on relâche via : , donc ; on relâche via : . On extrait (distance ), on relâche via : , donc . Résultat final : .
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 (, 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ù est la plus courte distance de à en n'utilisant que les sommets intermédiaires . C'est avantageux par rapport à exécutions de Dijkstra () lorsque le graphe est dense ( proche de ).
7. Récapitulatif
| Algorithme | Problème résolu | Complexité |
| DFS / BFS | parcours, plus courts chemins (non pondéré) | |
| Dijkstra | plus courts chemins depuis une source (poids ) | |
| Bellman-Ford | plus courts chemins depuis une source (poids quelconques, sans cycle négatif) | |
| Floyd-Warshall | plus courts chemins entre toutes les paires |
Exercices de la leçon
Exercice 1
Quelle structure de données utilise naturellement le parcours en largeur (BFS) ?
Corrigé
Le BFS enfile les voisins découverts et les traite dans l'ordre FIFO, garantissant une exploration niveau par niveau.
Exercice 2
Vrai ou faux : dans un graphe non pondéré, le BFS depuis une source calcule les plus courts chemins (en nombre d'arêtes) vers tous les sommets atteignables.
Corrigé
Vrai. C'est la propriété clé du BFS : comme il explore niveau par niveau, la première fois qu'un sommet est atteint, c'est nécessairement par un chemin le plus court possible (en nombre d'arêtes).
Exercice 3
Quelle structure de données est utilisée par l'algorithme de Dijkstra pour sélectionner efficacement le prochain sommet à traiter ?
Corrigé
Dijkstra utilise une file de priorité (typiquement un tas-min) pour extraire efficacement, à chaque étape, le sommet non encore traité ayant la plus petite distance provisoire.
Exercice 4
Vrai ou faux : l'algorithme de Dijkstra fonctionne correctement même si certaines arêtes ont un poids négatif.
Corrigé
Faux. Dijkstra suppose que les distances ne peuvent qu'augmenter par relaxation future, ce qui échoue avec des poids négatifs (un chemin découvert plus tard pourrait raccourcir une distance déjà finalisée). Il faut alors utiliser Bellman-Ford.
Exercice 5
Quelle est la complexité de l'algorithme de Floyd-Warshall pour calculer les plus courts chemins entre toutes les paires de sommets ?
Corrigé
Floyd-Warshall procède par programmation dynamique avec trois boucles imbriquées sur les sommets, donnant une complexité .
Exercice 6
Pour un graphe avec sommets et arêtes représenté en liste d'adjacence, quelle est la complexité totale du DFS ou du BFS ?
Corrigé
Chaque sommet est visité (et marqué) une seule fois, et chaque arête est examinée au plus une ou deux fois (selon orientation), d'où une complexité linéaire — optimale, puisqu'il faut au moins lire toute l'entrée.
Exercice 7
Sur le graphe orienté (poids 2), (poids 5), (poids 1), exécuter Dijkstra depuis et donner .
Corrigé
Chemin direct : coût . Chemin : coût , strictement meilleur. Dijkstra trouve donc (en passant par ).
Exercice 8
Pourquoi préfère-t-on la liste d'adjacence à la matrice d'adjacence pour représenter un graphe creux (peu d'arêtes par rapport au nombre de sommets, ) ?
Corrigé
Pour un graphe creux, , donc la liste d'adjacence ( mémoire) est bien plus économe que la matrice d'adjacence ( mémoire, dont la plupart des cases seraient à ).
Exercice 9
Vrai ou faux : le DFS peut être utilisé pour détecter la présence d'un cycle dans un graphe orienté.
Corrigé
Vrai. En suivant les couleurs des sommets pendant le parcours (blanc = non visité, gris = en cours d'exploration, noir = terminé), on détecte un cycle si l'on rencontre une arête menant vers un sommet gris (déjà sur la pile d'appels en cours) — c'est une arête de retour.
Exercice 10
Expliquer pourquoi la complexité de Dijkstra avec une file de priorité par tas binaire est plutôt que .
Corrigé
Décomposition du coût total en deux parties.
Extractions : chaque sommet est extrait de la file de priorité exactement une fois (une fois sa distance finalisée, il n'y retourne jamais). Avec un tas binaire, chaque extraction du minimum coûte . Total pour les extractions : .
Relaxations (mises à jour de priorité) : chaque arête du graphe est examinée au plus une fois (lors du traitement de son sommet de départ), et peut déclencher une mise à jour de la priorité du sommet d'arrivée dans le tas, coûtant avec une implémentation adaptée (tas indexé permettant la mise à jour de clé). Total pour les arêtes : .
Somme totale : .
C'est bien plus efficace que pour les graphes creux où : on obtient au lieu de .
Exercice 11
Sur le graphe orienté (poids 4), (poids 1), (poids 2), (poids 1), (poids 5), exécuter Dijkstra depuis pas à pas et donner les distances finales pour .
Corrigé
Initialisation : , .
Étape 1 — extraire (distance ). Relâcher ses voisins : ; .
Étape 2 — extraire (distance minimale ). Relâcher ses voisins : (amélioration !) ; .
Étape 3 — extraire (distance minimale ). Relâcher ses voisins : (amélioration !).
Étape 4 — extraire (distance ). Aucun voisin sortant, rien à relâcher.
Résultat final : , , , . Le plus court chemin vers passe par (coût ), pas par le chemin direct (coût ) ni par (coût ).
Exercice 12
Démontrer pourquoi l'algorithme de Dijkstra peut donner un résultat incorrect en présence d'une arête de poids négatif, à l'aide d'un contre-exemple explicite.
Corrigé
Construction du contre-exemple. Graphe orienté : (poids ), (poids ), (poids ).
Exécution de Dijkstra depuis :
- Initialisation : .
- Extraction de : relâcher () et ().
- Extraction du sommet de distance minimale parmi les non traités : (distance ). On finalise et on ne le remet jamais en question (Dijkstra suppose qu'aucune amélioration future n'est possible pour un sommet déjà extrait).
- Extraction de (distance ) : on relâche via , donnant — mais a déjà été extrait et finalisé à la valeur , donc Dijkstra ignore cette amélioration (selon l'implémentation standard, qui ne retraite jamais un sommet déjà extrait).
Résultat erroné de Dijkstra : .
Vrai plus court chemin : , de coût , strictement inférieur à .
Conclusion : Dijkstra donne ici un résultat incorrect ( au lieu de ), car son hypothèse fondamentale (un sommet extrait ne peut plus être amélioré) est violée par la présence du poids négatif sur l'arête . C'est exactement pourquoi Dijkstra exige des poids non négatifs, et pourquoi on utilise Bellman-Ford dans le cas contraire.
Exercice 13
Vrai ou faux : l'algorithme de Bellman-Ford peut détecter la présence d'un cycle de poids négatif accessible depuis la source.
Corrigé
Vrai. Après les itérations habituelles de relaxation (suffisantes pour des plus courts chemins simples, sans cycle), Bellman-Ford effectue une itération supplémentaire : si une relaxation améliore encore une distance, c'est la preuve qu'un cycle de poids négatif est accessible (sinon le plus court chemin ne serait pas borné, n'existant simplement pas au sens usuel).
Exercice 14
Justifier, dans le cadre de la programmation dynamique de Floyd-Warshall, la relation de récurrence .
Corrigé
Définition de . C'est la longueur du plus court chemin de à dans le graphe, en n'autorisant comme sommets intermédiaires (ni ni eux-mêmes, mais les sommets traversés en chemin) que ceux de l'ensemble .
Disjonction de cas sur le rôle de . Considérons le plus court chemin optimal de à n'utilisant que comme intermédiaires. Deux cas s'excluent mutuellement :
Cas 1 — ce chemin optimal n'utilise pas comme sommet intermédiaire. Alors il n'utilise en fait que , donc sa longueur est exactement (qui est déjà optimal pour cet ensemble plus restreint de sommets autorisés).
Cas 2 — ce chemin optimal utilise comme sommet intermédiaire (nécessairement une seule fois, car repasser deux fois par créerait un cycle, ce qui n'est jamais optimal avec des poids positifs ou en l'absence de cycle négatif). Le chemin se décompose alors en un trajet et un trajet , chacun n'utilisant comme intermédiaires que (puisque n'apparaît qu'une fois, à la jonction). Pour que le chemin total soit optimal, chacun de ces deux sous-trajets doit aussi être optimal (principe d'optimalité de Bellman) : leur longueur totale est donc .
Conclusion. Le plus court chemin global est le meilleur des deux cas :
Exercice 15
Comparer, pour un graphe dense (), la complexité de calculer les plus courts chemins entre toutes les paires de sommets via (a) exécutions de Dijkstra, contre (b) une seule exécution de Floyd-Warshall. Quelle méthode est préférable ?
Corrigé
Méthode (a) — exécutions de Dijkstra. Chaque exécution de Dijkstra depuis un sommet source coûte . En répétant pour chacun des sommets comme source :
Pour un graphe dense (), cela devient .
Méthode (b) — Floyd-Warshall. Complexité directe , indépendamment de la densité du graphe.
Comparaison. Pour un graphe dense, : Floyd-Warshall est asymptotiquement meilleur (et plus simple à implémenter, sans nécessiter de structure de tas).
Remarque complémentaire (cas creux). Pour un graphe creux (), la méthode (a) donnerait plutôt , ce qui est meilleur que de Floyd-Warshall. Le choix optimal entre les deux méthodes dépend donc de la densité du graphe : Floyd-Warshall pour les graphes denses, exécutions de Dijkstra pour les graphes creux.
AlphaMath Académie · Algorithmes de graphes : parcours et plus courts chemins · Informatique L3 — Algorithmique, structures de données et graphes