Pesi negativi utilizzando l'algoritmo di Dijkstra


113

Sto cercando di capire perché l'algoritmo di Dijkstra non funzionerà con pesi negativi. Leggendo un esempio su Percorsi più brevi , sto cercando di capire il seguente scenario:

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

Dal sito web:

Supponendo che i bordi siano tutti diretti da sinistra a destra, se iniziamo con A, l'algoritmo di Dijkstra sceglierà il bordo (A, x) minimizzando d (A, A) + lunghezza (bordo), cioè (A, B). Quindi imposta d (A, B) = 2 e sceglie un altro arco (y, C) minimizzando d (A, y) + d (y, C); l'unica scelta è (A, C) e pone d (A, C) = 3. Ma non trova mai il percorso più breve da A a B, via C, con lunghezza totale 1.

Non riesco a capire perché utilizzando la seguente implementazione di Dijkstra, d [B] non verrà aggiornato a 1(Quando l'algoritmo raggiunge il vertice C, eseguirà un rilassamento su B, vedrà che d [B] è uguale a 2, e quindi aggiornerà il suo valore a 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)
}

Grazie,

Meir


Il pathfinding in generale con pesi dei bordi negativi è estremamente difficile. Non importa quale percorso trovi, c'è sempre la possibilità di un percorso arbitrariamente lungo con un bordo negativo arbitrariamente grande da qualche parte lungo di esso. Non sarei sorpreso se fosse NP completo.
Nick Johnson

4
Per chiunque altro abbia questo dubbio, puoi trovare il percorso più breve in un grafico DATO che non ha cicli di peso negativi. L'algoritmo di cui sopra funzionerebbe se la funzione Relax restituisse un valore "vero" quando relax ha effettivamente avuto successo, nel qual caso, il vertice adiacente "v" verrebbe accodato nella coda di priorità se non presente, o aggiornato se già presente. Ciò significa che i nodi visitati possono essere nuovamente aggiunti alla coda di priorità man mano che continuano a rilassarsi.
goelakash

Risposte:


202

L'algoritmo che hai suggerito troverà effettivamente il percorso più breve in questo grafico, ma non tutti i grafici in generale. Ad esempio, considera questo grafico:

Figura del grafico

Supponiamo che i bordi siano diretti da sinistra a destra come nel tuo esempio,

Il tuo algoritmo funzionerà come segue:

  1. Innanzitutto, imposta d(A)su zeroe le altre distanze su infinity.
  2. Quindi espandi il nodo A, impostando d(B)su 1, d(C)su zeroe d(D)su 99.
  3. Successivamente, ti espandi C, senza cambiamenti netti.
  4. Quindi ti espandi B, il che non ha alcun effetto.
  5. Infine, espandi D, che cambia d(B)in -201.

Si noti che alla fine di questo, tuttavia, d(C)è ancora 0, anche se il percorso più breve per raggiungere Cè lungo -200. Il tuo algoritmo quindi non riesce a calcolare accuratamente le distanze in alcuni casi. Inoltre, anche se dovessi memorizzare i puntatori indietro che dicono come andare da ogni nodo al nodo iniziale A, finiresti per riprendere il percorso sbagliato da Ca A.


35
Da aggiungere alla tua eccellente risposta: Dijkstra essendo un algoritmo avido è la ragione della sua scelta miope.
blubb

4
Ci tengo a precisare che, tecnicamente, tutti i percorsi in questo grafico hanno un costo di infinito negativo per gentile concessione del ciclo negativo A, D, B, A.
Nate

2
@ Nate- Per chiarire, tutti i bordi nel grafico sono diretti da sinistra a destra. Era piuttosto difficile rendere le frecce nella mia arte ASCII di alta qualità. :-)
templatetypedef

2
Per coloro che non hanno mai visto grafici con bordi negativi prima, trovo che un'interpretazione utile di questo grafico sia una rete di strade a pedaggio, dove i pesi dei bordi danno il pedaggio che paghi. La strada -300 è una pazza strada a pedaggio all'indietro dove invece ti danno $ 300.
D Coetzee

3
@ SchwitJanwityanujit- Ecco come funziona l'algoritmo di Dijkstra. L'algoritmo non esplora i percorsi , ma funziona invece elaborando i nodi . Ogni nodo viene elaborato esattamente una volta, quindi non appena elaboriamo il nodo B e otteniamo che la sua distanza è 1, non rivisiteremo mai il nodo B né tenteremo di aggiornarne la distanza.
templatetypedef

