Algoritmo greedy
Un algoritmo greedy (dall'inglese greedy, avido) è un paradigma algoritmico che costruisce una soluzione passo dopo passo, effettuando ad ogni iterazione la scelta localmente ottimale, con l'obiettivo di trovare un ottimo globale[1]. Questi algoritmi non sempre garantiscono la soluzione ottimale, ma sono spesso efficienti e forniscono buone approssimazioni per problemi di ottimizzazione complessi[2].
Il paradigma greedy è caratterizzato da due proprietà fondamentali: la proprietà di scelta greedy (esiste sempre una soluzione ottimale che include la scelta localmente ottimale) e la sottostruttura ottima (la soluzione ottimale contiene soluzioni ottime ai sottoproblemi)[3].
Descrizione del paradigma
modificaUn algoritmo greedy effettua una sequenza di scelte, dove ogni scelta è localmente ottimale al momento in cui viene presa. L'algoritmo non riconsiderera mai le scelte precedenti, a differenza della programmazione dinamica che esplora tutte le possibili combinazioni di sottoproblemi[4].
La strategia greedy può essere schematizzata come segue:
- Ordinare gli elementi secondo un criterio di ottimalità locale
- Inizializzare una soluzione vuota
- Per ogni elemento, se è compatibile con le scelte precedenti, aggiungerlo alla soluzione
- Restituire la soluzione costruita
Confronto con altri paradigmi
modificaParadigma | Strategia | Garanzia di ottimalità | Complessità tipica |
---|---|---|---|
Greedy | Scelta localmente ottima | Solo per problemi specifici | O(n log n) |
Programmazione dinamica | Esplorazione di tutti i sottoproblemi | Sì, se esiste sottostruttura ottima | O(n²) - O(n³) |
Divide et impera | Suddivisione ricorsiva | Dipende dal problema | O(n log n) |
Backtracking | Esplorazione con ritorno | Sì, ma può essere esponenziale | O(2ⁿ) - O(n!) |
Proprietà fondamentali
modificaProprietà di scelta greedy
modificaLa proprietà di scelta greedy stabilisce che una scelta localmente ottima porta sempre a una soluzione globalmente ottima. Formalmente, esiste sempre una soluzione ottima che contiene la scelta greedy effettuata al primo passo[5].
Dimostrazione tipica (Exchange Argument):
- Supporre l'esistenza di una soluzione ottima I* che non contiene la scelta greedy
- Costruire una nuova soluzione I' sostituendo un elemento di I* con la scelta greedy
- Dimostrare che |I'| ≤ |I*| e che I' è ammissibile
- Concludere che I' è ottima e contiene la scelta greedy
Sottostruttura ottima
modificaLa sottostruttura ottima garantisce che, dopo aver effettuato la scelta greedy, il problema residuo mantiene la stessa struttura di ottimalità. Questo permette di applicare ricorsivamente la strategia greedy sui sottoproblemi rimanenti[6].
Esempi classici
modificaSelezione di attività
modificaIl problema di selezione delle attività consiste nel selezionare il massimo numero di attività mutuamente compatibili da un insieme di attività, ciascuna caratterizzata da un tempo di inizio e un tempo di fine.
Strategia greedy: Selezionare sempre l'attività che termina prima tra quelle non ancora considerate.
ACTIVITY_SELECTOR(s, f, n):
A = {a₁}
k = 1
for m = 2 to n:
if s[m] ≥ f[k]:
A = A ∪ {aₘ}
k = m
return A
Complessità: O(n log n) per l'ordinamento iniziale, O(n) per la selezione.
Algoritmo di Kruskal
modificaL'algoritmo di Kruskal trova l'albero di copertura minimo di un grafo pesato utilizzando una strategia greedy.
Strategia greedy: Selezionare sempre l'arco di peso minimo che non crea cicli.
KRUSKAL(G):
A = ∅
for each vertex v ∈ G.V:
MAKE_SET(v)
sort edges of G.E in nondecreasing order by weight
for each edge (u,v) ∈ G.E:
if FIND_SET(u) ≠ FIND_SET(v):
A = A ∪ {(u,v)}
UNION(u,v)
return A
Complessità: O(E log E) dove E è il numero di archi.
Codifica di Huffman
modificaLa codifica di Huffman costruisce un codice a lunghezza variabile ottimale per la compressione di dati.
Strategia greedy: Unire sempre i due nodi con frequenza minima.
HUFFMAN(C):
n = |C|
Q = C
for i = 1 to n-1:
allocate new node z
z.left = x = EXTRACT_MIN(Q)
z.right = y = EXTRACT_MIN(Q)
z.freq = x.freq + y.freq
INSERT(Q, z)
return EXTRACT_MIN(Q)
Complessità: O(n log n) utilizzando una coda di priorità.
Analisi di correttezza
modificaPer dimostrare la correttezza di un algoritmo greedy sono necessari due passi:
Dimostrazione della proprietà greedy
modificaUtilizzando l'exchange argument:
- Assumere l'esistenza di una soluzione ottima che differisce dalla scelta greedy
- Mostrare che è possibile "scambiare" elementi per ottenere una soluzione altrettanto buona che include la scelta greedy
- Concludere che esiste sempre una soluzione ottima con la scelta greedy
Dimostrazione della sottostruttura ottima
modifica- Mostrare che dopo la scelta greedy, il problema residuo ha la stessa struttura
- Dimostrare che una soluzione ottima del problema originale contiene soluzioni ottime dei sottoproblemi
Limitazioni
modificaGli algoritmi greedy non sempre producono soluzioni ottime. Un esempio classico è il problema dello zaino frazionario vs problema dello zaino 0-1:
- Zaino frazionario: L'algoritmo greedy (ordinare per rapporto valore/peso) è ottimale
- Zaino 0-1: L'algoritmo greedy può fallire e richiede programmazione dinamica
Esempio di fallimento: Capacità zaino: 10, Oggetti: (peso=6, valore=30), (peso=5, valore=20), (peso=4, valore=15)
- Greedy: seleziona primo oggetto → valore totale = 30
- Ottimo: seleziona secondo e terzo oggetto → valore totale = 35
Problemi di approssimazione
modificaQuando gli algoritmi greedy non garantiscono l'ottimo, spesso forniscono buone approssimazioni:
- Set Cover: Greedy garantisce approssimazione O(log n)
- Vertex Cover: Greedy garantisce approssimazione 2-ottimale
- TSP metrico: Algoritmi greedy garantiscono approssimazioni costanti
Teoria dei matroidi
modificaUn matroide M = (E, I) è una struttura matematica dove E è un insieme finito e I è una famiglia di sottoinsiemi "indipendenti" di E che soddisfa:
- ∅ ∈ I (insieme vuoto è indipendente)
- Se A ∈ I e B ⊆ A, allora B ∈ I (proprietà ereditaria)
- Se A, B ∈ I e |A| < |B|, esiste x ∈ B\A tale che A ∪ {x} ∈ I (proprietà di scambio)
Teorema fondamentale: Un algoritmo greedy trova sempre una soluzione ottima per problemi di ottimizzazione su matroidi pesati[7].
Algoritmo greedy per matroidi pesati
modificaGREEDY_MATROID(M, w):
A = ∅
sort elements of M.E in decreasing order by weight w
for each x ∈ M.E:
if A ∪ {x} ∈ M.I:
A = A ∪ {x}
return A
Esempi di implementazione
modificaProblema del resto (cambio di monete)
modificaPer sistemi monetari "canonici" (come Euro), l'algoritmo greedy è ottimale:
CHANGE_MAKING(amount, coins):
result = []
sort coins in decreasing order
for each coin in coins:
while amount ≥ coin:
result.append(coin)
amount -= coin
return result
Attenzione: Per sistemi non canonici, l'algoritmo può fallire.
Scheduling con deadline
modificaDato un insieme di lavori con profitti e deadline, massimizzare il profitto:
JOB_SCHEDULING(jobs, n):
sort jobs by profit in decreasing order
result = []
for i = 0 to n-1:
for j = min(n, jobs[i].deadline)-1 down to 0:
if result[j] is empty:
result[j] = jobs[i]
break
return result
Voci correlate
modificaNote
modifica- ^ Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest e Clifford Stein, Introduction to Algorithms, 4ª ed., MIT Press, 2022, p. 414-442, ISBN 978-0-262-04630-5.
- ^ Jon Kleinberg e Éva Tardos, Algorithm Design, Pearson, 2013, p. 115-171, ISBN 978-0321295354.
- ^ Camil Demetrescu, Irene Finocchi e Giuseppe Liotta, Algoritmi e strutture dati, 3ª ed., McGraw-Hill, 2019, p. 167-189, ISBN 978-8838615818.
- ^ Robert Sedgewick e Kevin Wayne, Algorithms, 4ª ed., Addison-Wesley, 2011, p. 655-678, ISBN 978-0321573513.
- ^ Thomas H. Cormen, Introduction to Algorithms, 2022, p. 418.
- ^ Jon Kleinberg, Algorithm Design, 2013, p. 118.
- ^ Jack Edmonds, Matroids and the greedy algorithm, in Mathematical Programming, vol. 1, n. 1, 1971, pp. 127-136, DOI:10.1007/BF01584082.
Bibliografia
modifica- Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest e Clifford Stein, Introduction to Algorithms, 4ª ed., MIT Press, 2022, ISBN 978-0-262-04630-5.
- Jon Kleinberg e Éva Tardos, Algorithm Design, Pearson, 2013, ISBN 978-0321295354.
- Jack Edmonds, Paths, trees, and flowers, in Canadian Journal of Mathematics, vol. 17, 1965, pp. 449-467.
- Camil Demetrescu, Irene Finocchi e Giuseppe Liotta, Algoritmi e strutture dati, 3ª ed., McGraw-Hill, 2019, ISBN 978-8838615818.
- Robert Sedgewick e Kevin Wayne, Algorithms, 4ª ed., Addison-Wesley, 2011, ISBN 978-0321573513.
Altri progetti
modifica- Wikimedia Commons contiene immagini o altri file sugli algoritmo greedy
Collegamenti esterni
modifica- (EN) Stanford CS161: Greedy Algorithms
- (EN) Algorithm Archive: Greedy Algorithms
- (EN) Eric W. Weisstein, Greedy Algorithm, su MathWorld, Wolfram Research.