Perché l'algoritmo di Dijkstra non funziona per i bordi di peso negativo?


Risposte:


175

Ricorda che nell'algoritmo di Dijkstra, una volta che un vertice è contrassegnato come "chiuso" (e fuori dall'insieme aperto) - l'algoritmo ha trovato il percorso più breve per raggiungerlo e non dovrà mai più sviluppare questo nodo - assume il percorso sviluppato per questo il percorso è il più breve.

Ma con pesi negativi, potrebbe non essere vero. Per esempio:

       A
      / \
     /   \
    /     \
   5       2
  /         \
  B--(-10)-->C

V={A,B,C} ; E = {(A,C,2), (A,B,5), (B,C,-10)}

Dijkstra da A svilupperà prima C, e in seguito non riuscirà a trovare A->B->C


MODIFICA una spiegazione un po 'più profonda:

Si noti che questo è importante, perché in ogni fase di rilassamento, l'algoritmo assume che il "costo" per i nodi "chiusi" sia davvero minimo, e quindi anche il nodo che verrà selezionato successivamente è minimo.

L'idea è: se abbiamo un vertice aperto in modo tale che il suo costo sia minimo - aggiungendo un numero positivo a qualsiasi vertice - la minimalità non cambierà mai.
Senza il vincolo sui numeri positivi, l'ipotesi di cui sopra non è vera.

Dato che "sappiamo" che ogni vertice "chiuso" è minimo - possiamo tranquillamente fare la fase di rilassamento - senza "voltarci indietro". Se abbiamo bisogno di "guardare indietro", Bellman-Ford offre una soluzione ricorsiva (DP) per farlo.


5
Scusa ma non ricevo alcun errore. Prima A->Bsarà 5 e A->Csarà 2. Poi B->Csarà -5. Quindi il valore di Csarà lo -5stesso di Bellman-Ford. Come mai questo non sta dando la risposta giusta?
Anirban Nag 'tintinmj'

5
@tintinmj prima, Dijkstra "chiuderà" il nodo Acon valore 0. Quindi, cercherà il nodo con valore minimo, Bè 5 ed Cè 2. Il minimo è C, quindi si chiuderà Ccon valore 2 e non guarderà mai indietro, quando successivamente Bè chiuso, non può modificare il valore di C, poiché è già "chiuso".
amit

4
@amit Come l'algoritmo di Dijkstra non trova il percorso A -> B -> C? Prima aggiornerà Cla distanza a 2, quindi Bla distanza a 5. Supponendo che nel tuo grafico non ci siano bordi in uscita da C, allora non facciamo nulla durante la visita C(e la sua distanza è ancora 2). Quindi visitiamo Di nodi adiacenti e l'unico nodo adiacente è C, la cui nuova distanza è -5. Nota che nell'algoritmo di Dijkstra, teniamo anche traccia del genitore da cui raggiungiamo (e aggiorniamo) il nodo e, facendolo C, otterrai il genitore B, e quindi A, ottenendo un risultato corretto. Cosa mi manca?
nbro

12
@amit Il problema con il tuo ragionamento (credo), e ho visto altre persone farlo (stranamente), è che pensi che l'algoritmo non riconsidererà i nodi la cui distanza più breve è già stata determinata (e che abbiamo finito), ma questo non è corretto, ed è per questo che abbiamo il passaggio "rilassamento" ... iteriamo attraverso tutti i nodi del grafo e, per ciascuno di essi, iteriamo attraverso i nodi adiacenti, anche se uno qualsiasi dei nodi adiacenti potrebbe sono già stati rimossi dalla nostra coda con priorità minima, ad esempio.
nbro

10
@amit Controllare questa risposta a una domanda simile, in cui l'esempio in realtà ha un senso: stackoverflow.com/a/6799344/3924118
nbro

37

Considera il grafico mostrato sotto con la fonte come Vertex A. Prima prova a eseguire tu stesso l'algoritmo di Dijkstra su di esso.

inserisci qui la descrizione dell'immagine

Quando mi riferisco all'algoritmo di Dijkstra nella mia spiegazione, parlerò dell'algoritmo di Dijkstra come implementato di seguito,

Algoritmo di Dijkstra

Quindi iniziando i valori ( la distanza dalla sorgente al vertice ) inizialmente assegnati a ciascun vertice sono,

inizializzazione

Per prima cosa estraiamo il vertice in Q = [A, B, C] che ha il valore più piccolo, cioè A, dopodiché Q = [B, C] . Nota A ha un bordo diretto a B e C, inoltre entrambi sono in Q, quindi aggiorniamo entrambi questi valori,

