☀️ Stage Pré-rentrée · dès le 24 aoûtRéserver ma place →
Majorant
📘 Fiche de cours · 1re année📐 MPSI💻 Informatique Informatique communeNiveau · Sup

Plus court chemin : Dijkstra

Le plus court chemin pondéré expliqué pas à pas : distances provisoires, sommet finalisé, relâchement d'arête, la boucle de Dijkstra codée et tracée sur un graphe concret, la preuve de l'optimalité de la finalisation et le contre-exemple qui montre pourquoi les poids négatifs cassent tout — avec trois exercices corrigés.

Fiche rédigée par les mentors Majorant — alumni Polytechnique, CentraleSupélec et Mines Paris.

2 définitions1 théorèmes1 démos à savoirMis à jour le 2026-08-02

Vue d'ensemble

Tu sais déjà trouver un chemin dans un graphe avec un parcours en largeur (BFS). Mais le BFS compte le nombre d'arêtes : il suppose que toutes les arêtes « coûtent » pareil. Sur une carte routière, ce n'est pas le cas : aller de Paris à Lyon coûte plus que d'aller de Paris à Versailles. Il faut un algorithme qui tienne compte du poids de chaque arête. C'est exactement le rôle de l'algorithme de Dijkstra : trouver le plus court chemin dans un graphe pondéré à poids positifs.

