Algorithme de Dijkstra

En théorie des graphes, l'algorithme de Dijkstra (prononcé ) sert à résoudre le problème du plus court chemin. Il permet, par exemple, de déterminer un plus court chemin pour se rendre d'une ville à une autre connaissant le réseau routier d'une région. Plus précisément, il calcule des plus courts chemins à partir d'une source vers tous les autres sommets dans un graphe orienté pondéré par des réels positifs. On peut aussi l'utiliser pour calculer un plus court chemin entre un sommet de départ et un sommet d'arrivée.

L'algorithme porte le nom de son inventeur, l'informaticien néerlandais Edsger Dijkstra, et a été publié en 1959.

Cet algorithme est de complexité polynomiale. Plus précisément, pour  sommets et  arcs, le temps est en , voire en .

Problème du plus court chemin 
L'algorithme de Dijkstra permet de résoudre un problème algorithmique : le problème du plus court chemin. Ce problème a plusieurs variantes. La plus simple est la suivante : étant donné un graphe non-orienté, dont les arêtes sont munies de poids, et deux sommets de ce graphe, trouver un chemin entre les deux sommets dans le graphe, de poids minimum. L'algorithme de Dijkstra permet de résoudre un problème plus général : le graphe peut être orienté, et l'on peut désigner un unique sommet, et demander d'avoir la liste des plus courts chemins pour tous les autres nœuds du graphe.

Principe sur un exemple 
L'algorithme prend en entrée un graphe orienté pondéré par des réels positifs et un sommet source. Il s'agit de construire progressivement un sous-graphe dans lequel sont classés les différents sommets par ordre croissant de leur distance minimale au sommet de départ. La distance correspond à la somme des poids des arcs empruntés.

Au départ, on considère que les distances de chaque sommet au sommet de départ sont infinies, sauf pour le sommet de départ pour lequel la distance est nulle. Le sous-graphe de départ est l'ensemble vide.

Au cours de chaque itération, on choisit en dehors du sous-graphe un sommet de distance minimale et on l'ajoute au sous-graphe. Ensuite, on met à jour les distances des sommets voisins de celui ajouté. La mise à jour s'opère comme suit : la nouvelle distance du sommet voisin est le minimum entre la distance existante et celle obtenue en ajoutant le poids de l'arc entre sommet voisin et  sommet ajouté à la distance  du sommet ajouté.

On continue ainsi jusqu'à épuisement des sommets (ou jusqu'à sélection du sommet d'arrivée).

Distance entre la ville A et la ville J 

L'algorithme de Dijkstra fonctionne aussi sur un graphe non orienté. L'exemple ci-contre montre les étapes successives dans la résolution du chemin le plus court dans un graphe. Les nœuds symbolisent des villes identifiées par une lettre et les arêtes indiquent la distance entre ces villes. On cherche à déterminer le plus court trajet pour aller de la ville A à la ville J.

En neuf étapes, on peut déterminer le chemin le plus court menant de A à J, il passe par C et H et mesure .

Présentation sous forme de tableau 

On peut aussi résumer l'exécution de l'algorithme de Dijkstra avec un tableau. Chaque étape correspond à une ligne. Une ligne donne les distances courantes des sommets depuis le sommet de départ. Une colonne donne l'évolution des distances d'un sommet donné depuis le sommet de départ au cours de l'algorithme. La distance d'un sommet choisi (car minimale) est soulignée. Les distances mises à jour sont barrées si elles sont supérieures à des distances déjà calculées.

Le tableau donne non seulement la distance minimale de la ville A à la ville J (487) mais aussi le chemin à suivre à rebours (J - H - C - A) pour aller de A à J ainsi que toutes les distances minimales de la ville A aux autres villes rangées par ordre croissant.

Schéma de l'algorithme 

Le graphe est noté  où :
 l'ensemble  est l'ensemble fini des sommets du graphe  ;
 l'ensemble  est l'ensemble des arcs de  tel que : si  est dans , alors il existe un arc depuis le nœud  vers le nœud  ;
 on définit la fonction  définie sur  dans  qui à un couple  associe le poids positif  de l'arc reliant  à  (et  s'il n'y a pas d'arc reliant  à ).
