.. Grafi (orientati): cammini minimi Una presentazione alternativa (con ulteriori dettagli)
Un algoritmo greedy per calcolare i cammini minimi da un vertice sorgente in un grafo orientato e pesato, senza cicli di peso negativo raggiungibili dal sorgente. Definiamo distanza di un vertice u da un vertice v: δ(v, u), in un grafo orientato e pesato, il peso di un cammino di peso minimo tra tutti i cammini da v a u. δ(v, u) = min {W(p) p cammino da v a u} dove W(p) è la somma dei pesi degli archi che formano il cammino. N.B. δ(v, u) è ben definita solo se nessun cammino tra v e u contiene un ciclo di peso negativo
Input: G = <V, E> grafo orientato e pesato, w: E R funzione peso, s V, G senza cicli di peso negativo raggiungibili da s Output: v V[G], l attributo d[v] indica la distanza di v dal vertice sorgente: d[v] = δ(s, v) Per costruire un algoritmo greedy, dobbiamo individuare l insieme di oggetti e definire per essi un grado di appetibilità. Si può pensare ai vertici come agli oggetti tra i quali scegliere; mantenendo per ogni vertice v, nell attributo d[v] una stima (maggiore o uguale) della distanza di v da s. Un vertice risulterà tanto più appetibile quanto minore è la stima della sua distanza dal sorgente stesso.
L idea dell algoritmo è la seguente: Inizialmente si stima la distanza di ogni vertice da s uguale a, tranne che per s stesso, di cui si conosce la distanza vera: δ(s, s) = 0. Si definisce pertanto d[s] = 0 Si costruisce poi un albero, di radice s, in cui viene inserito un vertice per volta: il più appetibile. L albero è memorizzato implicitamente come l insieme dei suoi archi < π[v], v> Quando un vertice u è inserito nell albero, si aggiornano le stime delle distanze dei vertici v ad esso adiacenti, in quanto potrebbe esitere un cammino da s a v, attraverso il vertice u, meno pesante del cammino da s a v considerato fino a quel momento.
Esempio 3 A 2 C B 4 3 3 E 1 D Se il vertice sorgente è A, si ha inizialmente: d[a] = 0, d[b] = d[c] = d[d] = d[e] = La strategia greedy sceglie percio il vertice A. Da A sono raggiungibili con un arco i vertici B, C e D. Si possono modificare pertanto le stime delle loro distanze da A. d[b] = 3, d[c] = 2, d[d] = 4, mentre resta d[e] =
3 A 2 C B 4 3 3 E 1 D Scegliendo C si scopre il cammino A - C - D, ma d[d] non viene modificata in quanto il suo peso (5) è maggiore del peso del cammino A - D (di peso 4). La scelta è ora tra i vertici B, D ed E, con d[b] = 3, d[d] = 4, d[e] =, e la stategia greedy impone di scegliere B. Si scopre così il cammino A - B - E e diventa d[e] = 6. La scelta di D permette infine di ottenere d[e] = 5, ed E sarà l ultimo vertice scelto.
Usiamo il secondo schema greedy e scriviamo una prima approssimazione dell algoritmo, dove S è l insieme dei vertici già considerati: Dijkstra (G, W, s) for ogni u V do d[u] π[u] nil d[s] 0 S Φ while S V[G] do u vertice con d minore tra quelli non ancora scelti S S {u} for ogni v ADJ[u] do {valuta le appetibilità iniziali dei vertici} {ci sono elementi da scegliere} if v S and d[v] > d[u] + W(u, v) then d[v] d[u] + W(u, v) π[v] u {scegli il vertice più appetibile} {aggiorna le appetibilità dei vertici}
Non sempre la strategia funziona! Ad esempio: 5 A 2 C B 3 2-3 E 1 D Se il vertice sorgente è A, dopo le opportune modifiche alle stime delle distanze, vengono scelti, nell ordine: A (quando: d[a] = 0, d[b] = d[c] = d[d] = d[e] = ) C (quando: d[b] = 5, d[c] = 2, d[d] = 3, d[e] = ) D (quando: d[b] = 5, d[d] = 3, d[e] = ) E (quando: d[b] = 5, d[e] = 4) B (quando: d[b] = 5) Ma δ(a, E) = 2, mentre d[e] = 4
Nell esempio considerato è facile capire che il non corretto comportamento è dovuto alla presenza dell arco <B, E>, il cui peso è negativo. Allora, se la strategia greedy funziona, essa funziona solo in assenza di archi di peso negativo. Riscriviamo l algoritmo di Dijkstra aggiungendo come precondizione la richiesta che la funzione peso non assuma valori negativi. {G = <V, E> grafo orientato e pesato & s V & & W: E R + {0}}
{G = <V, E> grafo orientato e pesato & s V & & W: E R + {0}} Dijkstra (G, W, s) for ogni u V do d[u] π[u] nil d[s] 0 S Φ while S V[G] do Usiamo una coda con priorità u vertice con d minore tra quelli non ancora scelti S S {u} for ogni v ADJ[u] do if v S and d[v] > d[u] + W(u, v) then d[v] d[u] + W(u, v) π[v] u
{G = <V, E> grafo orientato e pesato & s V & & W: E R + {0}} Dijkstra (G, W, s) Q V[G] for ogni u V do d[u] π[u] nil d[s] 0 S Φ while S Q Φ V[G] do u vertice Extract-min con d (Q) minore minimatra quelli non ancora scelti S S {u} for ogni v ADJ[u] do if v QS and d[v] > d[u] + W(u, v) then d[v] d[u] + W(u, v) π[v] u
Ricorda un altro algoritmo che conosciamo L algoritmo di PRIM
MST-Prim (G, W, r) for ogni u Q do key[u] π[r] nil key[r] 0 Q V[G] while Q Φdo u Extract-min(Q) MST-Prim (G, W, r) for ogni u Q do key[u] π[u] nil key[r] 0 S Φ Q V[G] while Q Φdo u Extract-min(Q) for ogni v Adj[u] do if v Q e W(u, v) < key[v] then key[v] W(u, v) π[v] u S S {u} for ogni v Adj[u] do if v Q e W(u, v) < key[v] then key[v] W(u, v) π[v] u
ST-Prim (G, W, r) for ogni u Q do key[u] Dijkstra (G, W, s) for ogni u Q do d[u] π[u] nil key[r] 0 S Φ Q V[G] while Q Φdo u Extract-min(Q) S S {u} for ogni v Adj[u] do if v Q e W(u, v) < key[v] then key[v] W(u, v) π[v] u π[u] nil d[r] 0 S Φ Q V[G] while Q Φdo u Extract-min(Q) S S {u} for ogni v ADJ[u] do if v Q and d[u] + W(u, v) < d[v then d[v] d[u] + W(u, v) π[v] u
La complessità dell algoritmo di Dijkstra è pertanto la stessa dell algoritmo di Prim: coda realizzata con heap binario: O(E logv) coda realizzata con un heap di Fibonacci: O(E + V logv) Dobbiamo ancora dimostrare che l algoritmo è corretto
{G = <V, E> grafo orientato e pesato & s V & & W: E R + {0}} Dijkstra (G, W, s) for ogni u V do d[u] π[u] nil d[s] 0 Q V[G] S Φ { t S: d[t] = δ(s, t) & S Q = V[G] & S Q = Φ} while Q Φdo u Extract-min (Q) S S {u} for ogni v ADJ[u] do if v Q and d[u] + W(u, v) < d[v] then d[v] d[u] + W(u, v) π[v] u { t V[G] : d[t] = δ(s, t)}
Proprietà 1. Un sottocammino di un cammino minimo è minimo. Dimostazione. Siano x e y due vertici qualunque in un cammino minimo p da u a v: p 1 p 2 p 3 p = u x y v = p 1 p 2 p 3 W(p) = W(p 1 ) + W(p 2 ) + W(p 3 ) Se il sottocammino p 2 da x a y non fosse minimo, ne esisterebbe un altro p 2 di peso inferiore. In tal caso il cammino p = p 1 p 2 p 3 sarebbe un cammino da u a v con W(p ) < W(p). Assurdo.
Proprieta` 2. Le seguenti asserzioni: 2.1 v V[G]: v S d[v] non viene modificata 2.2 v Q: π[v] nil π[v] S 2.3 v V[G] - {s}: d[v] π[v] nil 2.4 v V[G] - {s}: d[v] d[v] = d[π[v]] + W(π[v], v) sono invarianti del ciclo while. Dimostrazione. Ovvia esaminando il corpo del while. u Extract-min (Q) S S {u} for ogni v ADJ[u] do if v Q and d[u] + W(u, v) < d[v] then d[v] d[u] + W(u, v) π[v] u
Proprieta` 3. La seguente asserzione: v S: d[v] esiste un cammino da s a v in G e` invariante del ciclo while. Dimostrazione. ( ) Supponiamo l asserzione vera e dimostriamo che, se per il vertice u estratto dalla coda d[u], il cammino esiste. Per ipotesi induttiva esiste un cammino da s a π[u]. Allora il cammino da s a π[u] piu` l arco < π[u], u> costituisce un cammino da s a u. ( ) Se u viene estratto da Q con d[u] =, allora (*) tutti i vertici t in Q hanno d[t] =. Supponiamo che tra s e u vi sia almeno un cammino: s v 1 v 2... v k-1 v k u. Allora, tutti i vertici v i sul cammino devono avere d[v i ] = (se v l avesse d[v l ], allora per (*) v l S, quindi anche v i+1 avrebbre d[v i+1 ] ). Ma questo è assurdo perché d[s] = 0.
Dimostriamo ora che il predicato t S: d[t] = δ(s, t) & S Q = V[G] & S Q = Φ è un invariante del ciclo while. Il predicato è vero all inizio poichè S è vuoto e Q = V[G]. Supponiamo che sia vero quando l albero è stato costruito parzialmente, e dimostriamo che per il nuovo vertice u estratto da Q, d[u] = δ(s, u). s r x Per ipotesi induttiva t S: d[t] = δ(s, t).
Sia u il vertice estratto da Q. r s u x Si hanno due casi: 1. d[u] Sia π[u] = r (proprietà 2.3 ). Sappiamo allora (proprietà 2.2) che r S, cioè all albero e che (proprietà 2.4) d[u] = d[r] + W(r, u). Supponiamo che tra s e u esista un cammino di peso minore di d[u]; esso deve contenere un arco tra un vertice in S e uno in Q: <x, y>. y
Questo cammino tra s e u può allora essere visto come la concatenazione di tre cammini s x y u Se s x y u è minimo, anche s x y è minimo (proprietà 1), quindi d[y], che è il suo peso, è uguale a δ(s, y). W(s x y u) = W(s x y) + W(y u) = d[y] + W(y u) d[y] (W(y u) 0 perchè la funzione peso non può assumere valori negativi) d[u] ( perchè u è il vertice estratto da Q) d[u] = δ(s, u)
2. d[u] = Se il vertice u viene estratto quando d[u] = allora non esiste nessun cammino tra s e u (proprietà 3), cioè d[u] = = δ(s, u) t (S {u}): d[t] = δ(s, t) & (S {u}) (Q - {u}) = V[G] & (S {u}) (Q - {u}) = Φ
Riepilogo Algoritmo di Dijkstra per il calcolo di cammini minimi a sorgente singola, in un grafo orientato senza pesi negativi. Relazione con l algoritmo di Prim per il calcolo del minimo albero ricoprente in un grafo non orientato.