Théorie des graphes et programmation dynamique

L'algorithme de Floyd-Warshall expliqué

Dijkstra et Bellman-Ford répondent à une question : à quelle distance se trouve tout le reste d'ici ? Floyd-Warshall y répond pour toutes les paires d'un coup. Découvrez la récurrence de programmation dynamique derrière ses trois boucles imbriquées, comment il détecte gratuitement les cycles négatifs et quand il surpasse Dijkstra répété.

12 min de lecture Mis à jour : août 2026 Niveau avancé
Mohammed Islam Hadjoudj
Mohammed Islam Hadjoudj
Ingénieur expert en recherche opérationnelle

1. Introduction à l'algorithme de Floyd-Warshall

L'algorithme de Floyd-Warshall résout le problème des plus courts chemins entre toutes les paires. Pour un graphe orienté et pondéré, il détermine la plus courte distance entre chaque paire de sommets, et pas seulement les distances depuis un point de départ choisi. À la fin, vous disposez d'une matrice de distances complète : consultez n'importe quelle origine et n'importe quelle destination, la réponse s'y trouve déjà.

C'est une question différente de celle à laquelle répondent l'algorithme de Dijkstra et celui de Bellman-Ford. Ces derniers sont des algorithmes à source unique : vous leur donnez un sommet de départ et ils vous indiquent la distance de tous les autres sommets à celui-ci. Floyd-Warshall répond à toutes ces questions d'un coup, pour chaque sommet de départ possible, en une seule exécution.

Ce qui rend cet algorithme remarquable, c'est le peu dont il a besoin pour y parvenir. Pas de file de priorité, pas d'ensemble de sommets visités, pas de récursion. L'algorithme entier tient en trois boucles imbriquées sur une matrice, et sa correction repose sur une seule idée claire empruntée à la programmation dynamique. Il accepte en outre les poids d'arêtes négatifs, ce que Dijkstra ne peut pas faire, et il signale la présence d'un cycle négatif comme effet secondaire du travail qu'il effectuait déjà.

2. Toutes les paires ou source unique : pourquoi ne pas répéter Dijkstra ?

Une objection évidente consiste à dire qu'il suffirait d'exécuter un algorithme à source unique une fois depuis chaque sommet. Cette approche est légitime et parfois préférable, il vaut donc la peine de préciser quand chacune l'emporte.

Exécuter l'algorithme de Dijkstra depuis les V sommets, avec un tas binaire, coûte O(V * E log V). Dans un graphe creux, où le nombre d'arêtes E est proche de V, cela revient à environ O(V2 log V), ce qui bat confortablement Floyd-Warshall. Dans un graphe dense, où E approche V2, la même répétition coûte à peu près O(V3 log V), et la borne constante O(V3) de Floyd-Warshall devient la meilleure.

Deux autres considérations tranchent souvent le choix avant même la complexité :

Règle pratique : choisissez Floyd-Warshall pour les graphes denses, pour les graphes à arêtes négatives, ou lorsque vous avez réellement besoin de toutes les paires. Choisissez Dijkstra répété pour les grands graphes creux à poids positifs ou nuls.

3. L'idée centrale : les sommets intermédiaires

L'intuition derrière Floyd-Warshall consiste à contraindre le problème d'une manière qui le rende facile à faire croître. Au lieu de demander d'emblée « quel est le plus court chemin de i à j ? », il pose une question plus étroite :

Quel est le plus court chemin de i à j qui n'a le droit de passer, comme escales intermédiaires, que par les sommets d'un certain ensemble autorisé ?

Numérotez les sommets de 1 à V. Définissez l'ensemble autorisé comme les k premiers sommets et notez dk(i, j) la plus courte distance de i à j n'utilisant que {1, 2, ..., k} comme sommets intermédiaires. Les extrémités i et j sont toujours autorisées, qu'elles appartiennent ou non à l'ensemble autorisé. Seuls les sommets strictement intermédiaires sont restreints.

Les deux extrémités de cette définition sont instructives. Lorsque k = 0, rien ne peut servir d'escale, donc d0(i, j) est exactement le poids de l'arête directe de i à j, ou l'infini si une telle arête n'existe pas. Lorsque k = V, tous les sommets sont autorisés, donc dV(i, j) est la véritable distance minimale sans restriction. L'algorithme est la mécanique qui mène de la première à la seconde.

