
Table des Matières
Qu'est-ce que l'Algorithme de Dijkstra ?
L'algorithme de Dijkstra, publié par Edsger W. Dijkstra en 1959, trouve le plus court chemin depuis une seule source vers tous les autres nœuds d'un graphe pondéré. Il fonctionne sur les graphes orientés comme non orientés, avec une condition ferme : chaque poids d'arête doit être non négatif.
Voyez le graphe comme un réseau routier. Les nœuds sont des carrefours, les arêtes des routes, et chaque poids est le temps ou la distance pour parcourir cette route. L'algorithme de Dijkstra répond à la question que pose toute application de navigation : quel est l'itinéraire le plus rapide d'où je suis vers partout ailleurs ? C'est un élément de la famille plus large traitée dans les algorithmes de plus courts chemins, et un incontournable de la feuille de route de la théorie des graphes.
L'Idée Centrale
L'algorithme de Dijkstra est glouton. Il conserve une distance la plus courte provisoire vers chaque nœud et répète un geste simple :
Visitez toujours le nœud non visité ayant la plus petite distance connue, puis servez-vous-en pour améliorer ses voisins.
L'intuition qui rend cela correct : comme tous les poids sont non négatifs, dès que vous choisissez le nœud non visité le plus proche, aucun chemin futur ne pourra l'atteindre à moindre coût. Ainsi, à l'instant où un nœud est choisi, sa distance est définitive. Cette seule garantie constitue tout l'algorithme.
Concrètement, l'algorithme entretient :
- Une distance vers chaque nœud, toutes commençant à l'infini sauf la source, qui vaut
0. - Une file de priorité (tas-min) qui rend toujours le nœud non visité le plus proche.
- Un ensemble de nœuds définitifs dont la distance la plus courte est arrêtée.
Améliorer un voisin s'appelle la relaxation : si le chemin passant par le nœud courant est plus court que la distance enregistrée du voisin, vous l'abaissez.
Comment ça Marche, Pas à Pas
Exécutons Dijkstra depuis le nœud A sur ce graphe pondéré. Les arêtes bleues formeront l'arbre final des plus courts chemins, et le nombre à côté de chaque nœud est sa distance la plus courte finale depuis A.
Voici le déroulé. À chaque étape, nous figeons le nœud non visité le plus proche (en gras) et relaxons ses voisins. ∞ signifie "pas encore atteint".
| Visite | A | B | C | D | E | F |
|---|---|---|---|---|---|---|
| Départ | 0 | ∞ | ∞ | ∞ | ∞ | ∞ |
| A (0) | 0 | 4 | 2 | ∞ | ∞ | ∞ |
| C (2) | 0 | 3 | 2 | 10 | ∞ | ∞ |
| B (3) | 0 | 3 | 2 | 8 | ∞ | ∞ |
| D (8) | 0 | 3 | 2 | 8 | 10 | 14 |
| E (10) | 0 | 3 | 2 | 8 | 10 | 13 |
| F (13) | 0 | 3 | 2 | 8 | 10 | 13 |
Remarquez l'étape trois : visiter C a abaissé B de 4 à 3, car la route A → C → B (2 + 1) bat l'arête directe A → B (4). C'est la relaxation à l'œuvre. Voir cela se dérouler sur un graphe en direct rend le schéma immédiat, ce que vous pouvez faire dans le visualiseur d'algorithmes.
Implémentation en Python
La version propre et prête pour l'entretien utilise heapq de Python comme file de priorité. Le graphe est une liste d'adjacence associant chaque nœud à une liste de paires (voisin, poids).
import heapq
def dijkstra(graph, start):
# Chaque nœud commence infiniment loin, sauf la source.
distances = {node: float('inf') for node in graph}
distances[start] = 0
pq = [(0, start)] # (distance jusqu'ici, nœud)
while pq:
dist, node = heapq.heappop(pq)
# Une entrée périmée et plus longue pour un nœud déjà réglé : on l'ignore.
if dist > distances[node]:
continue
for neighbour, weight in graph[node]:
new_dist = dist + weight
# Relaxation : on a trouvé un chemin moins cher vers le voisin.
if new_dist < distances[neighbour]:
distances[neighbour] = new_dist
heapq.heappush(pq, (new_dist, neighbour))
return distances
Deux détails comptent. D'abord, nous insérons une nouvelle entrée au lieu de mettre à jour le tas sur place, puis nous ignorons les entrées périmées grâce au test dist > distances[node]. Cette "suppression paresseuse" garde le code simple et c'est une pratique courante. Ensuite, l'algorithme calcule naturellement les distances vers tous les nœuds ; pour vous arrêter tôt sur une seule cible, retournez dès que vous la retirez.
Complexité en Temps et en Espace
Le coût dépend de la file de priorité. Chaque arête peut déclencher au plus une insertion, et chaque insertion ou retrait sur un tas binaire coûte O(log V).
| File de priorité | Temps | Idéal quand |
|---|---|---|
| Tas binaire | O((V + E) log V) | Le choix habituel, graphes creux |
| Tas de Fibonacci | O(E + V log V) | Graphes denses, optimum théorique |
| Tableau simple | O(V²) | Graphes très denses |
L'espace est O(V) pour la table des distances plus la file. Pour le raisonnement derrière ces bornes et leur comparaison sur l'ensemble des algorithmes de graphes, voir le guide de la complexité des algorithmes de graphes et l'aide-mémoire d'une page.
Quand Dijkstra échoue : les Poids Négatifs
La garantie gloutonne repose entièrement sur des poids non négatifs. Ajoutez une arête négative et tout peut casser.
Supposons que l'algorithme fige un nœud parce qu'il semble le plus proche, distance 5. Plus tard, il découvre une route d'apparence plus longue qui passe par une arête -4 et atteint en fait ce nœud en 3. Trop tard : Dijkstra a déjà déclaré 5 définitif et est passé à autre chose. La réponse est fausse.
Règle à retenir : poids non négatifs, utilisez Dijkstra. Le moindre poids négatif, utilisez Bellman-Ford, qui relâche chaque arête à répétition et peut aussi détecter les cycles négatifs.
Dijkstra face aux Autres Algorithmes
Dijkstra n'est qu'un outil parmi d'autres. Choisir le bon dépend du graphe.
| Algorithme | Poids négatifs ? | Idéal pour | Temps |
|---|---|---|---|
| BFS | Non pondéré seulement | Plus court chemin non pondéré | O(V + E) |
| Dijkstra | Non | Poids non négatifs | O((V + E) log V) |
| Bellman-Ford | Oui | Poids négatifs, détection de cycle | O(V · E) |
| A* | Non | Une cible, avec une heuristique | O(E) typique |
Le parent le plus proche est la recherche A*, c'est-à-dire Dijkstra plus une heuristique qui oriente la recherche vers un but unique. Et Dijkstra lui-même n'est en réalité que le parcours en largeur, une simple file étant remplacée par une file de priorité.
Applications Concrètes
- Navigation et cartes : l'itinéraire le plus court ou le plus rapide entre deux lieux, l'usage classique.
- Routage réseau : les protocoles à état de liens comme OSPF utilisent Dijkstra pour calculer les tables de transfert.
- Jeux et robotique : les coûts de déplacement sur une carte, souvent avec A* par-dessus.
- Opérations et logistique : les chemins de moindre coût dans les réseaux d'approvisionnement, de télécoms et de transport, un pilier de la recherche opérationnelle.
Regardez Dijkstra choisir son prochain nœud
La file de priorité devient limpide dès que vous la voyez tirer le nœud le moins cher, encore et encore. Exécutez Dijkstra sur un graphe en direct, pas à pas.
Ouvrir le visualiseur d'algorithmesFoire Aux Questions
Que fait l'algorithme de Dijkstra ?
L'algorithme de Dijkstra trouve le plus court chemin d'un unique nœud source vers tous les autres nœuds d'un graphe pondéré, tant que tous les poids d'arêtes sont non négatifs. C'est la méthode standard derrière le routage, la cartographie et les protocoles réseau.
Quelle est la complexité temporelle de l'algorithme de Dijkstra ?
Avec un tas binaire comme file de priorité, l'algorithme de Dijkstra s'exécute en temps O((V + E) log V) et en espace O(V). Avec un tas de Fibonacci, il s'améliore en O(E + V log V), et avec un simple tableau il est en O(V au carré), ce qui est plus rapide sur les graphes denses.
Pourquoi l'algorithme de Dijkstra ne fonctionne-t-il pas avec des poids négatifs ?
Dijkstra fige chaque nœud dès qu'il est retiré de la file de priorité, en supposant qu'aucun chemin moins cher ne peut apparaître ensuite. Une arête négative brise cette hypothèse, car une route plus longue pourrait tout de même abaisser le coût total. Utilisez Bellman-Ford pour les graphes à poids négatifs.
L'algorithme de Dijkstra est-il identique au BFS ?
Dijkstra généralise le parcours en largeur. Le BFS utilise une simple file et trouve le plus court chemin dans les graphes non pondérés. Dijkstra remplace la file par une file de priorité minimale, de sorte qu'il étend toujours le nœud non visité le plus proche et gère les arêtes pondérées.