25

Si noti che Dijkstra funziona anche per pesi negativi, se il grafico non ha cicli negativi, cioè cicli il cui peso sommato è inferiore a zero.

Naturalmente ci si potrebbe chiedere, perché nell'esempio fatto da templatetypedef Dijkstra fallisce anche se non ci sono cicli negativi, anzi nemmeno cicli. Questo perché sta utilizzando un altro criterio di arresto, che mantiene l'algoritmo non appena viene raggiunto il nodo di destinazione (o tutti i nodi sono stati regolati una volta, non l'ha specificato esattamente). In un grafico senza pesi negativi questo funziona bene.

Se si utilizza il criterio di arresto alternativo, che arresta l'algoritmo quando la coda di priorità (heap) è vuota (questo criterio di arresto è stato utilizzato anche nella domanda), allora dijkstra troverà la distanza corretta anche per i grafici con pesi negativi ma senza cicli negativi.

Tuttavia, in questo caso, il limite di tempo asintotico di dijkstra per grafici senza cicli negativi viene perso. Questo perché un nodo precedentemente impostato può essere reinserito nell'heap quando viene trovata una distanza migliore a causa di pesi negativi. Questa proprietà è chiamata correzione dell'etichetta.


2. Non è chiaro perché pensi che il tempo mi sarebbe "più simile a Bellman-Ford" e non esponenziale (che è peggio di Bellman-Ford). Hai in mente un algoritmo concreto e una prova?
Gassa

3
Per 1 .: poiché puoi usare esattamente la stessa implementazione di dijkstra con il citato criterio di arresto, che si ferma quando la coda viene eseguita vuota (vedi pseudocodice nella domanda originale), è ancora l'algoritmo dijkstras per i percorsi più brevi, anche se si comporta diversamente regolare i nodi più volte (correzione dell'etichetta).
infty10000101

1
A 2 .: Era solo un'ipotesi, quindi la cancellerò. Penso che tu abbia ragione con il tempo esponenziale, poiché ci sono molti percorsi esponenziali, che devono essere esplorati.
infty10000101

11

non hai usato S da nessuna parte nel tuo algoritmo (oltre a modificarlo). l'idea di dijkstra è una volta che un vertice è su S, non sarà mai più modificata. in questo caso, una volta che B è dentro S, non la raggiungerai più tramite C.

questo fatto garantisce la complessità di O (E + VlogV) [altrimenti, ripeterai gli archi più di una volta e i vertici più di una volta]

in altre parole, l'algoritmo che hai postato, potrebbe non essere in O (E + VlogV), come promesso dall'algoritmo di dijkstra.


Inoltre, non è necessario modificare il vertice senza bordi di peso negativi, il che infrange completamente l'ipotesi che i costi del percorso possano aumentare solo con bordi ripetuti
prusswan

questa ipotesi è esattamente ciò che ci permette di usare S, e "conoscere" una volta che un vertice è in S, non sarà mai più modificato.
amit

La tua ultima affermazione è sbagliata. L'algoritmo pubblicato ha complessità temporale O (E + VlogV) quando funziona su grafici senza bordi negativi. Non c'è bisogno di controllare che abbiamo già visitato un nodo, poiché il fatto che sia stato visitato garantisce che la procedura di rilassamento non lo aggiungerà più una volta in coda.
Pixar

7

Poiché Dijkstra è un approccio avido, una volta che un vertice è contrassegnato come visitato per questo anello, non verrà mai più rivalutato anche se c'è un altro percorso con un costo inferiore per raggiungerlo in seguito. E tale problema potrebbe verificarsi solo quando nel grafico sono presenti bordi negativi.


Un algoritmo avido , come suggerisce il nome, fa sempre la scelta che sembra essere la migliore in quel momento. Supponi di avere una funzione obiettivo che deve essere ottimizzata (massimizzata o minimizzata) in un dato punto. Un algoritmo Greedy fa scelte avide in ogni fase per garantire che la funzione obiettivo sia ottimizzata. L'algoritmo Greedy ha una sola possibilità per calcolare la soluzione ottimale in modo che non torni mai indietro e inverta la decisione.


4

TL; DR: la risposta dipende dalla tua implementazione. Per lo pseudo codice che hai pubblicato, funziona con pesi negativi.


Varianti dell'algoritmo di Dijkstra

