web-dev-qa-db-fra.com

Poids négatifs en utilisant l'algorithme de Dijkstra

J'essaie de comprendre pourquoi l'algorithme de Dijkstra ne fonctionnera pas avec des poids négatifs. En lisant un exemple sur Shortest Paths , j'essaie de comprendre le scénario suivant:

    2
A-------B
 \     /
3 \   / -2
   \ /
    C

Sur le site:

En supposant que les bords soient tous dirigés de gauche à droite, si nous commençons par A, l'algorithme de Dijkstra choisira le bord (A, x) minimisant d (A, A) + longueur (Edge), à ​​savoir (A, B). Il définit ensuite d (A, B) = 2 et choisit un autre bord (y, C) minimisant d (A, y) + d (y, C); le seul choix est (A, C) et il définit d (A, C) = 3. Mais il ne trouve jamais le chemin le plus court de A à B, via C, avec une longueur totale de 1.

Je ne comprends pas pourquoi l'utilisation de l'implémentation suivante de Dijkstra, d [B] ne sera pas mise à jour en 1 _ (Lorsque l’algorithme atteint le sommet C, il exécute une relaxation sur B, voyez que le d [B] est égal à 2, et met donc à jour sa valeur sur 1).

Dijkstra(G, w, s)  {
   Initialize-Single-Source(G, s)
   S ← Ø
   Q ← V[G]//priority queue by d[v]
   while Q ≠ Ø do
      u ← Extract-Min(Q)
      S ← S U {u}
      for each vertex v in Adj[u] do
         Relax(u, v)
}

Initialize-Single-Source(G, s) {
   for each vertex v  V(G)
      d[v] ← ∞
      π[v] ← NIL
   d[s] ← 0
}

Relax(u, v) {
   //update only if we found a strictly shortest path
   if d[v] > d[u] + w(u,v) 
      d[v] ← d[u] + w(u,v)
      π[v] ← u
      Update(Q, v)
}

Merci,

Meir

105
Meir

L'algorithme que vous avez suggéré trouvera effectivement le chemin le plus court dans ce graphique, mais pas tous les graphiques en général. Par exemple, considérons ce graphique:

Figure of graph

Supposons que les bords sont dirigés de gauche à droite comme dans votre exemple,

Votre algorithme fonctionnera comme suit:

  1. Tout d’abord, vous définissez d(A) sur zero et les autres distances sur infinity.
  2. Vous développez ensuite le noeud A, en définissant d(B) sur 1, d(C) sur zero et d(D) à 99.
  3. Ensuite, vous développez C, sans modifications nettes.
  4. Vous développez ensuite B, qui n'a aucun effet.
  5. Enfin, vous développez D, ce qui change d(B) en -201.

Notez qu'à la fin de ceci, cependant, que d(C) est toujours 0, même si le chemin le plus court vers C a la longueur -200. Votre algorithme ne parvient donc pas à calculer les distances avec précision dans certains cas. De plus, même si vous stockiez des pointeurs indiquant comment aller de chaque nœud au nœud de départ A, vous finissiez par retourner le mauvais chemin de C à A.

188
templatetypedef

Notez que Dijkstra fonctionne même pour les poids négatifs, si le graphique n’a pas de cycles négatifs, c’est-à-dire des cycles dont le poids total est inférieur à zéro.

Bien sûr, on peut se demander pourquoi, dans l'exemple de templatetypedef, Dijkstra échoue alors même qu'il n'y a pas de cycles négatifs, même pas de cycles. En effet, il utilise un autre critère d'arrêt, qui maintient l'algorithme dès que le noeud cible est atteint (ou que tous les noeuds ont été installés une fois, il ne l'a pas précisé exactement). Dans un graphique sans pondération négative, cela fonctionne bien.

Si on utilise le critère d'arrêt alternatif, qui arrête l'algorithme lorsque la file d'attente prioritaire (tas) est vide (ce critère d'arrêt a également été utilisé dans la question), alors dijkstra trouvera la distance correcte même pour les graphiques avec des poids négatifs mais sans cycles négatifs.

Cependant, dans ce cas, la limite temporelle asymptotique de dijkstra pour les graphes sans cycles négatifs est perdue. Cela est dû au fait qu'un nœud précédemment établi peut être réinséré dans le tas lorsqu'une meilleure distance est trouvée en raison de poids négatifs. Cette propriété est appelée correction d’étiquette.

22
infty10000101

vous n’avez utilisé S nulle part dans votre algorithme (en plus de le modifier). L'idée de dijkstra est qu'une fois qu'un sommet est sur S, il ne sera plus jamais modifié. dans ce cas, une fois que B est à l'intérieur de S, vous ne pourrez plus l'atteindre via C.

