Metodi diide-et-impera, programmazione dinamica e algoritmi greed La programmazione dinamica, come il metodo diide-et-impera, risole n problema mettendo insieme le solzioni di n certo nmero di sottoproblemi. In particolare i metodi diide-et-impera sddiidono il problema in sottoproblemi indipendenti, li risole ricorsiamente e la solzione del problema originale iene determinata fondendo assieme le solzioni dei sottoproblemi. La programmazione dinamica, inece, si pò applicare anche qando i sottoproblemi non sono indipendenti: la risolzione di alcni sottoproblemi richiede di risolere i medesimi sottoproblemi. In qesti casi gli algoritmi del tipo diide-et-impera farebbero più laoro di qello strettamente necessario doendo risolere più olte gli stessi sottoproblemi. Gli algoritmi di programmazione dinamica risolono ciascn sottoproblema na sola olta e memorizzano la solzione in na tabella; in qesto modo si eita di calcolare noamente la solzione ogni olta che dee essere risolto lo stesso sottoproblema. Per alcni problemi di ottimizzazione, comnqe, la scelta di metodologie di programmazione dinamica non è sempre la migliore in qanto è possibile realizzare algoritmi più semplice ed efficienti. Gli algoritmi greed adottano la strategia di prendere qella decisione che, al momento, appare come la migliore. In altri termini, iene sempre presa qella decisione che, localmente, è la decisione ottima, senza preoccparsi se tale decisione porterà a na solzione ottima del problema nella sa globalità. Non è sempre facile o possibile silppare n algoritmo greed ma i sono particolari classi di problemi che si prestano bene ad approcci greed. Una di qeste classi di problemi è qella della ricerca in grafi pesati di percorsi a minimo costo, anzi in qesti casi sono stati silppati approcci greed considerati ad oggi i più efficienti. I grafi I grafi sono strttre dati molto diffse in ottimizzazione e in informatica. Un grafo è rappresentato graficamente tramite na strttra a nodi ed archi. I nodi possono essere isti come eenti dal qale si dipartono differenti alternatie (gli archi). Tipicamente i grafi engono tilizzati per rappresentare in modo binioco na rete (di calcolatori, stradale, di serizi, ): i nodi rappresentano i singoli calcolatori, gli incroci stradali o le fermate di atobs e gli archi i collegamenti elettrici o le strade. Un grafo iene indicato in modo compatto con G = (V, E), doe V indica l insieme dei nodi ed E l insieme degli archi che lo costitiscono, il nmero di nodi è V e il nmero di archi E. Vi sono dierse tipologie di grafi: si parla di grafi non orientati qelli per i qali gli archi non hanno n erso di percorrenza in contrapposizione a qelli orientati. Ad esempio i grafi non orientati - 48 -
engono tilizzati per rappresentare reti di calcolatori con collegamenti sincroni per la trasmissione dei dati, i grafi orientati possono rappresentare reti iarie, permettendo la rappresentazione di doppi sensi e sensi nici. Si parla di grafi connessi se da n qalnqe nodo si pò raggingere ttti gli altri nodi del grafo. Si parla di grafi pesati se ad n ogni arco (, ) E, con, V, è associato n peso, normalmente definito da na fnzione peso w: E R. Il peso pò essere isto come n costo o la distanza fra i de nodi che l arco nisce. Il costo pò essere dipendente dal flsso che attraersa l arco tramite na legge. In tal senso la fnzione w pò essere lineare o meno e dipendere dal flsso che attraersa l arco (reti non congestionate) o anche dal flsso degli archi icini (reti congestionate). Vi sono de modi standard di rappresentare n grafo: come na collezione di liste di adiacenza o come na matrice di adiacenza. Di solito si preferisce la prima perché fornisce n modo compatto di rappresentare grafi sparsi, cioè qelli per ci E è molto inferiore rispetto V, mentre il secondo tipo di rappresentazione è tilizzato per grafi densi, ossia E V. La rappresentazione con liste di adiacenza consiste in n ettore di V liste, na per ogni ertice. Per ogni nodo V, la lista di adiacenza Adj[] contiene ttti i ertici tali che esista n arco (, ) E; qindi Adj[] comprende ttti i ertici adiacenti ad in G. In ogni lista di adiacenza i ertici engono di solito memorizzati in ordine arbitrario. 3 4 3 4 4 4 3 3 4 Se G è n grafo orientato, la somma delle lnghezze di ttte le liste di adiacenza è E, perché n arco della forma (, ) è rappresentato ponendo in Adj[]. Se il grafo non è orientato la somma delle lnghezze di ttte le liste di adiacenza è E, dato che appare nella lista di adiacenza di e iceersa. La rappresentazione con liste di adiacenza è robsta, ossia pò essere adattata a molte arianti di grafi, ma qalnqe sia il tipo di grafo l occpazione di memoria massima è O(V+E). Per la rappresentazione con matrice di adiacenza di n grafo G = (V,E), si assme che i ertici siano nmerati,,, V in modo arbitrario. La rappresentazione consiste in na matrice A = (a ij ) di dimensione V V tale che a ij = se (i, j) E, 0 altrimenti. Nel caso del grafo precedente la matrice di adiacenza è: - 49 -
3 4 0 0 0 0 3 0 0 0 4 0 0 0 0 La memoria occpata è Θ(V ) indipendentemente dal nmero di archi. Con la matrice di incidenza si possono rappresentare atocicli come nelle liste di adiacenza ma non il caso di più archi che collegano de nodi. Si noti che nel caso di grafi non orientati la matrice di adiacenza è simmetrica e qindi basterebbe tilizzare i dati sopra la diagonale principale (diagonale inclsa) ridcendo di qasi la metà il nmero di dati da memorizzare. Cammini minimi Sia dato il grafo pesato G = (V,E). Un cammino da n nodo ad n nodo è na seqenza di archi contigi che nisce i de nodi. Il cammino iene rappresentato in modo indicizzato come seqenza di nodi: p = ( 0,,, i,, k ) oe i V, ( i, i+ ) E, i =,,k, 0 = e k =. Sia dato il grafo orientato e pesato G = (V, E), con fnzione di peso w: E R. Il peso di n cammino p è la somma dei pesi degli archi che lo costitiscono: k i i= w(p) = w(, ). i Il peso di cammino minimo da a è definito come min{ ( ):, δ(, )= w p p se esiste n cammino da a altrimenti. Un cammino minimo dal ertice al ertice è definito come n qalnqe cammino p con peso w(p) = δ(, ). Il peso degli archi pò essere inteso come na distanza, ma anche come tempo, costo, penalità, perdita o qalnqe altra grandezza che si accmla in modo lineare e che si ole minimizzare. Vi sono dierse arianti del problema, fra le qali le de più importanti sono: cammino minimo fra na coppia di nodi, e alberi di cammini minimi. Nel primo caso si cerca n cammino minimo fra de nodi del grafo, nel secondo si cercano i cammini minimi fra n nodo e ttti gli altri della rete. - 0 -
Rappresentazione dei cammini minimi Spesso non è importante solo conoscere il peso di cammino minimo, ma anche i ertici di tale cammino. Dato n grafo G = (V, E), si mantenga per ogni ertice V n predecessore π[] che è n altro ertice oppre NIL (ossia Nlla). Gli algoritmi di cammino minimo che edremo tilizzano l operatore π in modo tale che la catena dei predecessori che parte da n ertice sega in direzione inersa, n cammino minimo da s a. Drante l eseczione dell algoritmo i alori di π, tttaia, possono non indicare i cammini minimi. In realtà saremo interessati al sottografo dei predecessori G π = (V π, E π ) indotto dai alori di π doe: V π è l insieme di ertici di G con predecessore dierso da NIL ossia V π ={ V: π[] NIL} {s} E π è l insieme di archi orientati indotto da π per i ertici V π, cioè E π ={(π[], ) E: V π -{s}}. Gli algoritmi che edremo aranno la proprietà che alla fine G π sarà n albero di cammini minimi con radice s. Sottostrttra ottima di n cammino minimo Gli algoritmi di cammino minimo normalmente sfrttano la proprietà che n cammino minimo tra de ertici contiene al so interno altri cammini minimi. Qesta proprietà di sottostrttra garantisce l applicabilità sia della programmazione dinamica che del metodo greed. Lemma - Sottocammini di cammini minimi sono cammini minimi Dato n grafo orientato e pesato G = (V, E) con na fnzione peso w: E R, sia p = (,,, k ) n cammino minimo dal ertice al ertice k, e per ogni i e j tali che i j k, sia p ij = ( i, i+,, j ) il sottocammino di p dal ertice i al ertice j. Allora p ij è n cammino minimo da i a j. Dimostrazione Se si decompone il cammino p come p = p i + p ij + p jk, allora w(p) = w(p i ) + w(p ij ) + w(p jk ). Si spponga che esiste n cammino p ij con peso w(p ij ) w(p ij ). Allora p = p i + p ij + p jk sarebbe n cammino da a k, con peso inferiore di p contraddicendo l ipotesi. - -
Corollario Sia G = (V, E) n grafo orientato e pesato con fnzione peso w: E R. Si spponga che n cammino minimo p da na sorgente s ad n ertice possa essere decomposto in s per n qalche ertice e cammino p. Allora il peso di cammino minimo da s a è δ(s, ) = δ(s, ) + w(, ). Dimostrazione Per il lemma il sottocammino p è n cammino minimo dalla sorgente s al ertice. Qindi δ(s, ) = w(p) = w(p ) + w(, ) =δ(s, ) + w(, ) Lemma p' Sia G = (V, E) n grafo orientato e pesato con fnzione peso w: E per ogni arco (, ) E ale δ(s, ) δ(s, ) + w(, ). R e ertice sorgente s. Allora Rilassamento Gli algoritmi che edremo sano la tecnica del rilassamento. Per ogni ertice V, si mantiene n attribto d[] che costitisce n limite speriore al peso di n cammino minimo dalla sorgente s a : chiameremo d[] na stima del cammino minimo. Le stime di cammino minimo e i predecessori engono inizializzati dalla segente procedra: Inizializza (G, s) for ogni ertice V do d[] 3 π[] NIL 4 d[s] 0 Il processo di rilassare n arco (, ) consiste nel erificare se si pò migliorare il cammino minimo per troato fino a qel momento passando per, e, in qesto caso, nell aggiornare d[] e π[]. Il rilassamento sarà la segente procedra: Rilassa(,, w) if d[] > d[] + w(, ) then d[] d[] + w(, ) 3 π[] = - -
Algoritmo di Dijkstra L algoritmo di Dijkstra tilizza na strategia greed per risolere il problema di cammini minimi con sorgente singola s di n grafo orientato e pesato G = (V, E) nel caso in ci ttti i pesi degli archi siano non negatii. L algoritmo mantiene n insieme S che contiene i ertici il ci peso di cammino minimo dalla sorgente s è già stato determinato, cioè per ttti i ertici S ale d[] = δ(s, ). L algoritmo seleziona ripettamente i ertici V - S con la minima stima di cammino minimo, inserisce in S, e rilassa ttti gli archi scenti da. Inoltre iene mantenta na coda con priorità Q che contiene ttti i ertici V - S, sando come chiae i rispettii alori d ; la realizzazione assme che il grafo G sia rappresentato con liste di adiacenza. Dijkstra(G, w, s) Inizializza(G, s) S 3 Q V 4 while Q do Estrai_Minimo(Q) 6 S S {} for ogni ertice Adj[] 8 do Rilassa(,, w) La linea esege la normale inizializzazione dei alori d e π, e la linea inizializza l insieme S con l insieme oto. La linea 3 inizializza la coda con priorità Q con ttti i ertici in V - S = V - = V. Ogni olta che si esege il ciclo while delle linee 4-8, n ertice iene estratto da Q = V - S e iene inserito nell insieme S (la prima olta che iene esegito il ciclo, = s). Il ertice ha qindi la minima stima di cammino minimo tra ttti i ertici in V - S. Qindi le linee -8 rilassano ogni arco (, ) che esce da, aggiornando la stima d[] ed il predecessore π[] se il cammino minimo per pò essere migliorato passando per. Si noti che nessn ertice iene inserito in Q dopo la linea 3, e che ogni ertice iene estratto da Q ed inserito in S esattamente na olta, qindi il ciclo while delle linee 4-8 iene ripetto esattamente V olte. La conergenza all ottimo dell algoritmo di Dijkstra è stata dimostrata. Qanto è eloce l algoritmo di Dijkstra? Consideriamo il caso in ci mantiene la coda con priorità Q = V - S con n arra lineare. Infatti Estrai_Minimo è na fnzione che estrae il minimo elemento in n ettore. Estrai_Minimo richiede n tempo O(V), e poiché i sono V operazioni di qesto tipo il tempo totale richiesto da Estrai_Minimo è O(V ). Ogni ertice V iene inserito nell insieme S esattamente na olta, e qindi ogni arco nella lista di adiacenza Adj[] iene - 3 -
esaminato nel ciclo for delle linee 4-8 esattamente na olta nel corso dell algoritmo. Poiché il nmero totale di archi in ttte le liste di adiacenza è E, i sono in totale E iterazioni di qesto ciclo for, ognna delle qali richiede tempo O(). Qindi il tempo totale di eseczione dell algoritmo è O(E + V ) = O(V ). Nel caso di grafi sparsi se si tilizza n heap binario per rappresentare la coda con priorità Q, si ottiene il cosiddetto algoritmo di Dijkstra modificato che richiede n tempo di calcolo totale pari a O(ElgV) se ttti i ertici sono raggingibili dalla sorgente. Tale tempo pò essere abbassato a O(VlgV + E) sando heap di Fibonacci. Esempio 0 0 0 0 3 9 4 6 3 9 4 6 a) b) 8 4 0 3 9 4 6 c) d) 8 3 3 9 4 6-4 -
s 0 0 8 9 8 9 0 3 9 4 6 3 9 4 6 e) f) Una eseczione dell algoritmo di Dijkstra. La sorgente è il ertice s. Le stime di cammino minimo sono indicate all interno dei ertici, e gli archi in neretto indicano i alori del campo predecessore: se l arco (, ) è in neretto, allora π[] =. I ertici grigi sono nell insieme S, mentre i ertici bianchi sono nella coda Q. (a) La sitazione sbito prima della prima iterazione del ciclo while delle linee 4-8. Il ertice in grigio ha il alore minimo di d ed è scelto come ertice nella linea. (b)-(f) la sitazione dopo ogni iterazione sccessia del ciclo while. Il ertice in grigio è il noo nodo. Algoritmo di Bellman-Ford L algoritmo di Bellman-Ford risole il problema dei cammini minimi con sorgente singola nel caso più generale in ci i pesi degli archi possono essere negatii. Dato n grafo orientato e pesato G = (V, E) con sorgente s e fnzione peso w: E R, l algoritmo di Bellman-Ford restitisce n alore booleano che indica se esiste oppre no n ciclo di peso negatio raggingibile della sorgente. In caso positio, l algoritmo indica che non esiste alcna solzione ; se inece n tale ciclo non esiste, allora l algoritmo prodce i cammini minimi ed i loro pesi. Come il Dijkstra, anche l algoritmo di Bellman-Ford sa la tecnica del rilassamento, diminendo progressiamente na stima d[] del peso di n cammino minimo dalla sorgente s ad ogni ertice V, finchè non si ragginge il reale peso di commino minimo δ(s, ). L algoritmo restitisce TRUE se e solo se il grafo non contiene n ciclo di peso negatio raggingibile dalla sorgente. Dopo aer effettato la solita inizializzazione, l algoritmo fa V - passate sgli archi del grafo: ogni passata è na iterazione del ciclo for delle linee -4, e consiste nel rilassare ogni arco del grafo na olta. Dopo le V - passate, le linee -8 controllano l esistenza di n ciclo di peso negatio e - -
restitiscono il alore booleano appropriato. L algoritmo di Bellman-Ford richiede tempo O(VE). Anche in qesto caso iene dimostrata la correttezza dell algoritmo. Bellman-Ford(G, w, s) Inizializza(G, s) for i to V - 3 do for ogni ertice (, ) E 4 do Rilassa(,, w) for ogni arco (, ) E 6 do if d[] > d[] + w(, ) then retrn FALSE 8 retrn TRUE Esempio 6 6 6-6 - -3-3 8 8-4 -4 9 9 a) b) 6 4-8 -3-4 9 6 c) d) 4-8 -3-4 9-6 -
s 0 6 4-8 -3-4 - 9 e) Una eseczione dell algoritmo di Bellman-Ford. La sorgente è il ertice s. I alori d sono mostrati all interno dei ertici, e gli archi in neretto indicano i alori di π. (a) La sitazione sbito prima della prima passata sgli archi. (b)-(e) La sitazione dopo ogni passata sccessia sgli archi. Alla fine l algoritmo fornisce TRUE. - -