prima iterazione

Ora estraiamo C come (2 <5), ora Q = [B] . Nota che C non è connesso a nulla, quindi il line16ciclo non viene eseguito.

seconda iterazione

Infine estraiamo B, dopodiché Q è Phi. Nota B ha un fronte diretto a C ma C non è presente in Q quindi di nuovo non entriamo nel ciclo for line16,

3 °?

Quindi finiamo con le distanze come

nessun cambiamento ragazzi

Nota come questo sia sbagliato poiché la distanza più breve da A a C è 5 + -10 = -5, quando vai da a a b a c .

Quindi per questo grafico l'algoritmo di Dijkstra calcola erroneamente la distanza da A a C.

Ciò accade perché l'algoritmo di Dijkstra non cerca di trovare un percorso più breve per i vertici che sono già estratti da Q .

Quello line16che sta facendo il ciclo è prendere il vertice u e dire "hey sembra che possiamo andare a v dalla sorgente tramite u , questa distanza (alt o alternativa) è migliore della dist [v] corrente che abbiamo? Se è così, aggiorniamo dist [v] "

Notare che in line16essi controllano tutti i vicini v (cioè esiste un arco diretto da u a v ), di u che sono ancora in Q . In line14essi rimuovono le note visitate da Q. Quindi se x è un vicino visitato di u , il percorso nonsorgente da u a x è nemmeno considerato come una possibile via più breve dalla sorgente a v .

Nel nostro esempio sopra, C era un vicino visitato di B, quindi il percorso Da A a B a Cnon è stato considerato, lasciando Dalla A alla Cinvariato il percorso più breve corrente .

Ciò è effettivamente utile se i pesi degli spigoli sono tutti numeri positivi , perché in tal caso non sprecheremmo il nostro tempo a considerare percorsi che non possono essere più brevi.

Quindi dico che quando si esegue questo algoritmo se x viene estratto da Q prima di y , non è possibile trovare un percorso, non possibileche è più breve. Lasciatemi spiegare questo con un esempio,

Come y è stato appena estratto e x è stato estratto prima di se stesso, allora dist [y]> dist [x] perché altrimenti y sarebbe stato estratto prima di x . ( line 13distanza minima prima)

E poiché abbiamo già assunto che i pesi degli archi siano positivi, ovvero lunghezza (x, y)> 0 . Quindi la distanza alternativa (alt) tramite y sarà sempre maggiore, cioè dist [y] + length (x, y)> dist [x] . Quindi il valore di dist [x] non sarebbe stato aggiornato anche se y fosse stato considerato come un percorso per x , quindi concludiamo che ha senso considerare solo i vicini di y che sono ancora in Q (nota il commento in line16)

Ma questa cosa dipende dalla nostra ipotesi di lunghezza del bordo positiva, se la lunghezza (u, v) <0 allora a seconda di quanto sia negativo quel bordo potremmo sostituire dist [x] dopo il confronto in line18.

Quindi qualsiasi dist [x] calcolo di che faremo sarà errato se x viene rimosso prima che tutti i vertici v - in modo tale che x sia un vicino di v con un bordo negativo che li collega - vengono rimossi.

Perché ognuno di quei vertici v è il penultimo vertice su un potenziale percorso "migliore" dalla sorgente a x , che viene scartato dall'algoritmo di Dijkstra.

Quindi, nell'esempio che ho fornito sopra, l'errore era perché C è stato rimosso prima che B fosse rimosso. Mentre quel C era un vicino di B con un lato negativo!

Giusto per chiarire, B e C sono i vicini di A. B ha un unico vicino C e C non ha vicini. length (a, b) è la lunghezza del bordo tra i vertici a e b.


2
Come hai detto, il modo migliore per risolvere questo problema è utilizzare il metodo heapq.heappush dopo ogni confronto. Spingiamo indietro la distanza aggiornata nella coda. In questa condizione, i Dijkstra possono lavorare su pesi negativi. Ho provato e il risultato è stato 0,5, -5
nosense

1
"il percorso sorgente da x a u non è nemmeno considerato"; intendevi fonte per u a x?
slmatrix

1
@slmatrix grazie per averlo capito, sì, volevo dire che il percorso dalla sorgente a u a x, perché x è un vicino di u.
Aditya P

23

L'algoritmo di Dijkstra presume che i percorsi possano solo diventare `` più pesanti '', quindi se hai un percorso da A a B con un peso di 3 e un percorso da A a C con un peso di 3, non c'è modo di aggiungere un bordo e andare da A a B a C con un peso inferiore a 3.

