Problema dello zaino: differenze tra le versioni
Contenuto cancellato Contenuto aggiunto
Aggiungi 2 libri per la Wikipedia:Verificabilità (20250910)) #IABot (v2.0.9.5) (GreenC bot |
|||
(71 versioni intermedie di 54 utenti non mostrate) | |||
Riga 1:
{{
[[File:Knapsack Problem Illustration.svg|miniatura|In questo caso, la soluzione è di mettere nello zaino tre libri gialli e tre grigi]]
Il '''
:
Il problema rappresenta un classico esempio di [[programmazione dinamica]] e illustra perfettamente il concetto di [[sottostruttura ottimale]], dove la soluzione ottimale del problema può essere costruita combinando soluzioni ottime dei sottoproblemi<ref>{{cita libro|cognome=Kleinberg|nome=Jon|cognome2=Tardos|nome2=Éva|titolo=Algorithm Design|editore=Pearson|anno=2013|isbn=978-0321295354|p=260-267}}</ref>.
== Introduzione ==
Riga 9 ⟶ 12:
Il problema espresso in maniera più formale diventa:
La [[funzione obiettivo]] da massimizzare è:
: <math>
I vincoli:
: <math>\sum_{i=1}^N w_i\cdot x_i\leq W.</math>
In base al tipo di variabili <math>x_i</math> si ha poi la distinzione in:
:: <math>x_i=\left\{0,1\right\}
: ogni oggetto può esserci o non esserci senza ripetizioni (abbiamo un solo esemplare di ciascun oggetto);
* '''problema dello zaino con limiti'''
:: <math>x_i \leq b_i \quad \forall i=1,\ldots,N,</math>
: ogni oggetto può apparire nello zaino al massimo <math>b_i</math> volte (abbiamo <math>b_1</math> esemplari dell'oggetto 1, <math>b_2</math> esemplari dell'oggetto 2 e così via);
* '''problema dello zaino senza limiti'''
:: <math>x_i \in \N \quad \forall i=1,\ldots,N,</math>
: ogni oggetto può apparire nello zaino un numero arbitrario di volte.
== Complessità computazionale ==
Il problema dello zaino è risolto spesso usando la [[programmazione dinamica]], anche se è noto che
La versione decisionale di questo problema è [[NP-completo|NP-completa]] e infatti è uno dei [[21 problemi NP-completi di Karp]].
Il problema dello zaino, nella versione di ottimizzazione, è di fondamentale importanza in quanto
=== Analisi della complessità ===
{| class="wikitable"
|-
! Variante !! Complessità temporale !! Complessità spaziale !! Note
|-
| Zaino 0-1 (DP) || O(nW) || O(nW) o O(W) || Pseudo-polinomiale
|-
| Zaino senza limiti (DP) || O(nW) || O(W) || Pseudo-polinomiale
|-
| Zaino frazionario (Greedy) || O(n log n) || O(1) || Polinomiale
|-
| Branch and Bound || O(2ⁿ) || O(n) || Esponenziale (caso peggiore)
|}
La complessità O(nW) è '''pseudo-polinomiale''' perché dipende dal valore numerico W, non dalla sua lunghezza in bit (log W)<ref>{{cita libro|cognome=Dasgupta|nome=Sanjoy|cognome2=Papadimitriou|nome2=Christos|cognome3=Vazirani|nome3=Umesh|titolo=Algorithms|url=https://archive.org/details/algorithms0000dasg|editore=McGraw-Hill|anno=2008|isbn=978-0073523408|p=[https://archive.org/details/algorithms0000dasg/page/174 175]-178}}</ref>.
== Soluzione problema dello zaino senza limiti ==
Viene descritta di seguito la soluzione per il ''problema dello zaino senza limiti''.
Si indichino con <math>c_1,\
Si definiscono gli <math>A(
* <math>A(0) = 0;</math>
* <math>A(
Ciò non contraddice il fatto che il problema dello zaino
=== Implementazione zaino senza limiti ===
<syntaxhighlight lang="text">
KNAPSACK_UNLIMITED(pesi, valori, n, W):
// Inizializzazione
dp[0] = 0
for w = 1 to W:
dp[w] = 0
// Riempimento tabella DP
for w = 1 to W:
for i = 1 to n:
if pesi[i] <= w:
dp[w] = max(dp[w], dp[w - pesi[i]] + valori[i])
return dp[W]
</syntaxhighlight>
== Soluzione problema dello zaino 0-1 ==
Si indichino con <math>w_i</math> il peso dell'i-esimo oggetto e con <math>c_i</math> il suo valore. Si vuole massimizzare il valore totale rispettando il vincolo che il peso totale sia minore o uguale al peso massimo consentito <math>W</math>. Definiamo <math>A(i,j)</math> come il massimo valore che può essere trasportato con uno zaino di capacità <math>j \le W</math> avendo a disposizione solo i primi <math>i</math> oggetti.
Si può definire <math>A(i,j)</math> ricorsivamente come segue:
* <math>A(0, j) = 0,</math>
* <math>A(i, 0) = 0,</math>
* <math>A(i,j) = A(i - 1, j)</math> se <math>w_i > j,</math>
* <math>A(i,j) = \max \lbrace A(i - 1, j), A(i - 1, j - w_i) + c_i \rbrace </math> se <math>w_i\le j.</math>
Si può trovare la soluzione calcolando <math>A(n,W)</math>. Per farlo in modo efficiente si può usare una tabella che memorizzi i calcoli fatti precedentemente. Questa soluzione impiegherà quindi un tempo proporzionale a <math>O(nW)</math> e uno spazio anch'esso proporzionale a <math>O(nW)</math>, anche se con alcune piccole modifiche si può ridurre lo spazio utilizzato a <math>O(W)</math>.
=== Implementazione dettagliata zaino 0-1 ===
==== Versione con tabella completa ====
<syntaxhighlight lang="text">
KNAPSACK_01_FULL(pesi, valori, n, W):
// Creazione tabella DP
dp = array[n+1][W+1]
// Inizializzazione casi base
for i = 0 to n:
dp[i][0] = 0
for w = 0 to W:
dp[0][w] = 0
// Riempimento tabella
for i = 1 to n:
for w = 1 to W:
// Non includiamo l'oggetto i
dp[i][w] = dp[i-1][w]
// Includiamo l'oggetto i se possibile
if pesi[i] <= w:
dp[i][w] = max(dp[i][w], dp[i-1][w-pesi[i]] + valori[i])
return dp[n][W]
</syntaxhighlight>
==== Versione ottimizzata per spazio ====
<syntaxhighlight lang="text">
KNAPSACK_01_OPTIMIZED(pesi, valori, n, W):
// Array monodimensionale
dp = array[W+1]
// Inizializzazione
for w = 0 to W:
dp[w] = 0
// Processa ogni oggetto
for i = 1 to n:
// Scorri da destra a sinistra per evitare conflitti
for w = W down to pesi[i]:
dp[w] = max(dp[w], dp[w - pesi[i]] + valori[i])
return dp[W]
</syntaxhighlight>
=== Ricostruzione della soluzione ===
Per ricostruire quali oggetti fanno parte della soluzione ottimale:
<syntaxhighlight lang="text">
RECONSTRUCT_SOLUTION(dp, pesi, valori, n, W):
soluzione = []
i = n
w = W
while i > 0 and w > 0:
// Se il valore deriva dall'inclusione dell'oggetto i
if dp[i][w] != dp[i-1][w]:
soluzione.append(i)
w = w - pesi[i]
i = i - 1
return reverse(soluzione)
</syntaxhighlight>
=== Esempio passo-passo ===
Consideriamo 3 oggetti con zaino di capacità 5:
* Oggetto 1: peso=2, valore=1
* Oggetto 2: peso=3, valore=4
* Oggetto 3: peso=4, valore=7
'''Tabella DP:'''
{| class="wikitable"
|-
! i\w !! 0 !! 1 !! 2 !! 3 !! 4 !! 5
|-
| 0 || 0 || 0 || 0 || 0 || 0 || 0
|-
| 1 || 0 || 0 || 1 || 1 || 1 || 1
|-
| 2 || 0 || 0 || 1 || 4 || 4 || 5
|-
| 3 || 0 || 0 || 1 || 4 || 7 || 7
|}
'''Soluzione ottimale:''' Valore = 7 (solo oggetto 3)
== Algoritmo Greedy ==
Martello e Toth (1990) hanno utilizzato un'[[euristica]] [[Algoritmo greedy|greedy]] per risolvere il problema dello zaino<ref>{{Cita libro|nome=Silvano|cognome=Martello|coautori=Paolo Toth|anno=1990|titolo=Knapsack Problems: Algorithms and Computer Implementations|editore=Wiley|isbn=0-471-92420-2|url=http://www.or.deis.unibo.it/knapsack.html}}</ref>. La loro versione ordina gli oggetti in base al loro costo unitario, vale a dire <math>\frac{c_i}{w_i}</math> e li esamina in ordine decrescente. L'oggetto corrente viene inserito [[se e solo se]] il suo peso non supera la capacità residua corrente.
=== Implementazione algoritmo greedy ===
<syntaxhighlight lang="text">
KNAPSACK_GREEDY(pesi, valori, n, W):
// Calcola rapporto valore/peso
rapporti = []
for i = 1 to n:
rapporti[i] = valori[i] / pesi[i]
// Ordina per rapporto decrescente
oggetti_ordinati = sort_by_ratio_desc(rapporti)
// Algoritmo greedy
peso_corrente = 0
valore_totale = 0
soluzione = []
for oggetto in oggetti_ordinati:
if peso_corrente + pesi[oggetto] <= W:
soluzione.append(oggetto)
peso_corrente += pesi[oggetto]
valore_totale += valori[oggetto]
return valore_totale, soluzione
</syntaxhighlight>
Questi algoritmi sono euristici, quindi non garantiscono di trovare la soluzione ottima, ma sono in grado di fornire una "buona" soluzione in tempo ragionevole; spesso questo tipo di algoritmi viene utilizzato in approcci di enumerazione implicita come gli algoritmi [[Branch and bound]].
=== Analisi del rapporto di approssimazione ===
Per il problema dello zaino 0-1, l'algoritmo greedy non garantisce alcun rapporto di approssimazione costante. Tuttavia, per il '''problema dello zaino frazionario''' (dove si possono prendere frazioni di oggetti), l'algoritmo greedy è ottimale<ref>{{cita libro|cognome=Vazirani|nome=Vijay V.|titolo=Approximation Algorithms|url=https://archive.org/details/approximationalg0000vazi|editore=Springer|anno=2001|isbn=978-3540653677|p=[https://archive.org/details/approximationalg0000vazi/page/74 74]-77}}</ref>.
== Rilassamento continuo ==
Si dimostra che il rilassamento continuo del problema dello zaino è equivalente all'euristica CUD quando si permettono valori in <math>[0, 1]</math> delle variabili <math>x_i</math>, in particolare una sola variabile avrà valore non binario. In questo modo euristica e rilassamento possono essere risolti simultaneamente in maniera efficiente.
=== Soluzione del rilassamento ===
<syntaxhighlight lang="text">
KNAPSACK_FRACTIONAL(pesi, valori, n, W):
// Ordina per rapporto valore/peso decrescente
oggetti = sort_by_value_weight_ratio_desc(pesi, valori)
peso_corrente = 0
valore_totale = 0
soluzione = array[n] // frazione di ogni oggetto
for i = 1 to n:
if peso_corrente + pesi[oggetti[i]] <= W:
// Prendi tutto l'oggetto
soluzione[oggetti[i]] = 1
peso_corrente += pesi[oggetti[i]]
valore_totale += valori[oggetti[i]]
else:
// Prendi frazione dell'oggetto
frazione = (W - peso_corrente) / pesi[oggetti[i]]
soluzione[oggetti[i]] = frazione
valore_totale += frazione * valori[oggetti[i]]
break
return valore_totale, soluzione
</syntaxhighlight>
== Algoritmi di approssimazione ==
=== Algoritmo 2-approssimato ===
Un semplice algoritmo 2-approssimato per il problema dello zaino 0-1:
<syntaxhighlight lang="text">
KNAPSACK_2_APPROX(pesi, valori, n, W):
// Soluzione 1: algoritmo greedy
valore_greedy, sol_greedy = KNAPSACK_GREEDY(pesi, valori, n, W)
// Soluzione 2: oggetto di valore massimo
valore_max = 0
oggetto_max = 0
for i = 1 to n:
if pesi[i] <= W and valori[i] > valore_max:
valore_max = valori[i]
oggetto_max = i
// Restituisci la migliore delle due
if valore_greedy >= valore_max:
return valore_greedy, sol_greedy
else:
return valore_max, [oggetto_max]
</syntaxhighlight>
=== FPTAS (Fully Polynomial-Time Approximation Scheme) ===
Esiste un FPTAS per il problema dello zaino che garantisce una soluzione (1+ε)-approssimata in tempo O(n³/ε)<ref>{{cita libro|cognome=Williamson|nome=David P.|cognome2=Shmoys|nome2=David B.|titolo=The Design of Approximation Algorithms|editore=Cambridge University Press|anno=2011|isbn=978-0521195270|p=65-69}}</ref>:
<syntaxhighlight lang="text">
KNAPSACK_FPTAS(pesi, valori, n, W, epsilon):
// Scala i valori per ridurre la complessità
V_max = max(valori)
K = epsilon * V_max / n
valori_scalati = []
for i = 1 to n:
valori_scalati[i] = floor(valori[i] / K)
// Risolvi con DP sui valori scalati
return KNAPSACK_BY_VALUE(pesi, valori_scalati, n, W, K)
</syntaxhighlight>
== Varianti avanzate ==
=== Problema dello zaino multidimensionale ===
Estensione con più vincoli di peso:
<syntaxhighlight lang="text">
// Zaino con m dimensioni (es: peso, volume, costo)
MULTIDIMENSIONAL_KNAPSACK(pesi, valori, n, capacita, m):
// DP con m+1 dimensioni
dp = array[n+1][capacita[1]+1]...[capacita[m]+1]
// Inizializzazione
// ... (tutti gli elementi a 0)
for i = 1 to n:
for c1 = capacita[1] down to pesi[i][1]:
for c2 = capacita[2] down to pesi[i][2]:
// ... per tutte le m dimensioni
for cm = capacita[m] down to pesi[i][m]:
dp[c1][c2]...[cm] = max(
dp[c1][c2]...[cm],
dp[c1-pesi[i][1]][c2-pesi[i][2]]...[cm-pesi[i][m]] + valori[i]
)
return dp[capacita[1]][capacita[2]]...[capacita[m]]
</syntaxhighlight>
=== Problema dello zaino con conflitti ===
Alcuni oggetti non possono essere selezionati insieme:
<syntaxhighlight lang="text">
KNAPSACK_WITH_CONFLICTS(pesi, valori, n, W, conflitti):
// Usa branch and bound o programmazione intera
// conflitti[i][j] = true se oggetti i e j sono in conflitto
function is_valid(soluzione):
for i in soluzione:
for j in soluzione:
if i != j and conflitti[i][j]:
return false
return true
// Algoritmo di backtracking
return BACKTRACK_KNAPSACK(pesi, valori, W, conflitti, [], 0)
</syntaxhighlight>
== Applicazioni pratiche ==
=== Allocazione risorse ===
* '''Budget allocation:''' Distribuzione ottimale di fondi tra progetti
* '''Portfolio optimization:''' Selezione di investimenti con budget limitato
* '''Resource scheduling:''' Assegnazione di risorse computazionali
=== Informatica ===
* '''Memory management:''' Ottimizzazione dell'uso della memoria cache
* '''Bandwidth allocation:''' Distribuzione della banda di rete
* '''Load balancing:''' Distribuzione del carico sui server
=== Logistica ===
* '''Cargo loading:''' Caricamento ottimale di container e aerei
* '''Cutting stock:''' Minimizzazione dello spreco in taglio di materiali
* '''Bin packing:''' Ottimizzazione dello spazio in magazzini
=== Crittografia ===
* '''Subset-sum cryptography:''' Base per sistemi crittografici (ormai obsoleti)
* '''Lattice-based cryptography:''' Varianti moderne per crittografia post-quantistica
== Strumenti e librerie ==
=== Implementazioni efficienti ===
Per problemi reali di grandi dimensioni, sono disponibili solver specializzati:
* '''CPLEX:''' Solver commerciale per programmazione lineare intera
* '''Gurobi:''' Solver avanzato con supporto per problemi di zaino
* '''SCIP:''' Solver open-source per programmazione intera
* '''OR-Tools:''' Libreria Google per ottimizzazione combinatoria
== Note ==
<references/>
== Bibliografia ==
* {{
|
|
| anno = 1979
| titolo = Computers and Intractability: A Guide to the Theory of NP-Completeness
| url = https://archive.org/details/computersintract0000gare | editore = W.H. Freeman
| isbn = 0-7167-1045-5}}
* {{Cita libro
| nome = Silvano | cognome = Martello
| coautori = Paolo Toth
| anno = 1990
| titolo = Knapsack Problems: Algorithms and Computer Implementations
| editore = Wiley
| isbn = 0-471-92420-2
| url=http://www.or.deis.unibo.it/knapsack.html}}
* {{cita libro|cognome=Cormen|nome=Thomas H.|cognome2=Leiserson|nome2=Charles E.|cognome3=Rivest|nome3=Ronald L.|cognome4=Stein|nome4=Clifford|titolo=Introduction to Algorithms|edizione=4|editore=MIT Press|anno=2022|isbn=978-0-262-04630-5}}
* {{cita libro|cognome=Kleinberg|nome=Jon|cognome2=Tardos|nome2=Éva|titolo=Algorithm Design|editore=Pearson|anno=2013|isbn=978-0321295354}}
* {{cita libro|cognome=Vazirani|nome=Vijay V.|titolo=Approximation Algorithms|url=https://archive.org/details/approximationalg0000vazi|editore=Springer|anno=2001|isbn=978-3540653677}}
* {{cita libro|cognome=Williamson|nome=David P.|cognome2=Shmoys|nome2=David B.|titolo=The Design of Approximation Algorithms|editore=Cambridge University Press|anno=2011|isbn=978-0521195270}}
== Voci correlate ==
* [[Programmazione dinamica]]
* [[Sottostruttura ottimale]]
* [[Algoritmo greedy]]
* [[Problema di ottimizzazione]]
* [[NP-completezza]]
* [[Algoritmo di approssimazione]]
* [[Branch and bound]]
* [[Subset-sum]]
== Collegamenti esterni ==
* {{Collegamenti esterni}}
* {{FOLDOC|knapsack problem|knapsack problem}}
* {{en}} [https://web.stanford.edu/class/cs161/schedule.html Stanford CS161: Knapsack Problem]
* {{en}} [https://cp-algorithms.com/dynamic_programming/knapsack.html CP-Algorithms: Knapsack Problem]
{{Portale|informatica|matematica}}
[[Categoria:Problemi NP-completi]]
[[Categoria:Programmazione dinamica]]
[[Categoria:Algoritmi di ottimizzazione]]
|