4. La relation de récurrence

Supposons que vous connaissiez déjà toutes les valeurs de dk-1 et que vous vouliez dk. Considérez le plus court chemin de i à j pouvant utiliser {1, ..., k}. Il existe exactement deux possibilités, et elles s'excluent mutuellement :

  1. Le chemin n'utilise pas le sommet k. Il n'utilise alors que {1, ..., k-1}, donc sa longueur vaut dk-1(i, j), une valeur que vous possédez déjà.
  2. Le chemin utilise le sommet k. Comme un plus court chemin ne répète jamais un sommet, il passe par k exactement une fois. Cela le scinde en un tronçon de i à k et un tronçon de k à j, et aucun des deux ne peut utiliser k comme escale. Sa longueur vaut donc dk-1(i, k) + dk-1(k, j), et ces deux termes sont eux aussi déjà connus.

Le plus court chemin est le plus petit des deux, ce qui donne la récurrence au coeur de l'algorithme :

d[k][i][j] = min( d[k-1][i][j],
                  d[k-1][i][k] + d[k-1][k][j] )

Autrement dit : passer par k ne vaut la peine que si le détour vers k puis vers j est plus court que la meilleure route trouvée sans lui. C'est de la programmation dynamique à l'état pur. Chaque sous-problème est résolu une fois, stocké, puis réutilisé.

En pratique, personne ne stocke V matrices distinctes. Les valeurs peuvent être mises à jour sur place dans une unique matrice V x V, car pendant l'itération k les entrées d(i, k) et d(k, j) ne peuvent pas changer : les mettre à jour exigerait d(k, k), qui vaut 0 tant que le graphe ne contient pas de cycle négatif. Lire une valeur déjà écrasée dans ce même tour est donc sans danger, et l'occupation mémoire tombe de O(V3) à O(V2).

Comparaison de la distance directe de i à j avec la distance passant par le sommet intermédiaire k.
Chaque étape pose une seule question : la route passant par le sommet k est-elle plus courte que la meilleure trouvée jusqu'ici ?

5. Exécution pas à pas

Les récurrences abstraites deviennent claires dès qu'on les fait tourner sur des nombres. Prenons un graphe orienté à quatre sommets avec ces arêtes :

Initialisez la matrice à partir des seules arêtes. La diagonale vaut 0, car chaque sommet s'atteint lui-même sans coût, et toute arête absente vaut l'infini.

k = 0 (arêtes directes seulement)

        1     2     3     4
  1     0     5     inf   10
  2     inf   0     3     inf
  3     inf   inf   0     1
  4     inf   inf   inf   0

Tour k = 1. Le sommet 1 est désormais autorisé comme escale. Toute mise à jour exigerait un d(i, 1) fini, mais la colonne 1 est infinie partout sauf sur la diagonale, car aucune arête ne mène au sommet 1. Rien ne change.

Tour k = 2. Le sommet 2 devient disponible. La ligne 2 offre un d(2, 3) = 3 fini, et la colonne 2 un d(1, 2) = 5 fini. Cela donne une amélioration candidate :

Tour k = 3. Le sommet 3 devient disponible, et d(3, 4) = 1. Deux entrées s'améliorent :

Remarquez que l'amélioration de d(1, 4) s'est appuyée sur d(1, 3) = 8, une valeur découverte au tour précédent. L'algorithme construit les chemins longs à partir de chemins courts qu'il a déjà démontrés.

Tour k = 4. Le sommet 4 n'a aucune arête sortante, la ligne 4 est donc infinie hors diagonale et aucun chemin ne peut utilement passer par lui. Rien ne change, et l'algorithme se termine.

Résultat (toutes les paires)

        1     2     3     4
  1     0     5     8     9
  2     inf   0     3     4
  3     inf   inf   0     1
  4     inf   inf   inf   0

La réponse pour 1 -> 4 est 9, par la route 1 -> 2 -> 3 -> 4 au coût de 5 + 3 + 1, ce qui bat l'arête directe de poids 10. Les infinis restants sont corrects et non inachevés : aucune arête n'entre dans le sommet 1, donc rien ne peut l'atteindre.

6. Implémentation et pseudo-code

L'algorithme est assez court pour être mémorisé. Le détail qui compte plus que tout autre est l'ordre des boucles.

