Cammini minimi e algoritmo di Dijkstra
In questa pagina 5
Sia un grafo non diretto e pesato, con (esempio: rete di aeroporti con distanze in km). La lunghezza di un cammino è ; se per ogni arco è il numero di archi. La distanza è la minima lunghezza di un cammino da a e il cammino che la realizza è un cammino minimo.
Proposizione (sottostruttura ottima). Se è un cammino minimo da a , allora è un cammino minimo da a per ogni . Dimostrazione: se esistesse un cammino più corto da a , sostituendolo nel cammino originale si otterrebbe un cammino da a più corto, contro l'ipotesi.
SSSP (single-source shortest paths): dato , determinare le distanze da a tutti gli altri vertici e i relativi cammini minimi (rappresentati dai campi parent). Si assume che non abbia cicli di peso negativo (altrimenti la lunghezza potrebbe diventare arbitrariamente piccola). Se tutti i pesi sono basta la BFS in (vedi Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →).
Algoritmo di Dijkstra
Sviluppato nel 1956, richiede pesi non negativi. Generalizza la BFS: a partire da fa crescere una cloud di vertici di cui si conoscono già distanza da e cammino minimo.
- La cloud parte con .
- Ogni vertice ha
v.D= distanza corrente da (lunghezza del miglior cammino noto con vertici interni nella cloud) ev.parent= predecessore su quel cammino. - A ogni iterazione entra nella cloud il vertice esterno con
Dminimo; poi si rilassano gli archi con ancora fuori: seu.D + w(u,v) < v.Dsi ponev.D <- u.D + w(u,v)ev.parent <- u. - I vertici fuori dalla cloud stanno in una coda con priorità con chiave
v.D(vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →).
Algoritmo ShortestPaths(G, s)
Input: grafo non diretto G=(V,E,w), pesi >= 0, sorgente s
Output: distanze e cammini minimi da s, nei campi v.D e v.parent
s.D <- 0; s.parent <- null
forall v in V - {s} do v.D <- +infinito; v.parent <- null
Q <- coda con priorità con tutti i vertici (chiave v.D)
while Q non è vuota do
u <- Q.removeMin()
forall (u, v) in E con v in Q do
if u.D + w(u,v) < v.D then
v.D <- u.D + w(u,v); v.parent <- u
aggiorna Q per la nuova chiave di vEsempio svolto
Vertici e archi (peso): , , , , , , , , . Sorgente .
| Iterazione | Entra in cloud | Distanze correnti dopo il rilassamento |
|---|---|---|
| inizio | () | |
| 1 | () | (: ; : ) |
| 2 | () | (: ; : ) |
| 3 | () | (: ) |
| 4 | () | invariato (: ) |
| 5 | () | — |
Distanze da : , , , , . Cammini (da parent): ; ; ; ; . Quindi il cammino minimo è di lunghezza . (Verificato eseguendo un'implementazione con heap; a parità e entrano in cloud in uno dei due ordini.)
Correttezza
Segue da due lemmi (la dimostrazione del Lemma 2 non è richiesta nel dettaglio). Alla fine di ogni iterazione del while, per ogni :
- Lemma 1. Se
v.Dallorav.parentnull; sev.D, il cammino ottenuto risalendo da conparentè un cammino da a di lunghezzav.D. - Lemma 2. Quando viene estratto da ,
u.D.
Idea per il Lemma 2. Se per assurdo u.D , si considera un cammino minimo da a e il primo vertice fuori dalla cloud su di esso. Il predecessore di è in cloud, quindi l'arco verso è stato rilassato e z.D u.D (qui servono i pesi : ). Allora sarebbe stato estratto prima di , contraddizione.
Perché servono pesi non negativi. In un grafo diretto con archi di peso , di peso e di peso (nessun ciclo negativo) Dijkstra estrae con , ma il cammino vale . (In un grafo non diretto un solo arco negativo è già un ciclo negativo: si percorre avanti e indietro.)
Complessità
Dipende dall'implementazione della coda . Ogni vertice è estratto una volta ( removeMin), si fanno al più aggiornamenti di chiave (un rilassamento per arco), e ogni vertice mantiene un puntatore alla propria entry in .
| Operazione su | Heap | Lista doppiamente concatenata non ordinata |
|---|---|---|
| costruzione iniziale | ||
removeMin |
||
| aggiornamento di una chiave |
Nello heap l'aggiornamento di una chiave diminuita richiede un up-heap bubbling in (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →). Totale:
- heap: ;
- lista non ordinata: (perché ).
Teorema. La complessità di ShortestPaths(G, s) è : con la scelta migliore dell'implementazione. Per grafi sparsi () conviene lo heap (); per grafi densi () la lista ( contro ).
Errori comuni
- Usare Dijkstra con pesi negativi.
- Dimenticare l'aggiornamento di
parentquando si miglioraD(si ottengono le distanze ma non i cammini). - Rilassare anche archi verso vertici già nella cloud (inutile, non cambia la distanza finale).
- Dire che costa sempre : dipende dall'implementazione di ; non tiene conto degli aggiornamenti di chiave su un grafo denso.
Versione ripasso
- Grafo pesato : lunghezza del cammino = somma dei pesi; distanza = minima lunghezza. Sottostruttura ottima: i sottocammini di un cammino minimo sono minimi.
- SSSP: distanze e cammini da (campi
D,parent); niente cicli di peso negativo; pesi tutti ⇒ BFS (vedi Visite di grafi - BFS e DFSVisite in ampiezza (BFS) e in profondità (DFS) come design pattern; etichette discovery, cross e back edge; BFS tree e distanze; complessità Theta(n+m) con liste di adiacenza; applicazioni (connettività, componenti, spanning tree, cammini minimi non pesati, cicli, vertici a distanza al più d); esempio svolto su un grafo di 7 vertici.Visite di grafi - BFS e DFS →). - Dijkstra (pesi ): cloud inizialmente ; = coda con priorità dei vertici fuori dalla cloud con chiave
v.D(vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →); a ogni passoremoveMine rilassamento degli archi con : seu.D + w(u,v) < v.Dallorav.Dev.parentsi aggiornano (e si aggiorna ). - Esempio (, , , , , , , , da ): , , , , ; cammino .
- Correttezza: Lemma 1 (il cammino da
parentha lunghezzaD); Lemma 2 (all'estrazioneu.D, per assurdo con il primo vertice fuori cloud su un cammino minimo e pesi ). - Costo:
removeMin+ aggiornamenti. Heap: ; lista non ordinata: ; in totale (heap per grafi sparsi, lista per densi; vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →). - Pseudocodice:
s.D <- 0, altriD <- +inf,parent <- null; = tutti i vertici con chiaveD; finché non è vuota:Q.removeMin(); per ogni con : seu.D + w(u,v) < v.Dallora aggiornav.D,v.parent <- ue la chiave in . - Operazioni su (heap / lista non ordinata): costruzione / ;
removeMin/ ; aggiornamento di chiave / . - Errori: pesi negativi;
parentnon aggiornato; senza distinguere l'implementazione.