Sommario della lezione
Introduzione alla tecnica di Programmazione Dinamica
Esempio di applicazione n. 1: Calcolo dei numeri di
Fibonacci
Esempio di applicazione n. 2: Calcolo di binomiali
Esempio di applicazione n. 3: Problema dello Zaino
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 1/37
Divide et Impera “contro" Programmazione Dinamica
Ricordiamo i passi fondamentali degli algoritmi basati sulla tecnica
Divide et Impera per risolvere un dato problema algoritmico:
1. Dividi il problema in sottoproblemi di taglia inferiore
2. Risolvi (ricorsivamente) i sottoproblemi di taglia inferiore
3. Combina le soluzioni dei sottoproblemi in una soluzione
per il problema originale
Nei problemi visti in precedenza, tipicamente i sottoproblemi che si
ottenevano dalla applicazione del passo 1. dello schema erano
diversi, pertanto, ciascuno di essi veniva individualmente risolto
dalla relativa chiamata ricorsiva del passo 2. In molte situazioni, i
sottoproblemi ottenuti al passo 1. potrebbero essere simili, o
addirittura uguali. In tal caso, l’algoritmo basato su D&I risolverebbe
lo stesso problema piú volte, svolgendo lavoro inutile!
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 2/37
Divide et Impera “contro" Programmazione Dinamica
In situazioni siffatte (ovvero quando i sottoproblemi di un dato
problema algoritmico tendono ad essere “simili", o addirittura
“uguali"), é utile impiegare la tecnica della Programmazione
Dinamica.
Tale tecnica é essenzialmente simile a D&I, con in piú l’ accortezza
di risolvere ogni sottoproblema una volta soltanto. Gli algoritmi
basati su Programmazione Dinamica risultano quindi essere piú
efficienti nel caso in cui i sottoproblemi di un dato problema
tendono a ripetersi.
Idea di Base di PD: Calcola la soluzione a distinti sottoproblemi una
volta soltanto, e memorizza tali soluzioni in una tabella, in modo tale
che esse possano essere usate nel seguito, se occorre.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 3/37
Primo esempio: calcolo dei numeri di Fibonacci
La sequenza F0 , F1 , F2 , F3 , . . . dei numeri di Fibonacci é
definita dall’equazione di ricorrenza:
F0 = F1 = 1, e per ogni n ≥ 2 Fn = Fn−1 + Fn−2
Ad esempio, abbiamo che i primi termini della sequenza
sono
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, . . .
I numeri di Fibonacci crescono rapidamente, si puó provare
infatti che Fn ≈ 2.694n
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 4/37
Esempio 1: Calcolo dei numeri di Fibonacci
Il primo algoritmo che viene in mente é basato su D&I e sulla
definizione stessa di numeri di Fibonacci
F IB(n)
≤ 1 then return 1
else return F IB(n − 1)+F IB(n − 2)
if n
Sia T (n) la complessitá di F IB(n). Abbiamo che T (0) = T (1) = 1.
In generale ∀ n ≥ 2 vale
T (n) = T (n − 1) + T (n − 2) + 1
Definendo T ′ (n) = T (n) + 1, otteniamo (esercizio!) che i numeri
T ′ (n) hanno la proprietá che T ′ (0) = T ′ (1) = 2 e
∀n≥2
T ′ (n) = T ′ (n − 1) + T ′ (n − 2)
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 5/37
Calcolo dei numeri di Fibonacci
Quindi (sempre per esercizio), otteniamo
T ′ (n) = 2Fn , ovvero T (n) = 2Fn − 1
in altre parole, il tempo di esecuzione T (n) di F IB(n) é pari a
T (n) = 2Fn − 1 ≈ 2 × 2.694n , troppo!
Dove stá il problema?
Il problema stá nel fatto che il programma F IB viene chiamato sullo
stesso input molte volte, é ció é chiaramente inutile.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 6/37
Esempio
Vediamo ad esempio lo sviluppo delle chiamate ricorsive per il
calcolo di F IB(5).
F IB(5)
F IB(4)
F IB(3)
F IB(1)
F IB(3)
F IB(2)
F IB(2)
F IB(1)
F IB(0)
F IB(2)
F IB(1)
F IB(1)
F IB(0)
F IB(1)
F IB(1)
F IB(0)
viene calcolato 5 volte,
F IB(0) e F IB(2) 3 volte, F IB(3)
2 volte
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 7/37
Quindi...
Dall’esempio precedente vediamo che stesse quantitá vengono
calcolate piú volte da differenti chiamate ricorsive sullo stesso input!
Questo é il motivo per cui F IB(n) é inefficiente. Ovvero, i
sottoproblemi in cui il problema originale viene decomposto non
sono distinti, ma l’algoritmo ricorsivo si comporta come se lo
fossero, e li risolve daccapo ogni volta.
Una volta che si é individuata la causa della inefficienza
dell’algoritmo F IB(n), é facile individuare anche la cura. Basta
memorizzare in un vettore i valori F IB(i), i < n quando li si calcola
per prima volta, cosicché in future chiamate ricorsive a F IB(i) non ci
sará piú bisogno di calcolarli, ma basterá leggerli dal vettore.
In altri termini, risparmiamo tempo di calcolo alle spese di un
piccolo aumento di occupazione di memoria
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 8/37
Algoritmo pú efficiente per i numeri di Fibonacci
M EM F IB(n)
% (si fá uso di un array ausiliario F [0 . . . n])
if n ≤ 1 then return 1
else if F [n] non é definitito
F [n] ← M EM F IB(n − 1) + M EM F IB(n − 2)
return F [n]
L’ aspetto importante dell’algoritmo M EM F IB(n) é che esso, prima di
sviluppare la ricorsione per calcolare qualche quantitá M EM F IB(i),
per qualche i < n, controlla innanzitutto se essa é stata calcolata
precedentemente e posta in F [i]. Nel caso affermativo, la
ricorsione non viene sviluppata e si legge semplicemente il valore
da F [i], con conseguente risparmio di tempo
Inoltre, se ad es. sviluppiamo le chiamate ricorsive di M EM F IB(5), ci
rendiamo conto che le componento dell’array F [0 . . . 5] vengono
calcolate e assegnate nell’ordine F [2], F [3], F [4], F [5]
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 9/37
Possiamo quindi anche usare un algoritmo iterativo
I TER F IB(n)
F [0] ← 1, F [1] ← 1
for i ← 2 to n do
F [i] ← F [i − 1] + F [i − 2]
return F [n]
L’algoritmo I TER F IB(n) richiede tempo O(n) e memoria O(n) per
calcolare l’n-esimo numero di Fibonacci Fn , un miglioramento
esponenziale rispetto al nostro primo algoritmo F IB(n)!
Un ulteriore miglioramento lo si puó ottenere sullo memoria usata.
Infatti, non é difficile notare che delle locazioni dell’array F ce ne
occorrono solo le ultime due calcolate, e non tutte le n (i dettagli
sono lasciati per esercizio).
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 10/37
Caveat Emptor!
Abbiamo detto che il primo algoritmo F IB(n) ha complessitá
O(Fn ) = O(2.694...n ) e che gli algoritmi M EM F IB(n) e I TER F IB(n) hanno
complessitá O(n).
In realtá abbiamo un pó imbrogliato, nel senso che ció che abbiamo
effettivamente provato é che gli algoritmi F IB(n), M EM F IB(n) e
I TER F IB(n) eseguono O(Fn ) = O(2.694...n ), O(n), e O(n) operazioni,
rispettivamente.
Ma quanto costa ciascuna di queste operazioni?
Molto. Infatti, poiché in numeri di Fibonacci Fn crescono
esponenzialmente ( ≈ come 2.694...n ), la loro rappresentazione in
binario é un vettore lungo O(n) bits, quindi ogni operazione che li
coinvolge (addizione, sottrazione,... ) costa tempo O(n), e non
tempo costante.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 11/37
Pertanto...
La “vera” complessitá di F IB(n), é O(nFn ) = O(n2.694...n ), e la
“vera” complessitá di M EM F IB(n) e I TER F IB(n) é
O(n · n) = O(n2 )
In ogni caso, un miglioramento esponenziale lo abbiamo
ottenuto
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 12/37
Come abbiamo fatto ad ottenerlo?
1. Siamo partiti da un algoritmo ricorsivo (e non efficiente), ottenuto
applicando D&I al problema in questione
2. Abbiamo scoperto che i motivi dell’inefficienza dell’algoritmo
risiedevano nel fatto che l’algoritmo risolveva piú volte lo stesso
sottoproblema
3. Abbiamo aggiunto una tabella all’algoritmo, indicizzata dai possibili
valori input (=sottoproblemi) alle chiamate ricorsive.
4. Abbiamo altresí aggiunto, prima di ogni chiamata ricorsiva
dell’algoritmo su di un particolare sottoproblema, un controllo sulla tabella
per verificare se la soluzione a quel sottoproblema era stata giá calcolata
in precedenza. Nel caso affermativo, ritorniamo semplicemente la
soluzione al sottoproblema (senza ricalcolarla). Altrimenti, chiamiamo
ricorsivamente l’algoritmo per risolvere il (nuovo) sottoproblema.
Questa tecnica (chiamata Memoization) ha validitá abbastanza generale,
e ci permetterá di ottenere algoritmi efficienti per una varietá di problemi
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 13/37
Esempio 2: Calcolo di Combinazioni
n
r
il numero di modi con cui possiamo scegliere r oggetti da
Sia
n
un insieme di n elementi. Come possiamo calcolare r ?
Per scegliere r oggetti da un insieme di n elementi, possiamo
o
• scegliere di prendere il primo oggetto dell’insieme (in questo caso
ci resterá il problema di scegliere i restanti r − 1 oggetti da un
n−1
insieme di n − 1 elementi, e questo si potrá fare in r−1 modi),
oppure
• scegliere di non prendere il primo oggetto dell’insieme (in questo
secondo caso ci resterá il problema di scegliere tutti gli r oggetti da
n−1
un insieme di n − 1 elementi, e questo si potrá fare in r modi).
Ció equivale a dire che
n
r
=
n−1
r−1
+
n−1
r
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 14/37
Possiamo quindi usare D&I per calcolare
n
r
C HOOSE(n, r)
if r = 0 o n = r then return (1)
else return (C HOOSE (n − 1, r − 1)+ C HOOSE (n − 1, r))
Analisi: Sia T (n, r) il numero di operazioni effettuate dall’algoritmo
C HOOSE(n, r), e sia T (n) = maxr T (n, r).
Abbiamo



c
se n = 1,


T (n) =



2T (n − 1) + d se n > 1
per qualche costante c e d.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 15/37
Risolviamo l’ equazione di ricorrenza
T (n) = 2T (n − 1) + d
= 2(2T (n − 2) + d) + d
= 4T (n − 2) + 2d + d
..
.
= 2i T (n − i) + d
i−1
X
2j
j=0
= 2n−1 T (1) + d
n−2
X
2j
j=0
= c2n−1 + d(2n−1 − 1) = (c + d)2n−1 − d
Quindi
T (n) = Θ(2n )
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 16/37
Purtroppo l’algoritmo risolve gli stessi sottoproblemi piú volte
Albero delle chiamate ricorsive di C HOOSE(6, 4))
6
4
5
5
3
4
2
3
1
4
3
3
2
2
1
1
0
4
3
2
2
2
1
1
1
0
2
1
2
2
1
1
4
4
3
3
4
3
3
2
3
3
2
1
2
2
1
0
1
1
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 17/37
Situazione quindi giá vista...
Abbiamo cioé un algoritmo inefficiente C HOOSE(n, r) per il calcolo
n
dei numeri r , la cui inefficienza risiede nel fatto che esso ricalcola
(mediante chiamate ricorsive ad esso stesso) quanto giá calcolato
in precedenza.
Sappiamo giá come ovviare a tale situazione: aggiungere
i
all’algoritmo una tabella T [i, j] che contenga i valori j man mano
che li calcoliamo.
Faremo precedere ad ogni chiamata ricorsiva dell’algoritmo
i
C HOOSE(i, j) un controllo per verificare se numero j é stato
precedentemente computato; nel caso affermativo ci limiteremo a
leggerlo dalla tabella in T [i, j], altrimenti sviluppiamo la ricorsione
per calcolarlo (ció accadrá una sola volta), e lo memorizzeremo in
T [i, j].
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 18/37
Algoritmo ricorsivo per il calcolo di
n
r
M EM C HOOSE(n, r) % (si fá uso di una tabella ausiliaria
T [1...n, 0...r])
if r = 0 oppure n = r then return (1)
else if T [n, r] non é definito
T [n, r] ← M EM C HOOSE(n − 1, r − 1) +M EM C HOOSE(n − 1, r)
return (T [n, r])
Espandendo in un albero tutte le chiamate ricorsive sull’esempio
n = 6 e r = 4 (esercizio molto consigliato!) ci si rende conto che
l’algoritmo M EM C HOOSE(6, 4) riempie le entrate della tabella
T [1...6, 0...4] a partire da quelle che appaiono sulle foglie
dell’albero, e poi via via dal basso in alto, fino all’entrata T [6, 4] che
conterrá l’output dell’algoritmo.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 19/37
Algoritmo iterativo per il calcolo di
i
j
n
r
Vogliamo calcolare tutti valori
, per i = 0, . . . n e j = 0, . . . r, e
metterli nella tabella T [i, j] (noi siamo interessati al valore T [n, r]).
i
i−1
i−1
i
La formula per j é
j = j−1 +
j
i
i
con le condizioni iniziali
i = 1= 0
L’algoritmo iterativo é:
I TER C HOOSE(n, r)
for i ← 0 to n − r do T [i, 0]←1
for i ← 0 to r do T [i, i]←1
for j ← 1 to r do
for i ← j + 1 to n − r + j do
T [i, j]←T [i − 1, j − 1] + T [i − 1, j]
return (T [n, r])
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 20/37
Come funziona l’algoritmo
I TER C HOOSE(n, r)
for i ← 0 to n − r do T [i, 0]←1
for i ← 0 to r do T [i, i]←1
for j ← 1 to r do
for i ← j + 1 to n − r + j do
T [i, j]←T [i − 1, j − 1] + T [i − 1, j]
return (T [n, r])
Al momento dell’ assegnazione T [i, j] ← T [i − 1, j − 1] + T [i − 1, j]
occorre che T [i − 1, j − 1] e + T [i − 1, j] siano stati giá calcolati (e infatti
l’algoritmo correttamente procede in questo modo)
j−1 j
i−1
i
+
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 21/37
Ricordiamo: T [i, j] = T [i − 1, j − 1] + T [i − 1, j], T [i, 0] = T [i, i] = 1
0
0
-1
-2
-3
-4
-5
-6
-7
-8
1
2
3
4
5
6
7
8
9
10
1
1
1
j−1 j
1
2
i−1
1
i
1
3
3
1
1
4
6
4
1
1
5
10
10
5
1
1
6
15
20
15
6
1
1
7
21
35
35
21
7
+
1
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 22/37
Analisi dell’algoritmo I TER C HOOSE(n, r)
I TER C HOOSE(n, r)
for i ← 0 to n − r do T [i, 0] ← 1
for i ← 0 to r do T [i, i] ← 1
for j ← 1 to r do
for i ← j + 1 to n − r + j do
T [i, j] ← T [i − 1, j − 1] + T [i − 1, j]
return (T [n, r])
La tabella T [0...n, 0...r] ha n · r ≤ n2 entrate, ciascuna viene calcolata con
una addizione dalle precedenti due (ció richiede tempo O(1)).
Quindi l’algoritmo ha complessitá O(n2 ) (ricordiamo che il primo algoritmo
ricorsivo aveva complessitá Θ(2n ))
L’algoritmo usa O(n2 ) spazio per memorizzare la tabella, ma poiché
T [i, j] = T [i − 1, j − 1] + T [i − 1, j], ad ogni iterazione dell’algoritmo basta
solo memorizzare la colonna j − 1 e j. Quindi l’algoritmo necessita solo
di O(n) locazioni di memoria.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 23/37
Morale
• Quando, nella risoluzione di un problema, Divide et Impera
genera molti sottoproblemi identici, la ricorsione porta ad algoritmi
inefficienti
• Conviene allora memorizzare la soluzione ai sottoproblemi in una
tabella, e leggerli all’occorrenza
Questa é l’essenza della Programmazione Dinamica
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 24/37
Per progettare un algoritmo basato con la PD occorre:
Identificazione:
• progettare un algoritmo Divide et Impera
• analizzarlo: tempo di calcolo esponenziale
• Perchè?: problemi identici sono risolti più volte
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 25/37
Per progettare un algoritmo basato con la PD occorre:
Costruzione:
• prendi la parte dell’algoritmo D&I che fá l’ “Impera", e rimpiazza le
chiamate ricorsive con letture in tabella
• invece di ritornare un valore, memorizzalo in un entrata della
tabella
• usa le condizione di base di D&I per riempire l’inzio della tabella
• riempi la tabella, seguendo un opportuno ordine, in modo tale che
il calcolo di nuove entrate della tabella venga effettuato usando
valori della tabella giá riempiti in precedenza
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 26/37
Confronto tra D&I e PD:
Divide et Impera:
C HOOSE(n, r)
if r = 0 oppure n = r then return (1)
else return (C HOOSE (n − 1, r − 1)+ C HOOSE (n − 1, r))
M EM C HOOSE(n, r)
if r = 0 oppure n = r then return (1)
else if T [n, r] non é definito
T [n, r] ← M EM C HOOSE(n − 1, r − 1) +M EM C HOOSE(n − 1, r)
return (T [n, r])
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 27/37
Confronto tra D&I e PD:
Divide et Impera:
C HOOSE(n, r)
if r = 0 o n = r then return (1)
else return (C HOOSE (n − 1, r − 1)+ C HOOSE (n − 1, r))
Programmazione Dinamica:
I TER C HOOSE(n, r)
for i ← 0 to n − r do T [i, 0] ← 1
for i ← 0 to r do T [i, i] ← 1
for j ← 1 to r do
for i ← j + 1 to n − r + j do
T [i, j] ← T [i − 1, j − 1] + T [i − 1, j]
return (T [n, r])
j−1 j
i−1
i
+
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 28/37
Il problema dello Zaino (semplice)
Dati n oggetti di lunghezza s1 , . . . , sn , esiste un sottoinsieme di tali
oggetti di lunghezza totale S?
s1
s2
s3
s4
s5
s6
S
s5
s2
s3
s4
s6
s1
S
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 29/37
Divide et Impera: individuazione e risoluzione sottoproblemi
Vogliamo progettare un algoritmo Z AINO(i, j) che su input (i, j)
restituisca valore True se e solo se esiste un sottoinsieme dei primi
i oggetti s1 , . . . si di lunghezza totale pari a j (alla fine ci servirá
Z AINO(n, S))
s1
s2
s3
s4
...
si−1
si
j
Quando Z AINO(i, j) restituirá valore True?
Distinguiamo i due casi: 1) o l’oggetto i-esimo si viene usato per
arrivare alla lunghezza totale j, 2) oppure l’oggetto i-esimo non é
usato.
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 30/37
Divide et Impera
1) Se l’oggetto i-esimo si non viene usato per arrivare alla
lunghezza totale j, allora Z AINO(i, j) restituirá valore True se e
solo se Z AINO(i − 1, j) restituisce valore True
s1
s2
s3
s4
...
si−1
j
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 31/37
Divide et Impera
1) Se l’oggetto i-esimo si viene usato per arrivare alla lunghezza
totale j, allora Z AINO(i, j) restituirá valore True se e solo se
Z AINO(i − 1, j − si ) restituirá valore True
s1
s2
s3
si−1
s4
...
si
j − si
j
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 32/37
Il “codice”
Z AINO(i, j) %(ritorna True se da s1 , . . . , si si puó riempire tutto j)
if i = 0 then return (j = 0)
%(True se j = 0, False altrimenti)
else if Z AINO(i − 1, j) then return (True)
else if si ≤ j then
return (Z AINO(i − 1, j − si ))
T (n) = tempo di esecuzione di Z AINO(n, S)



