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
[modifica | modifica wikitesto]Un 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
[modifica | modifica wikitesto]| Paradigma | 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
[modifica | modifica wikitesto]Proprietà di scelta greedy
[modifica | modifica wikitesto]La 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
[modifica | modifica wikitesto]La 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
[modifica | modifica wikitesto]Selezione di attività
[modifica | modifica wikitesto]Il 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
[modifica | modifica wikitesto]L'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
[modifica | modifica wikitesto]La 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
[modifica | modifica wikitesto]Per dimostrare la correttezza di un algoritmo greedy sono necessari due passi:
Dimostrazione della proprietà greedy
[modifica | modifica wikitesto]Utilizzando 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 | modifica wikitesto]- 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
[modifica | modifica wikitesto]Gli 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
[modifica | modifica wikitesto]Quando 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
[modifica | modifica wikitesto]Un 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
[modifica | modifica wikitesto]GREEDY_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
[modifica | modifica wikitesto]Problema del resto (cambio di monete)
[modifica | modifica wikitesto]Per 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
[modifica | modifica wikitesto]Dato 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
[modifica | modifica wikitesto]- Programmazione dinamica
- Algoritmo di approssimazione
- Teoria della complessità computazionale
- Matroide
- Algoritmo di Kruskal
- Algoritmo di Prim
- Codifica di Huffman
- Problema di selezione delle attività
Note
[modifica | modifica wikitesto]- ^ 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 | modifica wikitesto]- 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 | modifica wikitesto]
Wikimedia Commons contiene immagini o altri file sugli algoritmo greedy
Collegamenti esterni
[modifica | modifica wikitesto]- (EN) Stanford CS161: Greedy Algorithms
- (EN) Algorithm Archive: Greedy Algorithms
- (EN) Eric W. Weisstein, Greedy Algorithm, su MathWorld, Wolfram Research.