Adloun

Probleme – Bellman-Ford : ce que Dijkstra ne sait pas faire

Exercice · niveau 3 (difficile) · informatique (MP2I/MPI), chapitre 19 — Parcours de graphes et plus courts chemins

Énoncé

Dijkstra exige des poids positifs. On veut un algorithme qui s'en passe.

Corrigé

1. L'algorithme. Il ne choisit rien : il relâche tous les arcs, fois de suite.


(* Renvoie (d, pere, circuit) : d.(v) est la distance minimale de s a v,
   circuit vaut true si un circuit de poids negatif est accessible depuis s
   (auquel cas d n'a pas de sens).
   Preconditions : arcs est un tableau de (origine, but, poids), 0 <= s < n.
   Complexite : Theta(nm) en temps, Theta(n) en memoire. *)
let bellman_ford n arcs s =
  let d = Array.make n infinity and pere = Array.make n (-1) in
  d.(s) <- 0.0;
  (* INVARIANT : apres le tour k, d.(v) est la longueur minimale d'un chemin
     de s a v utilisant AU PLUS k arcs. *)
  for _ = 1 to n - 1 do
    Array.iter (fun (u, v, p) ->
      if d.(u) +. p < d.(v) then begin d.(v) <- d.(u) +. p; pere.(v) <- u end) arcs
  done;
  (* un tour de plus : s'il ameliore encore, c'est qu'il n'y a pas de minimum *)
  let circuit = Array.exists (fun (u, v, p) -> d.(u) +. p < d.(v)) arcs in
  (d, pere, circuit)

2. La correction.

L'invariant est celui écrit dans le code. Il tient au tour : le seul chemin à zéro arc est vers lui-même, de longueur . Supposons-le vrai après le tour , et soit atteignable par un chemin optimal à arcs, disons . Le préfixe a arcs, et il est optimal parmi les chemins à arcs (sous-structure optimale). Par hypothèse, vaut déjà cette valeur avant le tour . Comme le tour relâche l'arc , on obtient , la valeur voulue. L'invariant tient.

Le nombre de tours. S'il n'y a pas de circuit négatif, un plus court chemin est simple : s'il repassait par un sommet, on pourrait retirer la boucle intermédiaire, dont le poids est , sans allonger. Un chemin simple a au plus arcs. Après tours, l'invariant donne donc la vraie distance. C'est l'acyclicité des chemins optimaux qui fixe la borne, pas une propriété de l'algorithme.

3. La détection du circuit négatif. On fait un tour de plus. S'il améliore encore une distance, c'est qu'il existe un chemin à arcs strictement meilleur que tous les chemins à arcs — donc un chemin non simple meilleur que sa version simplifiée, donc un circuit de poids strictement négatif sur le trajet.

Mesure sur (1), (), (), () — le circuit pèse :


d = [0; -8; -6; -7]   circuit absorbant : true

Les valeurs de n'ont aucun sens : en tournant une fois de plus, on les aurait rendues plus petites encore. Le drapeau est la seule information à lire.

Sur le graphe de l'exercice sur les poids négatifs, où il n'y a pas de circuit négatif : d = [0; 1; 5; 2] ; circuit : false — c'est bien la réponse que Dijkstra manquait.

4. La comparaison.

DijkstraBellman-Ford
Complexité
Poids négatifsinterditsadmis
Circuit négatifnon détectédétecté
Structure auxiliairefile de prioritéaucune
Natureglouton (chapitre chap:gloutons)dynamique (chapitre chap:dynamique)

La ligne « nature » est la plus instructive. Dijkstra est un glouton : il fige un sommet et n'y revient jamais, et sa preuve d'échange exige la positivité. Bellman-Ford est de la programmation dynamique : le sous-problème est « la meilleure distance en au plus arcs », la récurrence est le relâchement, et il n'exige rien — au prix d'un facteur .

Une optimisation gratuite : si un tour n'améliore aucune distance, tous les suivants sont inutiles, et l'on peut sortir. Sur les graphes réels, Bellman-Ford converge souvent en trois ou quatre tours ; le est la borne du pire cas, pas le coût courant.

Les autres exercices de ce chapitre Le cours du chapitre

Un blocage sur cet exercice ? Le tuteur d'Adloun guide par questions, sans donner la réponse.