Questa ipotesi rende l'algoritmo più veloce degli algoritmi che devono prendere in considerazione pesi negativi.


8

Correttezza dell'algoritmo di Dijkstra:

Abbiamo 2 set di vertici in ogni fase dell'algoritmo. L'insieme A è costituito dai vertici per i quali abbiamo calcolato i cammini minimi. L'insieme B è costituito dai vertici rimanenti.

Ipotesi induttiva : ad ogni passaggio assumeremo che tutte le iterazioni precedenti siano corrette.

Passo induttivo : quando aggiungiamo un vertice V all'insieme A e impostiamo la distanza come dist [V], dobbiamo dimostrare che questa distanza è ottimale. Se questo non è ottimale, deve esserci un altro percorso al vertice V di lunghezza inferiore.

Supponiamo che questo altro percorso attraversi un vertice X.

Ora, poiché dist [V] <= dist [X], quindi qualsiasi altro percorso verso V sarà almeno di lunghezza dist [V], a meno che il grafico non abbia lunghezze di fronte negative.

Pertanto, affinché l'algoritmo di dijkstra funzioni, i pesi degli archi devono essere non negativi.


6

Prova l'algoritmo di Dijkstra sul grafico seguente, assumendo che Asia il nodo sorgente, per vedere cosa sta succedendo:

Grafico


6
Scusa ma non ricevo alcun errore. Prima A->Bvolontà 1e A->Cvolontà 100. Allora lo B->Dfarà 2. Allora lo C->Dfarà -4900. Quindi il valore di Dsarà lo -4900stesso di Bellman-Ford. Come mai questo non sta dando la risposta giusta?
Anirban Nag 'tintinmj'

9
@tintinmj Se hai un bordo in uscita da D, verrà visitato prima che la distanza di D sia diminuita e quindi non aggiornato dopo che lo è. Ciò risulterà quindi sicuramente in un errore. Se consideri D's 2 come la distanza finale già dopo aver scansionato i bordi in uscita, anche questo grafico risulta in un errore.
Christian Schnorr

@ tb- Scusa per averlo chiesto dopo così tanto tempo ma sono sulla strada giusta qui? Il primo A->Bsarà 1e A->Clo sarà 100. Quindi Bviene esplorato e si mette B->Da 2. Quindi D viene esplorato perché attualmente ha il percorso più breve per tornare alla fonte? Avrei ragione nel dire che se lo B->Dfosse stato 100, Csarebbe stato esplorato per primo? Capisco tutti gli altri esempi forniti dalle persone tranne il tuo.
Pejman Poh

@PejmanPoh dalla mia comprensione, se B-> D era 100, poiché A-> C è più alto nella HeapStructure che verrà utilizzata, l'estratto min restituirà prima A-> C, il che significa che il percorso più breve trovato successivo sarà il percorso a C, dopodiché il percorso da C-> D che ha peso -5000 sarà la scelta più ovvia, portandoci alla conclusione che il percorso più breve sarebbe da A-> C-> D e sono abbastanza sicuro che sarebbe essere il comportamento normale. Quindi a volte quando abbiamo cicli negativi potremmo comunque ottenere il valore giusto per il percorso più breve, ma sicuramente non sempre, questo è un esempio in cui non lo faremo ..
T.Dimitrov

1

Ricorda che nell'algoritmo di Dijkstra, una volta che un vertice è contrassegnato come "chiuso" (e fuori dall'insieme aperto), si presume che qualsiasi nodo originato da esso condurrà a una distanza maggiore , quindi l'algoritmo ha trovato il percorso più breve per raggiungerlo e non sarà mai necessario sviluppare nuovamente questo nodo, ma questo non vale in caso di pesi negativi.


0

Le altre risposte finora dimostrano abbastanza bene perché l'algoritmo di Dijkstra non può gestire pesi negativi sui percorsi.

Ma la domanda stessa è forse basata su una comprensione errata del peso dei percorsi. Se i pesi negativi sui percorsi fossero consentiti negli algoritmi di pathfinding in generale, si otterrebbero cicli permanenti che non si fermerebbero.

Considera questo:

A  <- 5 ->  B  <- (-1) ->  C <- 5 -> D

Qual è il percorso ottimale tra A e D?

Qualsiasi algoritmo di pathfinding dovrebbe eseguire un loop continuo tra B e C perché così facendo si ridurrebbe il peso del percorso totale. Quindi consentire pesi negativi per una connessione renderebbe discutibile qualsiasi algoritmo di ricerca del percorso, forse tranne se si limita ogni connessione a essere utilizzata solo una volta.


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.