Algoritmi greedy Gli algoritmi greedy in genere non

  • Slides: 17
Download presentation
Algoritmi greedy Gli algoritmi greedy in genere non sono esatti, cioè determinano soluzioni non

Algoritmi greedy Gli algoritmi greedy in genere non sono esatti, cioè determinano soluzioni non necessariamente ottime Per il problema dell’albero ricoprente sono esatti • Algoritmo di Prim – conserva connessione e aciclicità – tende alla ricopertura • Algoritmo di Kruskal – conserva aciclicità e ricopertura – tende verso la connessione

Algoritmo di Prim(G, c) U = {v 1}; X = While U V do

Algoritmo di Prim(G, c) U = {v 1}; X = While U V do e* = (u, v) = arg minu U, v VU cuv X = X {e*} U = U {v} • Parte con un solo vertice (arbitrario) e nessun arco (sottografo connesso e aciclico, ma non ricoprente) • Aggiunge ogni volta l’arco minimo del taglio individuato dall’albero corrente conserva connessione e aciclicità

Esempio di esecuzione dell’algoritmo di Prim 1 v 1 4 2 2 6 3

Esempio di esecuzione dell’algoritmo di Prim 1 v 1 4 2 2 6 3 2 v 5 4 U = {v 1} X =Ø 4 2 6 4 4 4 2 5 1 v 1 3 v 3 U = {v 1, v 2} X = {[v 1, v 2]} v 3 5 4 2 6 v 5 3 2 v 2 2 2 v 5 v 4 1 v 2 2 6 v 3 5 v 4 v 2 1 v 1 4 v 2 2 2 v 5 5 3 v 3 U = {v 1, v 2, v 5} X = {[v 1, v 2], [v 1, v 5]} v 4

Esempio di esecuzione dell’algoritmo di Prim 1 v 1 4 2 6 4 v

Esempio di esecuzione dell’algoritmo di Prim 1 v 1 4 2 6 4 v 5 5 v 2 2 3 v 3 U = {v 1, v 2 , v 3, v 5} X = {[v 1, v 2], [v 1, v 5], [v 3, v 5]} 4 v 4 4 2 6 2 1 v 2 2 2 v 5 3 v 3 5 U = {v 1, v 2 , v 3 , v 4, v 5} = V X = {[v 1, v 2], [v 1, v 5], [v 3, v 5], [v 4, v 5]} costo: 9

Esempio di esecuzione dell’algoritmo di Prim Altre soluzioni ammissibili (di costo non inferiore) 1

Esempio di esecuzione dell’algoritmo di Prim Altre soluzioni ammissibili (di costo non inferiore) 1 v 1 2 6 4 v 4 4 v 2 2 v 3 4 5 1 v 1 2 6 4 v 4 4 v 2 2 2 v 5 5 v 4 4 2 6 2 v 5 1 v 1 costo: 16 3 costo: 11 v 2 2 2 v 5 3 v 3 costo: 9 Altra soluzione ottima, ottenibile cambiando lato al passo 2

Complessità dell’algoritmo di Prim La complessità dipende da come si determina e* Idea 1:

Complessità dell’algoritmo di Prim La complessità dipende da come si determina e* Idea 1: scandire tutti i lati: O(mn) Ogni ciclo While richiede O(m) operazioni elementari (se T(U, X) è una lista di lati più un vettore di incidenza 4 m + 4 operazioni) • 2 m per l’appartenenza al taglio [v, w] (S), [v, w] A • 4 per aggiornare X e U (incremento contatore, aggiunta estremi nella lista, cambio di bit nel vettore) • 2 m per confrontare il costo minimo con cuv ed eventualmente aggiornarlo

Una versione O(n 2) N. B. : Ad ogni passo nel taglio cambiano solo

