Floyd-Warshall en Python : plus courts chemins entre tous les sommets

Floyd-Warshall en Python : plus courts chemins entre tous les sommets

Floyd-Warshall est un algorithme de graphes qui calcule les plus courts chemins entre toutes les paires de sommets. Contrairement à Dijkstra, qui part d’un sommet donné, Floyd-Warshall produit une matrice complète : distance de chaque sommet vers chaque autre sommet.

Il est utile quand le graphe est de taille modérée et que l’on veut répondre ensuite rapidement à beaucoup de questions de distance.

La réponse courte

Floyd-Warshall repose sur une idée de programmation dynamique :

distance[i][j] = min(distance[i][j], distance[i][k] + distance[k][j])

Pour chaque sommet intermédiaire k, on teste si passer par k améliore le trajet de i vers j.

Complexité :

Mesure Valeur
Temps O(n³)
Mémoire O(n²)
Type de résultat plus courts chemins entre toutes les paires

n est le nombre de sommets.

Quand utiliser Floyd-Warshall

Floyd-Warshall est pertinent si :

  • le graphe n’est pas trop grand ;
  • vous voulez toutes les distances, pas seulement depuis un sommet ;
  • le graphe peut contenir des poids négatifs, mais pas de cycle négatif ;
  • la représentation matricielle est pratique pour votre problème.

Si vous voulez seulement le plus court chemin depuis un sommet, Dijkstra en Python est souvent plus adapté.

Implémentation simple en Python

On part d’une liste de sommets et d’arêtes pondérées.

def floyd_warshall(sommets, aretes):
    infini = float("inf")
    distances = {
        i: {j: infini for j in sommets}
        for i in sommets
    }

    for sommet in sommets:
        distances[sommet][sommet] = 0

    for depart, arrivee, poids in aretes:
        distances[depart][arrivee] = min(distances[depart][arrivee], poids)

    for intermediaire in sommets:
        for depart in sommets:
            for arrivee in sommets:
                nouveau = distances[depart][intermediaire] + distances[intermediaire][arrivee]
                if nouveau < distances[depart][arrivee]:
                    distances[depart][arrivee] = nouveau

    return distances


sommets = ["A", "B", "C", "D"]
aretes = [
    ("A", "B", 3),
    ("A", "C", 10),
    ("B", "C", 2),
    ("C", "D", 1),
    ("B", "D", 7),
]

distances = floyd_warshall(sommets, aretes)
print(distances["A"]["D"])

Ici, le chemin A -> B -> C -> D est plus court que A -> B -> D ou A -> C -> D.

Reconstruire les chemins

Calculer la distance est utile, mais on veut parfois connaître le chemin.

Pour cela, on garde une matrice suivant.

def floyd_warshall_chemins(sommets, aretes):
    infini = float("inf")
    distances = {i: {j: infini for j in sommets} for i in sommets}
    suivant = {i: {j: None for j in sommets} for i in sommets}

    for sommet in sommets:
        distances[sommet][sommet] = 0
        suivant[sommet][sommet] = sommet

    for depart, arrivee, poids in aretes:
        distances[depart][arrivee] = poids
        suivant[depart][arrivee] = arrivee

    for k in sommets:
        for i in sommets:
            for j in sommets:
                nouveau = distances[i][k] + distances[k][j]
                if nouveau < distances[i][j]:
                    distances[i][j] = nouveau
                    suivant[i][j] = suivant[i][k]

    return distances, suivant


def reconstruire(suivant, depart, arrivee):
    if suivant[depart][arrivee] is None:
        return None

    chemin = [depart]

    while depart != arrivee:
        depart = suivant[depart][arrivee]
        chemin.append(depart)

    return chemin


distances, suivant = floyd_warshall_chemins(sommets, aretes)
print(reconstruire(suivant, "A", "D"))

Cette version est plus complète, mais elle demande aussi plus de mémoire.

Détecter un cycle négatif

Floyd-Warshall accepte les arêtes de poids négatif, mais pas les cycles négatifs. Un cycle négatif rend la notion de plus court chemin instable : on peut tourner dans le cycle pour réduire le coût indéfiniment.

Après l’algorithme, si une distance distances[sommet][sommet] devient négative, il existe un cycle négatif accessible.

def contient_cycle_negatif(distances):
    return any(distances[s][s] < 0 for s in distances)

Floyd-Warshall, Dijkstra ou BFS

Besoin Algorithme recommandé
Plus court chemin non pondéré BFS
Plus court chemin depuis un sommet, poids positifs Dijkstra
Plus court chemin vers une cible avec heuristique A*
Plus courts chemins entre toutes les paires Floyd-Warshall
Arbre couvrant minimum Prim ou Kruskal

Cette distinction évite beaucoup d’erreurs. Floyd-Warshall n’est pas “meilleur” que Dijkstra : il répond à une autre question.

Erreurs fréquentes

La première erreur est d’utiliser Floyd-Warshall sur un très grand graphe. O(n³) devient vite coûteux.

La deuxième erreur est de mal initialiser la diagonale. La distance d’un sommet vers lui-même doit commencer à 0.

La troisième erreur est d’oublier que le graphe peut être orienté. Si une arête va de A vers B, cela ne veut pas dire qu’elle va aussi de B vers A.

La quatrième erreur est d’ignorer les cycles négatifs. Si votre graphe contient des coûts négatifs, vérifiez la diagonale après calcul.

Où continuer

Floyd-Warshall complète le cluster graphes :

La règle à retenir : utilisez Floyd-Warshall quand vous voulez toutes les distances entre toutes les paires de sommets et que le graphe reste assez petit pour supporter un coût en O(n³).

Références

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur la façon dont les données de vos commentaires sont traitées.