function FloydWarshall(W, V):
    // W[i][j] = poids de l'arête i -> j, sinon Infinity
    // dist est une matrice V x V

    for i from 1 to V:
        for j from 1 to V:
            dist[i][j] = W[i][j]
        dist[i][i] = 0

    // k DOIT être la boucle la plus externe
    for k from 1 to V:
        for i from 1 to V:
            for j from 1 to V:
                if dist[i][k] + dist[k][j] < dist[i][j]:
                    dist[i][j] = dist[i][k] + dist[k][j]

    return dist

k doit être la boucle la plus externe. C'est de loin la manière la plus courante d'écrire cet algorithme de travers. La récurrence exige que toute paire (i, j) soit mise à jour vis-à-vis du sommet intermédiaire k avant de passer à k + 1. Si k est placée à l'intérieur, la matrice se remplit dans un ordre dépourvu de sens, et le résultat est un ensemble de distances qui paraissent plausibles mais ne sont pas optimales.

Une précaution d'implémentation : si vous représentez l'infini par une grande valeur sentinelle telle que INT_MAX plutôt que par un véritable infini flottant, dist[i][k] + dist[k][j] peut déborder et repasser à un nombre négatif, que la comparaison acceptera volontiers. Utilisez soit un infini réel, soit une garde sur l'addition en sautant la mise à jour dès que l'un des opérandes est la sentinelle.

7. Complexité en temps et en espace

L'insensibilité à E est le trait déterminant. Un graphe à quatre sommets et trois arêtes coûte autant qu'un graphe à quatre sommets et douze arêtes. C'est du gaspillage sur les graphes creux et parfaitement efficace sur les denses. En contrepartie, la constante est très faible et le schéma d'accès mémoire est régulier et favorable au cache, si bien que Floyd-Warshall dépasse souvent son asymptotique sur des graphes de quelques centaines de sommets.

8. Arêtes négatives et détection des cycles négatifs

Floyd-Warshall accepte les poids d'arêtes négatifs sans la moindre modification. Sa récurrence ne suppose jamais qu'allonger un chemin en augmente la longueur, et c'est précisément cette hypothèse qui fait échouer l'algorithme de Dijkstra sur des entrées négatives.

Les cycles négatifs sont une autre affaire, et aucun algorithme ne peut renvoyer des distances minimales sensées en leur présence : on peut boucler indéfiniment et faire chuter le coût sans borne. Ce que Floyd-Warshall vous offre, c'est un moyen de le remarquer gratuitement. Inspectez la diagonale une fois l'algorithme terminé :

for i from 1 to V:
    if dist[i][i] < 0:
        signaler "cycle négatif détecté"

La diagonale a été initialisée à 0. Un sommet ne peut se retrouver avec une distance négative vers lui-même que s'il existe une marche fermée partant de lui et y revenant dont le poids total est inférieur à zéro, ce qui est exactement la définition d'un cycle négatif. Là où Bellman-Ford a besoin d'un passage supplémentaire dédié sur toutes les arêtes pour établir la même chose, Floyd-Warshall se contente d'un coup d'oeil sur V entrées déjà calculées.

Notez la portée de cette vérification. Elle signale tout cycle négatif auquel le sommet concerné participe. Si la diagonale est saine, chaque distance de la matrice est fiable. Si elle ne l'est pas, les valeurs finies ailleurs dans la matrice doivent être tenues pour dépourvues de sens, et non simplement pour imprécises.

9. Reconstruire les chemins réels

La matrice des distances consigne l'éloignement entre deux sommets, mais pas la route qui y parvient. Retrouver cette route demande une matrice supplémentaire, et le procédé le plus économique stocke, pour chaque paire, le sommet suivant sur le chemin.

Initialisez next[i][j] = j dès qu'une arête directe existe, et laissez la case vide sinon. Ensuite, chaque fois que la boucle principale améliore dist[i][j] en passant par k, héritez du premier pas de la nouvelle route :

if dist[i][k] + dist[k][j] < dist[i][j]:
    dist[i][j] = dist[i][k] + dist[k][j]
    next[i][j] = next[i][k]

L'affectation est next[i][k], et non k. Le premier mouvement du trajet amélioré de i à j est le premier mouvement du trajet de i à k, qui peut fort bien être un tout autre sommet. Relire le chemin devient alors une courte promenade : partez de i, suivez next de proche en proche, et arrêtez-vous en atteignant j. Cela coûte O(V2) d'espace supplémentaire et aucun temps notable.