Au programme (BO 2021, informatique commune). Graphes pondérés, problème du plus court chemin, algorithme de Dijkstra. On attend la compréhension du principe (distances provisoires, sommet finalisé, relâchement d'arête), une implémentation en Python et la connaissance de la restriction aux poids positifs. La version au tas de priorité est hors exigence mais peut être mentionnée.

Prérequis

  • Notion de graphe (sommets, arêtes), orienté ou non.
  • Parcours de graphes (BFS/DFS) et la notion de chemin.
  • Dictionnaires Python : d[cle], d.items(), test cle in d.
  • Ensembles Python (set) : ajout .add(), test d'appartenance.
🎯 Accompagnement Majorant

Dijkstra tombe presque chaque année aux écrits et aux oraux d'info. Le point qui coince : bien distinguer distance provisoire et distance définitive. Nos mentors alumni X · Centrale · Mines te font dérouler l'algo à la main jusqu'à ce que ce soit un réflexe.

Trouver un mentor →

1. Représenter un graphe pondéré en Python

Pour un graphe non pondéré, on utilise souvent un dictionnaire sommet → liste de voisins. Ici chaque arête porte un poids : on remplace la liste de voisins par un dictionnaire voisin → poids. On obtient un dictionnaire de dictionnaires.

Définition 1.1 — Graphe pondéré comme dict de dicts
Un graphe pondéré est représenté par g tel que g[u] est le dictionnaire des voisins de u avec leurs poids : g[u][v] est le poids de l'arête . L'accès à un poids et le parcours des voisins d'un sommet se font alors en temps proportionnel au nombre de voisins.
g = {
    "A": {"B": 2, "C": 5},
    "B": {"C": 1, "D": 4},
    "C": {"D": 2, "E": 7},
    "D": {"E": 1},
    "E": {},
}
🔍 Décryptage ligne par ligne
g = { ... }Un dictionnaire dont les clés sont les sommets "A" à "E".
"A": {"B": 2, "C": 5}Depuis A partent deux arêtes : A→B de poids 2 et A→C de poids 5. On lit le poids par g["A"]["B"], qui vaut 2.
"E": {}Un dictionnaire vide : E n'a aucun voisin sortant. C'est un cul-de-sac, pas un oubli — il faut le déclarer pour que "E" soit une clé du graphe.
📝 Orienté ou non. Ce codage est orienté : écrire g["A"]["B"]=2 ne crée pas automatiquement A←B. Pour un graphe non orienté, on met le poids dans les deux sens : g["A"]["B"]=g["B"]["A"]=2.

2. Le principe de Dijkstra

On fixe un sommet de départ et on veut la distance (poids total minimal d'un chemin) du départ vers tous les autres sommets. L'idée de Dijkstra est de faire grandir, étape par étape, un ensemble de sommets dont on connaît la distance définitive.

Définition 2.1 — Distance provisoire, sommet finalisé, relâchement
  • Distance provisoire dist[s] : le poids du meilleur chemin connu pour l'instant du départ vers s. On l'initialise à pour le départ et à (float('inf')) pour les autres.
  • Sommet finalisé : un sommet dont on a décidé que sa distance provisoire est désormais définitive.
  • Relâcher l'arête de poids : si dist[u] + w < dist[v], on a trouvé un chemin plus court vers v passant par u, donc on pose dist[v] = dist[u] + w.
📐 Méthode — La boucle de Dijkstra
  1. Initialiser dist[depart]=0, dist[s]=inf pour les autres, aucun sommet finalisé.
  2. Tant qu'il reste un sommet non finalisé atteignable (distance provisoire finie) : choisir le sommet non finalisé u de plus petite distance provisoire.
  3. Finaliser u (sa distance est définitive).
  4. Relâcher toutes les arêtes sortantes de u.
  5. Recommencer. Les sommets restés à sont inatteignables depuis le départ.
📝 Pourquoi choisir le plus petit ? Le sommet non finalisé de plus petite distance provisoire ne peut plus être amélioré : tout autre chemin vers lui passerait d'abord par un sommet non finalisé de distance au moins aussi grande, et comme les poids sont positifs ce détour ne peut pas raccourcir. C'est le cœur de la preuve du théorème 4.1.

3. L'algorithme en Python (version simple)

Voici la version dite « simple » : à chaque étape on cherche le minimum par un parcours de tous les sommets. Pas besoin de structure sophistiquée — un dictionnaire dist et un ensemble finalises suffisent.

import math

def dijkstra(g, depart):
    dist = {s: math.inf for s in g}      # distances provisoires
    dist[depart] = 0
    finalises = set()                    # sommets déjà finalisés
    while len(finalises) < len(g):
        # 1) choisir le non-finalisé de plus petite distance
        u = None
        meilleure = math.inf
        for s in g:
            if s not in finalises and dist[s] < meilleure:
                meilleure = dist[s]
                u = s
        if u is None:                    # les restants sont à l'infini
            break                        # -> inatteignables, on s'arrête
        # 2) finaliser u
        finalises.add(u)
        # 3) relâcher les arêtes sortantes de u
        for v, w in g[u].items():
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
    return dist
🔍 Décryptage ligne par ligne
dist = {s: math.inf for s in g}Compréhension de dictionnaire : chaque sommet reçoit la distance provisoire . math.inf est un flottant plus grand que tout nombre, donc dist[u]+w < inf est toujours vrai la première fois — pratique pour un premier relâchement.
dist[depart] = 0On est déjà arrivé au départ : distance nulle. C'est la seule valeur finie au démarrage.
finalises = set()Un ensemble, car on ne veut tester que l'appartenance (s not in finalises), en temps constant en moyenne.
while len(finalises) < len(g):On continue tant que tous les sommets ne sont pas finalisés : au plus tours de boucle.
for s in g: ... if s not in finalises and dist[s] < meilleureCœur de la version simple : on balaie tous les sommets pour trouver le non-finalisé de plus petite distance provisoire. C'est ce balayage qui coûte par tour.
if u is None: breakSi aucun sommet non finalisé n'a de distance finie, tous les restants sont inatteignables : inutile de continuer.
finalises.add(u)On finalise u : le théorème 4.1 garantit que dist[u] est désormais définitif.
for v, w in g[u].items():On parcourt les voisins v de u et le poids w de l'arête u→v.
if dist[u] + w < dist[v]: dist[v] = dist[u] + wLe relâchement : si passer par u raccourcit le chemin vers v, on met à jour la distance provisoire de v.
return distÀ la fin, dist[s] est la distance minimale du départ à s (ou inf si s est inatteignable).

Trace pas à pas

Déroulons dijkstra(g, "A") sur le graphe de la partie 1. À chaque étape : on choisit le sommet non finalisé de plus petite distance (colonne « finalisé »), on le fige, puis on relâche ses arêtes. Le tableau donne l'état de dist après relâchement.

Exécution de dijkstra(g, "A") — poids : A→B 2, A→C 5, B→C 1, B→D 4, C→D 2, C→E 7, D→E 1
ÉtapeFinalisédist[A]dist[B]dist[C]dist[D]dist[E]
init0
1A (0)025
2B (2)0236
3C (3)023510
4D (5)02356
5E (6)02 ✓3 ✓5 ✓6 ✓
💡 Lecture d'une étape. À l'étape 2 on finalise B (plus petite distance non finalisée : 2). On relâche B→C : dist[B]+1 = 3 < 5, donc dist[C] passe de 5 à 3. On relâche B→D : 2+4 = 6 < ∞, donc dist[D] = 6. Plus tard (étape 3), C→D donnera 3+2 = 5 < 6 : dist[D] descend à 5. Une distance provisoire peut donc être améliorée plusieurs fois avant d'être finalisée.
📝 Reconstituer le chemin. Pour connaître non seulement la distance mais le chemin, on ajoute un dictionnaire pred : à chaque relâchement réussi dist[v]=dist[u]+w, on note pred[v]=u. On remonte ensuite de l'arrivée au départ via pred (voir exercice 3).
🎯 Accompagnement Majorant

« Je comprends le code mais je me perds dans la trace. » C'est le message qu'on reçoit le plus. Un mentor alumni X · Centrale · Mines te fait remplir le tableau ligne par ligne sur trois ou quatre graphes : au bout du troisième, c'est acquis.

Trouver un mentor →

4. Pourquoi ça marche : la finalisation est définitive

Théorème 4.1 — Optimalité de la finalisation ★ À savoir démontrer
Dans un graphe à poids positifs ou nuls, au moment où Dijkstra finalise un sommet u, la valeur dist[u] est exactement la distance minimale du départ à u.
Démonstration (pour comprendre le rôle des poids positifs)

On raisonne au moment où l'on s'apprête à finaliser u, le sommet non finalisé de plus petite distance provisoire. Notons la vraie distance minimale du départ à u. On a toujours dist[u] , car dist[u] correspond à un chemin réel. Montrons l'inégalité inverse.

Considérons un plus court chemin du départ à u, de poids . Le départ est finalisé (c'est le premier finalisé, à distance 0) et u ne l'est pas encore ; en suivant , il existe donc une première arête x est finalisé et y ne l'est pas.

Comme x est finalisé, on a déjà relâché l'arête , donc dist[y] dist[x] (le préfixe de jusqu'à y est lui aussi optimal). Ainsi dist[y] .

Or y est non finalisé, donc par le choix de u comme minimum : dist[u] dist[y] . Enfin est sur le plus court chemin vers u, et comme tous les poids sont , la portion de de à a un poids , d'où .

En chaînant : dist[u] dist[u]. Toutes ces quantités sont égales, donc dist[u] .

Où servent les poids positifs ? À la dernière étape : n'est vrai que parce que la portion ne peut pas avoir un poids négatif. C'est précisément ce qui casse au paragraphe suivant.

5. Le piège : les poids négatifs

⚠ Dijkstra est FAUX si une arête a un poids négatif. La finalisation « fige » un sommet trop tôt : un chemin plus court, découvert ensuite via une arête négative, arrive quand la décision a déjà été prise et propagée aux voisins.

Un contre-exemple concret. Quatre sommets, une seule arête négative (D→B) :

g = {
    "A": {"B": 1, "D": 3},
    "B": {"C": 5},
    "C": {},
    "D": {"B": -3},
}
# Vrai plus court chemin de A vers B :
#   direct  A->B        = 1
#   détour  A->D->B    = 3 + (-3) = 0   <-- meilleur !
# donc vrai plus court chemin A->C = A->D->B->C = 0 + 5 = 5
🔍 Décryptage ligne par ligne
"A": {"B": 1, "D": 3}Deux façons de commencer : aller directement à B (poids 1) ou passer par D (poids 3).
"D": {"B": -3}L'arête D→B a un poids négatif. Le détour A→D→B coûte donc 3 − 3 = 0, moins que le chemin direct à 1.
"B": {"C": 5}On n'atteint C qu'en passant par B : la distance de C dépend entièrement de celle, correcte ou non, de B.

Déroulons dijkstra(g, "A"). On finalise A (0) et on relâche : dist[B]=1, dist[D]=3. Le plus petit non finalisé est B à 1 : Dijkstra finalise B à 1 et relâche aussitôt B→C, d'où dist[C]=1+5=6. Ensuite il finalise D (3) et relâche D→B : 3+(−3)=0 < 1, donc dist[B] tombe à 0 — mais B est déjà finalisé : son arête B→C ne sera jamais re-relâchée. Bilan : le programme renvoie dist[C]=6, alors que le vrai plus court chemin A→D→B→C vaut 5. Dijkstra a finalisé B trop tôt et s'en est servi pour calculer C avant de découvrir, via l'arête négative, le meilleur chemin vers B. La garantie du théorème 4.1 tombe.

📝 Que faire alors ? Pour des poids éventuellement négatifs (sans cycle négatif), on utilise l'algorithme de Bellman-Ford, qui relâche toutes les arêtes plusieurs fois et ne « fige » rien. Hors programme de première année, mais bon à connaître pour ne pas croire Dijkstra universel.

6. Application et coût

💡 Itinéraire routier. Les sommets sont des carrefours, les arêtes des tronçons de route, les poids des distances (ou des durées) — toujours positives. Dijkstra depuis ta position donne le plus court trajet vers toutes les destinations. C'est le noyau des calculateurs d'itinéraires (GPS, applications de navigation).

Complexité de la version simple. La boucle while tourne fois (une finalisation par tour). À chaque tour, la recherche du minimum balaie les sommets : coût . Les relâchements, cumulés sur toute l'exécution, parcourent chaque arête une fois : au total. Le terme dominant est la recherche du minimum :

📝 Faire mieux avec un tas. Si on stocke les sommets non finalisés dans une file de priorité (un tas, le module heapq en Python), extraire le minimum coûte au lieu de . La complexité tombe alors à , bien meilleure pour un graphe creux (peu d'arêtes). Cette variante est hors exigence en première année : retiens juste qu'elle existe et pourquoi elle accélère.
⚠ Ne confonds pas avec le BFS. Le BFS donne le plus court chemin en nombre d'arêtes (poids tous égaux à 1). Dès que les poids diffèrent, il faut Dijkstra. Inversement, sur un graphe non pondéré, lancer Dijkstra marche mais un simple BFS en suffit.

7. Exercices corrigés

Exo 1Lire un graphe pondéréFacile

Soit g = {"P": {"Q": 4, "R": 1}, "Q": {"R": 2}, "R": {"Q": 5}}. (a) Quel est le poids de l'arête P→R ? (b) Écris une expression Python qui vaut le poids de Q→R. (c) Le graphe est-il non orienté ?

Voir la correction détaillée
(a) g["P"]["R"] vaut 1.
(b) g["Q"]["R"], qui vaut 2.
(c) Non : Q→R a le poids 2 mais R→Q a le poids 5. Les deux sens diffèrent, et P n'a aucune arête entrante déclarée. Le graphe est orienté.
Exo 2Dérouler Dijkstra à la mainIntermédiaire

Soit g = {"S": {"A": 4, "B": 1}, "B": {"A": 2, "C": 5}, "A": {"C": 1}, "C": {}}. Déroule dijkstra(g, "S") : donne l'ordre de finalisation et les distances finales.

Voir la correction détaillée
Init : dist = {S:0, A:∞, B:∞, C:∞}.
Finalise S (0). Relâche S→A : A=4 ; S→B : B=1.
Plus petit non finalisé : B (1). Relâche B→A : 1+2=3 < 4 → A=3 ; B→C : C=6.
Plus petit non finalisé : A (3). Relâche A→C : 3+1=4 < 6 → C=4.
Reste C (4), finalisé, aucune arête.
Ordre : S, B, A, C. Distances finales : {S:0, A:3, B:1, C:4}. On vérifie : le meilleur chemin vers A est S→B→A (coût 3), pas S→A direct (coût 4).
Exo 3Reconstituer le cheminDifficile

Modifie dijkstra pour qu'il renvoie aussi un dictionnaire pred permettant de reconstituer le plus court chemin, puis écris une fonction chemin(pred, depart, arrivee) renvoyant la liste des sommets du départ à l'arrivée.

Voir la correction détaillée
On note le prédécesseur à chaque relâchement réussi :
import math

def dijkstra_pred(g, depart):
    dist = {s: math.inf for s in g}
    dist[depart] = 0
    pred = {depart: None}
    finalises = set()
    while len(finalises) < len(g):
        u, meilleure = None, math.inf
        for s in g:
            if s not in finalises and dist[s] < meilleure:
                meilleure, u = dist[s], s
        if u is None:
            break
        finalises.add(u)
        for v, w in g[u].items():
            if dist[u] + w < dist[v]:
                dist[v] = dist[u] + w
                pred[v] = u          # v est atteint au mieux via u
    return dist, pred

def chemin(pred, depart, arrivee):
    if arrivee not in pred:          # inatteignable
        return None
    ch = [arrivee]
    while ch[-1] != depart:
        ch.append(pred[ch[-1]])      # on remonte de prédécesseur en prédécesseur
    ch.reverse()                     # on avait la liste à l'envers
    return ch
pred[v]=u mémorise « le meilleur moyen d'atteindre v passe en dernier par u ». On part de l'arrivée et on remonte via pred jusqu'au départ, puis on retourne la liste.
Sur le graphe de l'exo 2, chemin(pred, "S", "A") renvoie ["S", "B", "A"].

Récap final — Ce qu'il faut absolument retenir

Dijkstra calcule le plus court chemin dans un graphe pondéré à poids positifs, en finalisant à chaque tour le sommet non finalisé de plus petite distance provisoire, puis en relâchant ses arêtes. Vérifie que tu maîtrises :

  • Sais-tu représenter un graphe pondéré par un dict de dict {voisin: poids} et y lire un poids ?
  • Sais-tu ce que valent les distances provisoires à l'initialisation (0 au départ, inf ailleurs) ?
  • Sais-tu ce que veut dire « finaliser » un sommet et « relâcher » une arête ?
  • Sais-tu que Dijkstra choisit à chaque tour le non-finalisé de plus petite distance provisoire ?
  • Sais-tu dérouler la trace sur un petit graphe et remplir le tableau des distances ?
  • Sais-tu pourquoi une distance provisoire peut être améliorée plusieurs fois avant d'être finalisée ?
  • Sais-tu que l'algorithme échoue avec un poids négatif, et exhiber un contre-exemple ?
  • Sais-tu que la version simple est en , et qu'un tas la ramène à ?

À savoir refaire

  • Optimalité de la finalisation — au moment où Dijkstra finalise , dist[u] est la distance minimale ; la preuve repose sur le choix du minimum et sur la positivité des poids.

Débloque la fiche complète

Théorèmes, démonstrations à savoir refaire, méthodes-types et pièges de concours : crée ton compte gratuit pour tout lire. Une seule fois pour toutes les fiches et ressources Majorant.

Gratuit · vos données restent confidentielles.

Valide tes acquis

Quiz — Dijkstra

11 questions · une à la fois · seuil de maîtrise 80 %.

Informatique commune · SupQuiz — Plus court chemin : DijkstraQuestion 1 / 11
FacileChoix unique1 pt

On veut coder un graphe orienté avec les arêtes X→Y de poids 3 et X→Z de poids 7. Quelle représentation Python est correcte selon la convention dict sommet → dict{voisin: poids} ?

Sélectionne une réponse pour valider.

Fiches associées

📐 MPSI·Informatique

Recherche par dichotomie

La recherche dichotomique dans un tableau trié, expliquée ligne par ligne : code Python commenté, exécution pas à pas, preuve de terminaison et de correction (variant \(d-g\), invariant de boucle), version récursive et complexité \(O(\log n)\).

📐 MPSI·Informatique

Variables, types et affectations

Les briques de base de Python pour bien démarrer la prépa : affectation, types int/float/bool/str, conversions, opérateurs (/ // %), f-strings — avec les pièges classiques et deux exercices corrigés.

📐 MPSI·Informatique

Conditions et booléens

Faire choisir un programme : booléens, comparaisons, if / elif / else, rôle de l'indentation, et combinaison de conditions avec and / or / not — avec pièges et exercices corrigés.

📐 MPSI·Informatique

Boucles for et while

Répéter une action : boucle for avec range, boucle while qui termine, et le motif de l'accumulateur (somme, compteur) — avec table de trace, pièges et exercices corrigés.

📐 MPSI·Informatique

Fonctions : paramètres et valeurs de retour

Définir et appeler une fonction, comprendre paramètres et valeur de retour, et surtout ne jamais confondre return (renvoie) et print (affiche) — avec pièges et exercices corrigés.

📐 MPSI·Informatique

Listes et chaînes de caractères

Créer, indexer, parcourir et découper des listes et des chaînes ; comprendre qu'une liste est modifiable et une chaîne non — avec les pièges d'indices et deux exercices corrigés.

Tu veux aller plus loin sur ce chapitre ?

Nos mentors alumni de Polytechnique, CentraleSupélec et Mines Paris t'accompagnent en cours particuliers — démonstrations détaillées, exos type concours, oraux blancs.

Trouver un mentor →