La chiave è che ci sono 3 tipi di implementazione dell'algoritmo di Dijkstra , ma tutte le risposte a questa domanda ignorano le differenze tra queste varianti.

  1. Utilizzo di un ciclo annidatofor per rilassare i vertici. Questo è il modo più semplice per implementare l'algoritmo di Dijkstra. La complessità temporale è O (V ^ 2).
  2. Implementazione basata su coda di priorità / heap + NESSUN rientro consentito, dove il rientro significa che un vertice rilassato può essere nuovamente inserito nella coda di priorità per essere rilassato di nuovo in seguito .
  3. Implementazione basata su coda prioritaria / heap + rientro consentito.

Le versioni 1 e 2 falliranno sui grafici con pesi negativi (se ottieni la risposta corretta in questi casi, è solo una coincidenza), ma la versione 3 funziona ancora .

Lo pseudo codice pubblicato sotto il problema originale è la versione 3 sopra, quindi funziona con pesi negativi.

Ecco un buon riferimento da Algorithm (4a edizione) , che dice (e contiene l'implementazione java delle versioni 2 e 3 che ho menzionato sopra):

D. L'algoritmo di Dijkstra funziona con pesi negativi?

R. Sì e no. Esistono due algoritmi dei cammini minimi noti come algoritmo di Dijkstra, a seconda che un vertice possa essere accodato più di una volta nella coda prioritaria. Quando i pesi non sono negativi, le due versioni coincidono (poiché nessun vertice verrà accodato più di una volta). La versione implementata in DijkstraSP.java (che consente di accodare un vertice più di una volta) è corretta in presenza di spessori negativi (ma senza cicli negativi) ma il suo tempo di esecuzione è esponenziale nel caso peggiore. (Notiamo che DijkstraSP.java genera un'eccezione se il digrafo ponderato sul bordo ha un bordo con un peso negativo, in modo che un programmatore non sia sorpreso da questo comportamento esponenziale.) Se modifichiamo DijkstraSP.java in modo che un vertice non possa essere accodato più di una volta (ad esempio, utilizzando un array [] contrassegnato per contrassegnare quei vertici che sono stati rilassati),


Per ulteriori dettagli sull'implementazione e la connessione della versione 3 con l'algoritmo Bellman-Ford, vedere questa risposta di zhihu . È anche la mia risposta (ma in cinese). Al momento non ho tempo per tradurlo in inglese. Apprezzo davvero se qualcuno potesse farlo e modificare questa risposta su stackoverflow.


1

Considera cosa succede se vai avanti e indietro tra B e C ... voilà

(rilevante solo se il grafico non è diretto)

Modificato: credo che il problema abbia a che fare con il fatto che il percorso con AC * può essere migliore di AB solo con l'esistenza di bordi di peso negativi, quindi non importa dove vai dopo AC, con l'ipotesi di non- bordi di peso negativi è impossibile trovare un percorso migliore di AB una volta scelto di raggiungere B dopo essere andato AC.


questo non è possibile, il grafico è diretto.
amit

@ amit: buon punto, me lo sono perso. È ora di riconsiderare il problema
prusswan

1

"2) Possiamo usare l'algoritmo di Dijksra per i percorsi più brevi per i grafici con pesi negativi - un'idea può essere, calcolare il valore di peso minimo, aggiungere un valore positivo (uguale al valore assoluto del valore di peso minimo) a tutti i pesi ed eseguire l'algoritmo di Dijksra per il grafico modificato. Questo algoritmo funzionerà? "

Questo non funziona assolutamente a meno che tutti i percorsi più brevi non abbiano la stessa lunghezza. Ad esempio, dato un percorso più breve di lunghezza due bordi, e dopo aver aggiunto un valore assoluto a ciascun bordo, il costo totale del percorso viene aumentato di 2 * | peso negativo massimo |. D'altra parte un altro percorso di lunghezza tre bordi, quindi il costo del percorso è aumentato di 3 * | max peso negativo |. Quindi, tutti i percorsi distinti vengono aumentati di quantità diverse.


0

È possibile utilizzare l'algoritmo di dijkstra con bordi negativi che non includono il ciclo negativo, ma è necessario consentire a un vertice di essere visitato più volte e quella versione perderà la sua complessità temporale veloce.

In quel caso praticamente ho visto che è meglio usare l' algoritmo SPFA che ha una coda normale e può gestire i bordi negativi.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.