Le poids du chemin entre deux sommets est la somme des poids des arcs qui le composent. Pour une paire donnée de sommets  (le sommet du départ)  (le sommet d'arrivée) appartenant à , l'algorithme trouve un chemin depuis  vers  de moindre poids (autrement dit un chemin le plus léger ou encore le plus court).

L'algorithme fonctionne en construisant un sous-graphe  de manière que la distance entre un sommet  de   depuis  soit connue et soit un minimum dans . Initialement,  contient simplement le nœud  isolé, et la distance de  à lui-même vaut zéro. Des arcs sont ajoutés à  à chaque étape :

1. en identifiant  tous les arcs  dans ;
2. en choisissant l'arc  dans  qui donne la distance minimum depuis  à  en passant tous les chemins créés menant à ce nœud.

L'algorithme se termine soit quand  devient un arbre couvrant de , soit quand tous les nœuds d'intérêt sont dans .

On peut donc écrire l'algorithme de la façon suivante :

 Entrées :  un graphe avec une pondération positive  des arcs,  un sommet de 
 
 
  pour chaque sommet 
 
 Tant qu'il existe un sommet hors de 
     Choisir un sommet  hors de  de plus petite distance 
     Mettre  dans  
     Pour chaque sommet  hors de   voisin de 
        Si d[b] > d[a] + poids(a, b)
            
            prédécesseur[b] := a
     Fin Pour
 Fin Tant Que

Implémentation de l'algorithme

Fonctions annexes 
L'algorithme utilise les fonctions annexes suivantes.

Initialisation de l'algorithme 
 Initialisation(G,sdeb)
 1 pour chaque point s de G faire
 2    d[s] := infini             /* on initialise les sommets autres que sdeb à infini */
 3 fin pour
 4 d[sdeb] := 0                  /* la distance au sommet de départ sdeb est nulle */

Recherche d'un nœud de distance minimale 
 On recherche un nœud de distance minimale (relié par l'arc de poids le plus faible) de  parmi les nœuds situés hors de  . Le complémentaire de  est noté . On implémente pour cela une fonction Trouve_min(Q) qui choisit un nœud de Q de distance minimale.
 Trouve_min(Q)
 1 mini := infini
 2 sommet := -1
 3 pour chaque sommet s de Q
 4    si d[s] < mini
 5    alors 
 6        mini := d[s]
 7        sommet := s
 8 renvoyer sommet

Mise à jour des distances 
 On met à jour les distances entre  et  en se posant la question : vaut-il mieux passer par  ou pas ?
 maj_distances(s1,s2)
 1 si d[s2] > d[s1] + Poids(s1,s2)      /* Si la distance de sdeb à s2 est plus grande que */
 2                                      /* celle de sdeb à S1 plus celle de S1 à S2 */
 3    alors 
 4        d[s2] := d[s1] + Poids(s1,s2) /* On prend ce nouveau chemin qui est plus court */
 5        prédécesseur[s2] := s1        /* En notant par où on passe */

Fonction principale 

Voici la fonction principale utilisant les précédentes fonctions annexes :

 Dijkstra(G,Poids,sdeb)
 1 Initialisation(G,sdeb)
 2 Q := ensemble de tous les nœuds
 3 tant que Q n'est pas un ensemble vide faire
 4       s1 := Trouve_min(Q)
 5       Q := Q privé de s1
 6       pour chaque nœud s2 voisin de s1 faire
 7           maj_distances(s1,s2)
 8       fin pour
 9 fin tant que

Le plus court chemin de  à  peut ensuite se calculer itérativement selon l'algorithme suivant, avec  la liste représentant le plus court chemin de  à :
 1 A := suite vide
 2 s := sfin
 3 tant que s != sdeb faire
 4   A := cons(s, A)            /* on ajoute s en tête de la liste A */
 5   s := prédécesseur[s]       /* on continue de suivre le chemin */
 6 fin tant que
 7 A := cons(sdeb, A)           /* on ajoute le nœud de départ */

Attention : s'il n'y a pas de chemin de  à  cette partie de l'algorithme fait une boucle infinie ou une erreur selon votre implémentation.

Spécialisation de l'algorithme 

Il est possible de spécialiser l'algorithme en arrêtant la recherche lorsque l'égalité  est vérifiée, dans le cas où on ne cherche que la distance minimale entre  et .

Complexité de l'algorithme 
L'efficacité de l'algorithme de Dijkstra repose sur une mise en œuvre efficace de Trouve_min. L'ensemble  est implémenté par une file à priorités. Si le graphe possède  arcs et  nœuds, qu'il est représenté par des listes d'adjacence et si on implémente la file à priorités par un tas binaire (en supposant que les comparaisons des poids d'arcs soient en temps constant), alors la complexité en temps de l'algorithme est . En revanche, si on implémente la file à priorités avec un tas de Fibonacci, l'algorithme est en .

Correction de l'algorithme 
La démonstration de correction est une récurrence sur Card(P) (qui augmente de 1 à chaque itération) et repose sur l'invariant suivant :

où :
 mini(c) est le poids d'un plus court chemin menant à c ;
 miniP(c) est le poids d'un plus court chemin dont tous les sommets intermédiaires sont dans P menant à c.

Si n = Card(P), la preuve est la suivante :
 pour n = 0 et 1 : immédiat ;
 pour n un entier non nul, supposons l'invariant vrai.

L'algorithme sélectionne un pivot  tel que , et l'ajoute dans P. Il faut donc montrer que  après modification de P:

Par hypothèse,  avant modification de P, donc s'il existe un chemin C tel que  alors ce chemin contient au moins un sommet b  a tel que .

Soit donc un tel sommet b tel que tous ses prédécesseurs dans C soient dans P (il existe car le premier sommet de C est l'origine qui est dans P). Décomposons C en Cb- et Cb+ où Cb- est la première partie de C dont le dernier sommet est b, et Cb+ la suite de C. Alors  : contradiction.

Il n'existe donc aucun chemin C tel que  d'où . L'égalité est toujours vraie pour les autres éléments de P.

Enfin, l'algorithme met à jour la fonction d (et prédécesseur) pour les successeurs b du pivot a : .

Montrons qu'alors,  :

 si c n'est pas un successeur du pivot a, alors il n'existe aucun nouveau chemin C menant à c tel que  et dont tous les sommets intermédiaires sont dans P après l'ajout de a dans P puisqu'il faudrait alors passer par a avant de passer par un autre sommet , mais ce ne serait pas le plus court chemin jusqu'à d puisque le poids du plus court chemin jusqu'à d a déjà été calculé avant l'ajout de a dans P par hypothèse, et ce chemin ne contient donc que des sommets ayant été dans P avant l'ajout de a dans P ;

 sinon, il existe de tels chemins, en notant C le meilleur d'entre eux, alors C sera un des nouveaux chemins engendrés par l'ingestion du pivot a par P, donc  et d'autre part le pivot a est le dernier sommet intermédiaire de C  puisque le plus court chemin menant aux autres sommets de P ne passe par a comme expliqué plus haut. C est donc la réunion du plus court chemin menant à a avec l'arc (a, c) : d'où .

Applications 
L'algorithme de Dijkstra trouve une utilité dans le calcul des itinéraires routiers. Le poids des arcs pouvant être la distance (pour le trajet le plus court), le temps estimé (pour le trajet le plus rapide), la consommation de carburant et le prix des péages (pour le trajet le plus économique). 

Une application courante de l'algorithme de Dijkstra apparaît dans les protocoles de routage interne « à état de liens », tels que  () ou  – ou encore  sur les réseaux ATM –,

Comparaison avec d'autres algorithmes 
 L'algorithme de Dijkstra est fondé sur un parcours en largeur.
 La spécialisation de l'algorithme de Dijkstra qui calcule un plus court chemin d'une source à une destination est une instance de l'algorithme A* dans lequel la fonction heuristique est la fonction nulle. L'algorithme A* qui utiliserait une heuristique minorante et monotone (par exemple la distance à vol d'oiseau) peut être plus efficace .
 L'algorithme ne s'applique pas aux graphes avec des poids négatifs. Mais l'algorithme de Bellman-Ford permet de résoudre le problème des plus courts chemins depuis une source avec des poids négatifs (mais sans cycle négatif). 
 L'algorithme de Floyd-Warshall calcule des plus courts chemins entre tous les sommets dans un graphe où les poids peuvent être négatifs.

Notes et références

Annexes

Bibliographie 
  « A short introduction to the art of programming » de Edsger W. Dijkstra, 1971, contenant l'article original (1959) décrivant l'algorithme de Dijkstra (pages 67 à 73).
 , section 24.3, « Dijkstra's algorithm », pages 595–601.

Articles connexes 
 Lexique de la théorie des graphes
 Recherche de chemin et Problèmes de cheminement
 Algorithme de parcours en largeur
 Algorithme A*
 Article sur l'algorithme de Ford-Bellman qui permet de calculer le plus court chemin avec des poids d'arcs négatifs

Liens externes 
 Explication détaillée de l'algorithme de Dijkstra et implémentation complète en C++
 Explication détaillée de l'algorithme de Dijkstra et implémentation complète en Python
  Applet Java montrant l'algorithme de Dijkstra étape par étape
  Applets Java utilisant l'algorithme

Dijkstra
Algorithme de recherche