ce fait assure la complexité de O (E + VlogV) [sinon, vous répétez les arêtes plusieurs fois, et les sommets plus d'une fois]

en d'autres termes, l'algorithme que vous avez publié peut ne pas être en O (E + VlogV), comme promis par l'algorithme de dijkstra.

9
amit

Comme Dijkstra est une approche gourmande, une fois qu'un sommet est marqué comme visité pour cette boucle, il ne sera plus jamais réévalué, même s'il existe un autre chemin moins coûteux pour l'atteindre ultérieurement. Et un tel problème ne peut se produire que lorsque des arêtes négatives existent dans le graphique.


Un algorithme glouton , comme son nom l'indique, fait toujours le choix qui semble être le meilleur à ce moment-là. Supposons que vous avez une fonction objective qui doit être optimisée (maximisée ou minimisée) à un moment donné. n algorithme Greedy fait des choix gloutons à chaque étape pour s'assurer que la fonction objectif est optimisée. L'algorithme de Greedy n'a qu'un seul essai pour calculer la solution optimale pour que il ne revient jamais en arrière et n'inverse la décision.

6
punchoyeah

Considérez ce qui se passe si vous faites des va-et-vient entre B et C ... voila

(pertinent uniquement si le graphique n'est pas dirigé)

Édité: Je pense que le problème vient du fait que le chemin avec AC * ne peut être meilleur que pour AB avec l’existence d’avancées négatives en poids. il est impossible de trouver un meilleur chemin que AB une fois que vous avez choisi d'atteindre B après le passage à l'AC.

1
prusswan

"2) Pouvons-nous utiliser l'algorithme de Dijksra pour les chemins les plus courts pour les graphes avec des pondérations négatives? Une idée peut être, calculer la valeur de pondération minimale, ajouter une valeur positive (égale à la valeur absolue de la valeur de pondération minimale) à toutes les pondérations et exécuter l'algorithme de Dijksra pour le graphe modifié. Cet algorithme fonctionnera-t-il? "

Cela ne fonctionne absolument pas à moins que tous les chemins les plus courts aient la même longueur. Par exemple, si le plus court chemin de longueur est égal à deux arêtes et après avoir ajouté une valeur absolue à chaque arête, le coût total du trajet est augmenté de 2 * | max poids négatif |. D'autre part, un autre trajet de longueur trois arêtes augmente le coût du trajet de 3 * | max poids négatif |. Par conséquent, tous les chemins distincts sont augmentés de différentes quantités.

1
Wackyfool

TL; DR: Pour le pseudo-code que vous avez posté, cela fonctionne avec des poids négatifs.


Variantes de l'algorithme de Dijkstra

La clé est il existe 3 versions de l'algorithme de Dijkstra , mais toutes les réponses à cette question ignorent les différences entre ces variantes.

  1. Utiliser une boucle imbriquée for- pour détendre les sommets. C'est le moyen le plus simple d'implémenter l'algorithme de Dijkstra. La complexité temporelle est O (V ^ 2).
  2. Implémentation basée sur une file d'attente prioritaire/tas + AUCUNE ré-entrée autorisée , où une ré-entrée signifie qu'un sommet relâché peut à nouveau être placé dans la file d'attente prioritaire.
  3. Implémentation basée sur une file d'attente prioritaire/tas + ré-entrée autorisée .

Les versions 1 et 2 échoueront sur les graphiques avec une pondération négative (si vous obtenez la bonne réponse dans ce cas, ce n'est qu'une coïncidence), mais la version 3 fonctionne toujours .

Le pseudo-code affiché sous la question initiale est la version 3 ci-dessus, il fonctionne donc avec des pondérations négatives.

Voici une bonne référence de Algorithm (4th edition) , qui dit (et contient l’implémentation Java des versions 2 et 3 que j'ai mentionnées ci-dessus)):

Q. L'algorithme de Dijkstra fonctionne-t-il avec des poids négatifs?

R. Oui et non. Il existe deux algorithmes de chemins les plus courts connus sous le nom d'algorithme de Dijkstra, selon qu'un sommet peut ou non être mis en file d'attente sur la file d'attente prioritaire. Lorsque les poids ne sont pas négatifs, les deux versions coïncident (aucun sommet n'étant mis en file d'attente plus d'une fois). La version implémentée dans DijkstraSP.Java (qui permet à un sommet d'être mis en file d'attente plusieurs fois) est correcte en présence de pondérations Edge négatives (mais pas de cycles négatifs) mais son temps d'exécution est exponentiel dans le pire des cas. (Nous notons que DijkstraSP.Java lève une exception si le digraphe à pondération Edge a un Edge avec un poids négatif, de sorte qu'un programmeur ne soit pas surpris par ce comportement exponentiel.) Si nous modifions DijkstraSP.Java afin qu'un sommet ne puisse pas être mis en file d'attente plusieurs fois (par exemple, en utilisant un tableau marqué [] pour marquer les sommets qui ont été relâchés), il est alors garanti que l’algorithme s’exécutera dans E log V time, mais il peut en résulter des résultats incorrects lorsqu’il ya des arêtes de poids négatifs.


Pour plus de détails sur l'implémentation et la connexion de la version 3 avec l'algorithme Bellman-Ford, veuillez consulter cette réponse de zhih . C'est aussi ma réponse (mais en chinois). Actuellement, je n'ai pas le temps de le traduire en anglais. J'apprécie vraiment si quelqu'un pouvait faire cela et éditer cette réponse sur stackoverflow.

0
soloice