Plus courts chemins
Ce que ce chapitre apporte 6 points
- Distinguer plus court chemin en nombre d'arêtes et en poids total.
- Dérouler Dijkstra à la main et suivre le tableau des distances, l'algorithme même dont le routage OSPF se sert pour remplir une table de routage.
- Expliquer pourquoi Dijkstra exige des poids positifs, avec un contre-exemple.
- Employer Bellman-Ford et détecter un circuit de poids négatif.
- Reconstruire le chemin, pas seulement son poids.
- Choisir l'algorithme adapté à un problème donné.
Deux notions de « plus court »
Le chapitre sur les parcours a déjà résolu un cas : quand toutes les arêtes se valent, le BFS donne le plus court chemin en nombre d'arêtes. Dès que les arêtes portent des poids, cette réponse ne suffit plus.
Dans un graphe pondéré, le poids d'un chemin est la somme des poids de ses arêtes, à ne pas confondre avec sa longueur, qui est son nombre d'arêtes. Un plus court chemin de à est un chemin de à de poids minimal : on dit « plus court » par habitude, même quand le poids est un temps ou un coût.
Ce poids minimal est la distance de à , notée . Quand le départ est fixé, on écrit simplement .
Le chemin le plus court en nombre d'arêtes n'est donc pas le plus court en poids. Un BFS répondrait « une arête », ce qui est exact et sans intérêt pour qui cherche le trajet le moins cher.
Dijkstra
On maintient une distance provisoire pour chaque sommet, infinie au départ sauf pour la source, qui vaut 0. À chaque étape, on fige le sommet non figé de plus petite distance provisoire, puis on relâche ses arêtes : pour chaque voisin, si passer par lui fait mieux, on met à jour.
et l'on note au passage comme prédécesseur de : c'est lui qui permettra de reconstruire le chemin.
Dérouler la figure ci-dessous : à chaque pas, elle fige le sommet qui porte la plus petite distance provisoire, écrit en clair chaque relâchement, et ajoute une ligne au tableau des distances. Le tas y est dessiné aussi, avec ses entrées périmées, sur lesquelles le code reviendra. Les arêtes en gras forment l'arbre des plus courts chemins.
Plein : figé, avec son rang. Sous chaque sommet, sa distance provisoire. En gras : l'arbre des plus courts chemins ; en tirets : un prédécesseur encore provisoire.
- (0, A)
| on fige | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| Aucune ligne encore : dérouler pour figer la source. | ||||||
A-C et le porter à 6 : B est alors figé directement depuis A, à 4, et l'arbre change. Cliquer enfin F pour en faire la source : l'arbre se refait autour de lui.
Les distances finales sont , , , , , . Remarquer : la valeur 4 trouvée au premier tour a été améliorée en 3 quand on est passé par C. C'est tout le sens du relâchement.
À la main, on tient un tableau d'une ligne par sommet figé. Chaque case donne la distance provisoire après les relâchements de cette ligne et, entre parenthèses, le prédécesseur qui l'a fournie ; la valeur en gras est celle qu'on fige. Une case laissée vide désigne un sommet déjà figé, qui ne bouge plus.
| on fige | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| A | 0 | 4 (A) | 2 (A) | |||
| C | 3 (C) | 2 (A) | 10 (C) | 12 (C) | ||
| B | 3 (C) | 8 (B) | 12 (C) | |||
| D | 8 (B) | 10 (D) | 14 (D) | |||
| E | 10 (D) | 13 (E) | ||||
| F | 13 (E) |
Pour obtenir le chemin de A à F, on remonte les prédécesseurs : F vient de E, E de D, D de B, B de C, C de A. Le chemin est A, C, B, D, E, F, de poids 13.
Pourquoi figer le plus petit est correct. Dijkstra est un algorithme glouton, au sens du chapitre précédent : à chaque étape, il fait le choix qui paraît le meilleur, figer le sommet le plus proche, et ne revient jamais sur un sommet figé. Ce glouton-là est exact, à une condition.
Quand on choisit le sommet non figé de plus petite distance provisoire, aucun chemin ne pourra faire mieux ensuite : tout autre trajet passerait par un sommet non figé, donc de distance déjà supérieure, et ne ferait qu'ajouter du poids.
Cet argument suppose que les poids sont positifs. C'est exactement là que tout se joue.
Ce qui casse avec un poids négatif
Plein : figé, avec son rang. Sous chaque sommet, sa distance provisoire. En gras : l'arbre des plus courts chemins ; en tirets : un prédécesseur encore provisoire.
- (0, A)
| on fige | A | B | C | D |
|---|---|---|---|---|
| Aucune ligne encore : dérouler pour figer la source. | ||||
Un poids est négatif : Dijkstra n'est plus garanti. Le dérouler jusqu'au bout, et la figure comparera sa réponse à celle de Bellman-Ford.
C, et le récit dit ce que l'arc C → B aurait donné si B n'avait pas été figé. Une fois au bout, la figure compare sa réponse à celle de Bellman-Ford et désigne le premier sommet faux. Porter ensuite le poids de C → B à −2 : Dijkstra tombe juste, par chance, et rien dans sa réponse ne le distingue du cas précédent. Basculer enfin sur Bellman-Ford : il trouve les bonnes distances en trois passes et un contrôle, celles du tableau de la section suivante.
Dijkstra fige A, puis B à la distance 2, parce que 2 est la plus petite valeur provisoire, puis D à . Il fige enfin C à 5, relâche l'arc C -> B et découvre , meilleur que 2. Mais B est déjà figé, et D aussi, calculé à partir de lui : l'algorithme ne revient pas en arrière. Il annonce 2 pour B et 3 pour D, alors que les vraies distances sont 1, par A, C, B, et 2, par A, C, B, D. La réponse est fausse.
Une implémentation qui oublierait de protéger les sommets figés corrigerait ici après coup, mais pas ce qui a déjà été calculé à partir de B : elle annoncerait toujours 3 pour D, et ses résultats ne seraient même plus cohérents entre eux. C'est pourquoi le code plus bas ne relâche jamais un arc vers un sommet figé.
Dijkstra sur des poids négatifs ne plante pas, ne signale rien, et peut rendre un résultat faux, ou juste par chance sur l'exemple qu'on a essayé : avec les arcs A → B de poids , A → C de poids 3 et B → C de poids 1, il trouve les bonnes distances. C'est la pire des situations, et c'est pourquoi la question « les poids sont-ils tous positifs ? » doit être posée avant d'écrire la moindre ligne.
Dijkstra sur un graphe contenant un arc de poids négatif…
Et si un circuit a un poids total négatif, la question elle-même perd son sens : on peut le parcourir indéfiniment pour faire baisser le coût, et il n'existe pas de plus court chemin.
Ce qui rend le problème insoluble, c'est un circuit dont la somme des poids est négative, et lui seul. La distance de à n'est alors pas « très petite » : elle n'existe pas, puisqu'aucun minimum n'est atteint.
Un circuit négatif n'empoisonne d'ailleurs pas tout le graphe : seuls les sommets qu'il peut atteindre perdent leur distance. Un sommet situé en amont, ou dans une autre composante, garde la sienne.
Bellman-Ford
Relâcher toutes les arêtes, fois de suite. Un plus court chemin ayant au plus arêtes, cela suffit pour que toutes les distances soient correctes.
Une passe supplémentaire qui améliore encore une distance prouve l'existence d'un circuit de poids négatif.
Bellman-Ford est plus lent que Dijkstra, en contre , mais il accepte les poids négatifs et sait dire quand le problème n'a pas de solution.
Sur le graphe du contre-exemple, depuis la source , avec les arcs relâchés dans l'ordre , puis , puis , puis . Cet ordre est volontairement le plus défavorable. Avec sommets, il faut au plus passes, plus une de contrôle.
| après | ce qui s'est passé | ||||
|---|---|---|---|---|---|
| initialisation | |||||
| passe 1 | et ne servent à rien, et sont encore à | ||||
| passe 2 | donne ; trouve enfin | ||||
| passe 3 | repart de la nouvelle valeur de : | ||||
| passe 4 (contrôle) | rien ne bouge : les distances sont définitives |
Deux enseignements. D'abord, l'arc n'a servi qu'à la deuxième passe, parce qu'à la première était encore infinie, et la bonne valeur de n'arrive qu'à la troisième, parce qu'elle attend celle de : chaque passe propage l'information d'un arc de plus le long des chemins. C'est exactement la raison d'être des passes répétées, et c'est pourquoi l'algorithme ne cherche pas à ordonner les arcs intelligemment. Il n'en a pas besoin.
Ensuite, la quatrième passe ne change rien, et c'est elle qui prouve l'absence de circuit négatif. Elle n'est pas facultative : c'est le contrôle, et il ne coûte qu'un parcours de plus des arcs.
Pourquoi passes suffisent. Un plus court chemin ne repasse jamais par un sommet, sinon il contiendrait un circuit de poids positif ou nul qu'on pourrait retirer. Il a donc au plus arcs.
Or chaque passe garantit que tous les plus courts chemins d'un arc de plus sont désormais corrects : après la passe , toutes les distances atteintes en au plus arcs sont exactes. Il suffit donc de passes dans le pire des cas, et souvent de moins.
Et si une -ième passe améliore encore quelque chose, c'est qu'un chemin de arcs bat tous ceux de : il repasse forcément par un sommet, donc il contient un circuit, et ce circuit est nécessairement de poids négatif.
La figure suivante reprend le graphe au circuit négatif et y déroule Bellman-Ford, une passe par pas, dans la forme du tableau précédent.
Ce graphe a un circuit : aucune disposition en couches ne fait aller tous les arcs dans le même sens, et un arc revient en arrière.
Sous chaque sommet, sa distance après la passe ; cerclé : modifiée pendant cette passe. Trait épais : un arc qui vient de faire baisser une distance ; en tirets : les prédécesseurs, qui ne forment l'arbre des plus courts chemins, en gras, qu'à la fin.
Ordre des arcs à chaque passe : S → X, X → Y, Y → Z, Z → X, Z → T
| après | S | X | Y | Z | T |
|---|---|---|---|---|---|
| initialisation | 0 | ∞ | ∞ | ∞ | ∞ |
X, Y, Z et T baissent de 1 à chaque passe, parce que chacune fait faire au circuit un tour de plus. La cinquième, celle de contrôle, les fait encore baisser : la figure remonte les prédécesseurs, trouve le circuit X → Y → Z → X, de poids −1, et dit quels sommets n'ont pas de plus court chemin. Porter ensuite le poids de X → Y à 4 : le circuit pèse 0, et la deuxième passe ne change déjà plus rien. Basculer enfin sur Dijkstra : il annonce des nombres, sans rien signaler.
Voici les deux algorithmes en Python. Dijkstra range ses distances provisoires dans la file de priorité du chapitre précédent : le tas heapq lui rend à chaque tour le sommet non figé de plus petite distance provisoire, celui qu'il faut figer.
if u in fige.C'est plus simple et plus rapide que de chercher puis modifier l'entrée existante, au prix d'un tas légèrement plus gros. C'est l'implémentation standard en Python, où
heapq n'offre pas de diminution de clé.
1.Des chemins mènent de la source à la cible, et pourtant aucun n'est le plus court. Pourquoi ?
2.Toutes les arêtes valent 1. Que vaut-il mieux utiliser ?
Quel algorithme pour quel problème
| Situation | Algorithme | Coût |
|---|---|---|
| Toutes les arêtes de même poids | BFS | |
| Poids positifs, une source | Dijkstra | |
| Poids négatifs possibles, une source | Bellman-Ford | |
| Toutes les paires de sommets | Floyd-Warshall | |
| Poids positifs, cible connue, estimation disponible | A* | dépend de l'heuristique |
| Graphe orienté sans circuit, poids quelconques | relâchement dans l'ordre topologique (chapitre suivant) |
Floyd-Warshall calcule d'un coup les distances entre toutes les paires de sommets, en essayant chaque sommet comme étape intermédiaire. A* est un Dijkstra guidé vers une cible par une estimation de la distance restante, décrit dans les exercices type plus bas. Sur un graphe orienté sans circuit enfin, relâcher chaque arc une seule fois, dans l'ordre du tri topologique du chapitre suivant, donne toutes les distances, même avec des poids négatifs.
Prim compare le poids de l'arête :
poids(u, v).Dijkstra compare la distance cumulée depuis la source :
dist(u) + poids(u, v).Se tromper d'expression donne un programme qui tourne, ne signale rien, et répond à l'autre question.
Mettre en pratique
Pourquoi Dijkstra peut-il se tromper sur un poids négatif ?
Parce que sa correction repose sur un argument précis : quand on fige le sommet de plus petite distance provisoire, aucun chemin ultérieur ne peut faire mieux, puisque tout autre trajet passerait par un sommet de distance déjà supérieure et ne ferait qu'ajouter du poids.
Avec un poids négatif, ajouter une arête peut diminuer le coût. L'argument tombe, et un sommet figé trop tôt garde une valeur fausse.
Le pire est que l'algorithme ne le signale pas : il peut rendre un résultat plausible et faux, et le même code, essayé sur un autre graphe à arc négatif, peut tomber juste par chance.
Des chemins existent de la source à la cible, mais aucun n'est le plus court. Que se passe-t-il ?
Il existe un circuit de poids négatif accessible depuis la source et menant à la cible.
En tournant sur ce circuit, on fait baisser le coût autant qu'on veut : il n'existe pas de minimum, donc pas de plus court chemin.
C'est précisément ce que détecte la passe supplémentaire de Bellman-Ford : si une distance s'améliore encore après passes, un tel circuit existe.
Combien de passes fait Bellman-Ford, et pourquoi ce nombre ?
passes, chacune relâchant toutes les arêtes.
Un plus court chemin élémentaire ne peut pas contenir plus de arêtes, puisqu'il ne répète aucun sommet. Après passes, toutes les distances atteignables par un chemin d'au plus arêtes sont correctes.
En pratique on s'arrête dès qu'une passe ne change plus rien, ce qui arrive souvent bien avant.
Comment obtenir le chemin et pas seulement son poids ?
En mémorisant, pour chaque sommet, le prédécesseur qui a permis la dernière amélioration de sa distance.
À la fin, on part de la cible et on remonte de prédécesseur en prédécesseur jusqu'à la source, puis on retourne la liste obtenue.
Ce tableau ne coûte rien en temps ni en mémoire significative, et c'est exactement le même mécanisme que pour le BFS.
Quand utiliser A\* plutôt que Dijkstra ?
Quand on cherche le chemin vers une cible précise et qu'on dispose d'une estimation de la distance restante, par exemple la distance à vol d'oiseau sur une carte.
Dijkstra explore dans toutes les directions à la fois. A* oriente l'exploration vers la cible en ajoutant cette estimation à la priorité, ce qui réduit beaucoup le nombre de sommets visités.
La condition à respecter : l'estimation ne doit jamais surestimer la distance restante, sans quoi A* peut renvoyer un chemin non optimal. Et pour ne jamais revenir sur un sommet déjà traité, comme le fait Dijkstra, elle ne doit pas non plus baisser de plus que le poids de l'arc parcouru. La distance à vol d'oiseau remplit les deux conditions.
Sur un graphe où toutes les arêtes valent 1, faut-il Dijkstra ?
Non, un simple BFS suffit et il est plus rapide.
Avec des poids identiques, l'ordre de traitement par distance croissante est exactement l'ordre de découverte du BFS : la file de priorité n'apporte rien et coûte un facteur logarithmique.
C'est un réflexe utile : avant de sortir Dijkstra, vérifier si les poids sont vraiment différents.
Chapitre révisé en octobre 2026 · Une erreur dans ce chapitre ?
Dans la bibliothèque d'exercices
Relâcher une arête, et ce qui fait démarrer le calcul.
- Relâcher une arêteNiveau 2
- Compléter le calcul des plus courts cheminsNiveau 3
- Débogage : un algorithme qui ne démarre jamaisNiveau 2
- Dijkstra à la main sur un réseau routierNiveau 3
- Afficher le trajet, pas seulement la duréeNiveau 2
- Quand Dijkstra se trompeNiveau 3