Qualcuno può dirmi perché l'algoritmo di Dijkstra per il percorso più breve da una singola sorgente presume che i bordi debbano essere non negativi.
Sto parlando solo dei bordi, non dei cicli di peso negativo.
Qualcuno può dirmi perché l'algoritmo di Dijkstra per il percorso più breve da una singola sorgente presume che i bordi debbano essere non negativi.
Sto parlando solo dei bordi, non dei cicli di peso negativo.
Risposte:
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.
A->B
sarà 5 e A->C
sarà 2. Poi B->C
sarà -5
. Quindi il valore di C
sarà lo -5
stesso di Bellman-Ford. Come mai questo non sta dando la risposta giusta?
A
con valore 0. Quindi, cercherà il nodo con valore minimo, B
è 5 ed C
è 2. Il minimo è C
, quindi si chiuderà C
con valore 2 e non guarderà mai indietro, quando successivamente B
è chiuso, non può modificare il valore di C
, poiché è già "chiuso".
A -> B -> C
? Prima aggiornerà C
la distanza a 2, quindi B
la 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 D
i 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?
Quando mi riferisco all'algoritmo di Dijkstra nella mia spiegazione, parlerò dell'algoritmo di Dijkstra come implementato di seguito,
Quindi iniziando i valori ( la distanza dalla sorgente al vertice ) inizialmente assegnati a ciascun vertice sono,
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,
Ora estraiamo C come (2 <5), ora Q = [B] . Nota che C non è connesso a nulla, quindi il line16
ciclo non viene eseguito.
Infine estraiamo B, dopodiché . Nota B ha un fronte diretto a C ma C non è presente in Q quindi di nuovo non entriamo nel ciclo for line16
,
Quindi finiamo con le distanze come
Nota come questo sia sbagliato poiché la distanza più breve da A a C è 5 + -10 = -5, quando vai .
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 line16
che 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 line16
essi controllano tutti i vicini v (cioè esiste un arco diretto da u a v ), di u che sono ancora in Q . In line14
essi rimuovono le note visitate da Q. Quindi se x è un vicino visitato di u , il percorso non è 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 non è stato considerato, lasciando invariato 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, che è 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 13
distanza 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.
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.
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.
Prova l'algoritmo di Dijkstra sul grafico seguente, assumendo che A
sia il nodo sorgente, per vedere cosa sta succedendo:
A->B
volontà 1
e A->C
volontà 100
. Allora lo B->D
farà 2
. Allora lo C->D
farà -4900
. Quindi il valore di D
sarà lo -4900
stesso di Bellman-Ford. Come mai questo non sta dando la risposta giusta?
A->B
sarà 1
e A->C
lo sarà 100
. Quindi B
viene esplorato e si mette B->D
a 2
. Quindi D viene esplorato perché attualmente ha il percorso più breve per tornare alla fonte? Avrei ragione nel dire che se lo B->D
fosse stato 100
, C
sarebbe stato esplorato per primo? Capisco tutti gli altri esempi forniti dalle persone tranne il tuo.
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.
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.
È 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.