Una versione O(n 2) N. B. : Ad ogni passo nel taglio cambiano solo gli archi incidenti nell’ultimo vertice aggiunto a U Idea 2: per ogni vertice v fuori da U conservare il lato minimo incidente in v del taglio indotto da U (e il suo costo) Pred[v] = arg min u U cuv Cmin[v] = min u U cuv (cuv = + se [i, j] E) 4 v 1 U v 3 v 5 v U 2 6 Pred[v] = v 3 Cmin[v] = 2

Una versione O(n 2) Prim 2(G, c) For v = 1 to n do

Una versione O(n 2) Prim 2(G, c) For v = 1 to n do Uinc[v] = 0 Cmin[v] = + Pred[v] = 0 { U = {v 1}; X = } Uinc[1] = 1 Cmin[1] = 0 For v Adj(1) do Cmin[v] = c 1 v Pred[v] = 1 m=0 … … While m < n-1 do {U V} v. Min = 0 min = + For v = 2 to n do If (Uinc[v] = 0) and (Cmin[v] < min) min = Cmin[v] v. Min = v Uinc[v. Min] = 1 {U = U {v}} For v Adj(v. Min) do If (Uinc[v] = 0) and (cvv. Min<Cmin[v]) Cmin[v] = cvv. Min Pred[v] = v. Min

Esecuzione della versione O(n 2) Prim 2(G, c) For v = 1 to n

Esecuzione della versione O(n 2) Prim 2(G, c) For v = 1 to n do Uinc[v] = 0 Cmin[v] = + Pred[v] = 0 { U = {v 1}; X = } Uinc[1] = 1 Cmin[1] = 0 For v Adj(1) do Cmin[v] = c 1 v Pred[v] = 1 1 v 1 4 2 2 6 4 U = {v 1} X= Ø Uinc = [1 0 0] Cmin = [0 1 4 6 2] Pred = [1 1 1] 3 2 v 5 v 3 5 v 4 1 v 1 6 m=0 v 4 4 2 4 … v 2 2 2 v 5 5 3 v 3