c
se n = 1,


T (n) =



2T (n − 1) + d se n > 1
come prima, ha soluzione T (n) = Θ(2n )
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 33/37
Programmazione Dinamica
Memorizza Z AINO(i, j) in una tabella t[i, j]:
t[i, j] é posto a True se e solo se o vale
• t[i − 1, j] é True, oppure
• t[i − 1, j − si ] ha senso ed é stato posto in precedenza a True
Ció viene fatto col seguente codice
t[i, j] ← t[i − 1, j]
if j − si ≥ 0 then
t[i, j] ← (t[i, j] OR t[i − 1, j − si ])
j − si
i−1
i
j
per calcolare t[i, j] occorre
che la riga i − 1 sia stata giá
calcolata, quindi calcoleremo
la matrice t[·, ·] riga per riga,
da sinistra a destra, e
dall’alto in basso
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 34/37
L’algoritmo di Programmazione Dinamica
Z AINO(s1 , . . . , sn , S)
1. t[0, 0] ← True
2. for i ← 1 to S do t[0, j] ← False
3. for i ← 1 to n do
4. for j ← 0 to S do
5.
t[i, j] ← t[i − 1, j]
6.
if j − si ≥ 0 then
7.
t[i, j] ← (t[i, j] ∨ t[i − 1, j − si ])
8. return (t[n, S])
Analisi: Linee 1. e 8. costano O(1). Il for sulla linea 2. costa O(S).
Linee 5–7 costano O(1). Il for sulle linee 4–7 costa O(S). Il for
sulle linee 3–7 costa O(nS).
In totale, il tempo di esecuzione é O(nS)
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 35/37
Esempio
s1 = 1, s2 = 2, s3 = 2, s4 = 4, s5 = 5, s6 = 2, s7 = 4, S = 15
t[0, 0] =True, t[0, j] =False
t[i, j] ← (t[i − 1, j] ∨ t[i − 1, j − si ])
0
0 T
1 T
2 T
3
4
5
6
7
1
F
T
T
2
F
F
T
3
F
F
T
4
F
F
F
5
F
F
F
6
F
F
F
7
F
F
F
8
F
F
F
9
F
F
F
10 11 12 13 14 15
F F F F F F
F F F F F F
F F F F F F
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 36/37
Esercizi
Completare la tabella
É possibile calcolare t[n, S] usando solo 2 righe di t[·, ·]?
É possibile calcolare t[n, S] usando solo una riga di t[·, ·]?
Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 37/37