10. Variantes et applications concrètes

La structure à trois boucles se généralise bien au-delà des plus courts chemins, car la récurrence n'a besoin que d'une opération qui combine deux tronçons et d'une autre qui choisit entre des alternatives.

Fermeture transitive (algorithme de Warshall)

Remplacez l'addition par le ET logique et le minimum par le OU logique, et les mêmes boucles calculent l'accessibilité : l'existence ou non d'un chemin entre chaque paire, indépendamment du coût. C'est le résultat original de Warshall en 1962, et c'est pourquoi l'algorithme combiné porte les deux noms. On le retrouve dans l'analyse de flot de données des compilateurs, dans la résolution de dépendances et dans la planification de requêtes des bases de données.

Chemin le plus large et problèmes de goulot d'étranglement

Remplacez l'addition par le minimum et le minimum par le maximum, et l'algorithme trouve la route dont le maillon le plus étroit est aussi large que possible. C'est la formulation naturelle pour le routage à bande passante maximale dans un réseau et pour la planification de capacité en logistique.

Routage réseau et matrices de latence

Les opérateurs réseau ont souvent besoin d'une matrice complète des latences ou des nombres de sauts entre toutes les paires de noeuds d'une topologie. Les topologies de coeur de réseau sont généralement denses et de taille modeste en sommets, ce qui correspond exactement au régime pour lequel Floyd-Warshall a été conçu.

Arbitrage de devises

Modélisez les devises comme des sommets et les taux de change comme des arêtes. Prendre le logarithme négatif de chaque taux transforme la multiplication des taux en addition de poids, et une boucle d'arbitrage rentable devient un cycle négatif. La vérification de la diagonale indique alors s'il existe une opportunité d'arbitrage, et la matrice next reconstitue la séquence des transactions.

11. Ressources académiques et histoire

L'attribution de cet algorithme est particulièrement enchevêtrée. Plusieurs chercheurs sont parvenus aux mêmes trois boucles indépendamment, à quelques années d'intervalle.

Pour un traitement rigoureux assorti de preuves de correction complètes, la référence standard est Cormen, Leiserson, Rivest et Stein, Introduction to Algorithms, au chapitre consacré aux plus courts chemins entre toutes les paires. Ceux qui comparent les approches pour les graphes creux devraient également étudier l'algorithme de Johnson, qui repondère un graphe de sorte que Dijkstra répété reste valide même en présence d'arêtes négatives. Les références complètes figurent à la fin de cet article.

Foire aux questions

Quand utiliser Floyd-Warshall plutôt que l'algorithme de Dijkstra ?

Utilisez Floyd-Warshall lorsque vous avez besoin de la plus courte distance entre chaque paire de sommets, lorsque le graphe est dense, ou lorsque des poids d'arêtes négatifs sont présents. Exécuter l'algorithme de Dijkstra depuis chaque sommet coûte O(V * E log V), ce qui est plus rapide sur les grands graphes creux mais incorrect dès qu'une arête est négative. Le temps O(V^3) de Floyd-Warshall ne dépend pas du nombre d'arêtes, il l'emporte donc sur les graphes denses.

Floyd-Warshall gère-t-il les poids d'arêtes négatifs ?

Oui. Floyd-Warshall accepte les poids négatifs sans modification, car sa récurrence ne suppose jamais qu'allonger un chemin en augmente la longueur. Il ne peut pas renvoyer de distances sensées en présence d'un cycle négatif, mais il détecte ce cas gratuitement : après exécution, tout sommet dont la distance à lui-même est inférieure à zéro appartient à un cycle négatif.

Pourquoi la boucle k doit-elle être la plus externe ?

La récurrence exige que toute paire (i, j) soit mise à jour vis-à-vis du sommet intermédiaire k avant de passer à k + 1. Placer k dans une boucle interne remplit la matrice dans un ordre dépourvu de sens et produit des distances qui paraissent plausibles mais ne sont pas optimales. C'est de loin la manière la plus courante d'implémenter cet algorithme de travers.

Regardez la matrice des distances se remplir

Trois boucles imbriquées sont difficiles à imaginer et faciles à voir. Exécutez Floyd-Warshall et suivez chaque paire se résoudre.

Ouvrir le calculateur Floyd-Warshall

Références vérifiées et lectures complémentaires