Esecuzione della versione O(n 2) UU=={v{v 1}2} 1, v X =X{[v = 1Ø, v

Esecuzione della versione O(n 2) UU=={v{v 1}2} 1, v X =X{[v = 1Ø, v 2]} … While m < n-1 do {U V} v. Min = 0 min = + For v = 2 to n do If (Uinc[v] = 0) and (Cmin[v] < min) min = Cmin[v] v. Min = v Uinc[v. Min] = 1 {U = U {v}} For v Adj(v. Min) do If (Uinc[v] = 0) and (cuv < Cmin[v]) Cmin[v] = cuv Pred[v] = v. Min 1 v 1 2 6 4 v 4 4 v 2 2 2 v 5 3 v 3 5 Uinc = [1 01 0 0 0] Cmin = [0 1 3 6 2] Pred = [1 1 2 1 1]

Esecuzione della versione O(n 2) … While m < n-1 do {U V} v.

Esecuzione della versione O(n 2) … While m < n-1 do {U V} v. Min = 0 min = + For v = 2 to n do If (Uinc[v] = 0) and (Cmin[v] < min) min = Cmin[v] v. Min = v Uinc[v. Min] = 1 {U = U {v}} For v Adj(v. Min) do If (Uinc[v] = 0) and (cuv < Cmin[v]) Cmin[v] = cuv Pred[v] = v. Min U = {v 1, v 2, v 5} X = {[v 1, v 2], [v 1, v 5]} 1 v 1 2 6 4 v 4 4 v 2 2 2 v 5 3 v 3 5 Uinc = [1 1 0 0 1] Cmin = [0 1 2 4 2] Pred = [1 1 5 5 1]

Esecuzione della versione O(n 2) … While m < n-1 do {U V} v.

Esecuzione della versione O(n 2) … While m < n-1 do {U V} v. Min = 0 min = + For v = 2 to n do If (Uinc[v] = 0) and (Cmin[v] < min) min = Cmin[v] v. Min = v Uinc[v. Min] = 1 {U = U {v}} For v Adj(v. Min) do If (Uinc[v] = 0) and (cuv < Cmin[v]) Cmin[v] = cuv Pred[v] = v. Min U = {v 1, v 2 , v 3, v 5} X = {[v 1, v 2], [v 1, v 5], [v 3, v 5]} 1 v 1 4 2 6 4 v 5 v 2 2 2 3 v 3 5 Uinc = [1 1 1 0 1] Cmin = [0 1 2 4 2] Pred = [1 1 5 5 1]

Esecuzione della versione O(n 2) … While m < n-1 do {U V} v.

Esecuzione della versione O(n 2) … While m < n-1 do {U V} v. Min = 0 min = + For v = 2 to n do If (Uinc[v] = 0) and (Cmin[v] < min) min = Cmin[v] v. Min = v Uinc[v. Min] = 1 {U = U {v}} For v Adj(v. Min) do If (Uinc[v] = 0) and (cuv < Cmin[v]) Cmin[v] = cuv Pred[v] = v. Min U = {v 1, v 2 , v 3 , v 4, v 5} X = {[v 1, v 2], [v 1, v 5], [v 3, v 5], [v 4, v 5]} 1 v 1 2 6 4 v 4 4 v 2 2 2 v 5 3 v 3 5 Uinc = [1 1 1] Cmin = [0 1 2 4 2] Pred = [1 1 5 5 1]

Complessità – Prim versione O(n 2) 1 4 2 3 For v = 1

Complessità – Prim versione O(n 2) 1 4 2 3 For v = 1 to n do … For v Adj(1) do … 1 L’inizializzazione richiede O(n) 2 Il primo ciclo For interno richiede O(n) While m < n-1 do … For v = 2 to n do … For v Adj(v. Min) do … 3 Il secondo ciclo For interno scorre la stella uscente da v. Min L’analisi aggregata dà O(m) per l’insieme delle sue esecuzioni 4 I due cicli For interni sono eseguiti n - 1 volte O(n 2)

Una versione O(m log n) L’operazione più costosa è la ricerca del lato di

Una versione O(m log n) L’operazione più costosa è la ricerca del lato di costo minimo Idea 3: conserviamo gli O(n) lati candidati in uno heap • creiamo lo heap dei lati candidati (Build. Heap): O(n) in più • leggiamo direttamente l’elemento minimo: O(n) anziché O(n 2) • Aggiorniamo lo heap quando l’albero cresce di un vertice v; ogni lato incidente in v potrebbe entrare nello heap che andrebbe quindi aggiornato (Heapify): O(log n) anziché O(1) Complessivamente, esaminiamo O(m) archi

Correttezza dell’algoritmo di Prim Dato un albero ricoprente T, lato di diminuzione è un

Correttezza dell’algoritmo di Prim Dato un albero ricoprente T, lato di diminuzione è un lato e T che, aggiunto a T, vi induce un ciclo C T {e} contenente un lato f C {e} di costo superiore (ce < cf ) f ce < cf C e T c(T {e}{f}) < c(T) Se esiste un lato e di diminuzione rispetto a un albero T ricoprente, si può ridurre il costo di T scambiando il lato e con almeno un lato f di C

Correttezza dell’algoritmo di Prim Per assurdo: l’albero T(V, X) restituito dall’algoritmo di Prim non

Correttezza dell’algoritmo di Prim Per assurdo: l’albero T(V, X) restituito dall’algoritmo di Prim non sia ottimo, e lo sia invece T*(V, X*) Seguiamo l’esecuzione di Prim, arrestandoci al primo arco e X, ma X* Aggiungendo e a X* ciclo C U Sia f (U) C v h Sicuramente ce cf e f T* Se ce = cf allora T* {e} / {f} è ottimo perché costa come T* Se ce < cf, e è un lato di diminuzione quindi T* non è ottimo!