Introduzione alla tecnica di Programmazione Dinamica Esempio …uv/ALGO/l9.pdf · Nei problemi...

37
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 ´ a degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 1/37

Transcript of Introduzione alla tecnica di Programmazione Dinamica Esempio …uv/ALGO/l9.pdf · Nei problemi...

Sommario della lezione

Introduzione alla tecnica di Programmazione Dinamica

Esempio di applicazione n. 1: Calcolo dei numeri diFibonacciEsempio di applicazione n. 2: Calcolo di binomialiEsempio di applicazione n. 3: Problema dello Zaino

Universita 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!

Universita 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.

Universita 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 sequenzasono

1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, . . .

I numeri di Fibonacci crescono rapidamente, si puó provareinfatti che Fn ≈ 2.694n

Universita 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

FIB(n)

if n ≤ 1 then return 1

else return FIB(n− 1)+FIB(n− 2)

Sia T (n) la complessitá di FIB(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)

Universita 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 FIB(n) é pari a

T (n) = 2Fn − 1 ≈ 2× 2.694n, troppo!

Dove stá il problema?

Il problema stá nel fatto che il programma FIB viene chiamato sullo

stesso input molte volte, é ció é chiaramente inutile.

Universita 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 FIB(5).

FIB(5)

FIB(4) FIB(3)

FIB(3) FIB(2) FIB(2) FIB(1)

FIB(1) FIB(2) FIB(1) FIB(0) FIB(1) FIB(0)

FIB(1) FIB(0)

FIB(1) viene calcolato 5 volte,

FIB(0) e FIB(2) 3 volte, FIB(3)

2 volte

Universita 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 FIB(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 FIB(n), é facile individuare anche la cura. Basta

memorizzare in un vettore i valori FIB(i), i < n quando li si calcola

per prima volta, cosicché in future chiamate ricorsive a FIB(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

Universita 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

MEMFIB(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]← MEMFIB(n− 1) + MEMFIB(n− 2)

return F [n]

L’ aspetto importante dell’algoritmo MEMFIB(n) é che esso, prima di

sviluppare la ricorsione per calcolare qualche quantitá MEMFIB(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 MEMFIB(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]

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 9/37

Possiamo quindi anche usare un algoritmo iterativo

ITERFIB(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 ITERFIB(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 FIB(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).

Universita 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 FIB(n) ha complessitá

O(Fn) = O(2.694...n) e che gli algoritmi MEMFIB(n) e ITERFIB(n) hanno

complessitá O(n).

In realtá abbiamo un pó imbrogliato, nel senso che ció che abbiamo

effettivamente provato é che gli algoritmi FIB(n), MEMFIB(n) e

ITERFIB(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 cresconoesponenzialmente ( ≈ come 2.694...n), la loro rappresentazione inbinario é un vettore lungo O(n) bits, quindi ogni operazione che licoinvolge (addizione, sottrazione,... ) costa tempo O(n), e nontempo costante.

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 11/37

Pertanto...

La “vera” complessitá di FIB(n), é O(nFn) = O(n2.694...n), e la“vera” complessitá di MEMFIB(n) e ITERFIB(n) éO(n · n) = O(n2)

In ogni caso, un miglioramento esponenziale lo abbiamoottenuto

Universita 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), ottenutoapplicando D&I al problema in questione2. Abbiamo scoperto che i motivi dell’inefficienza dell’algoritmorisiedevano nel fatto che l’algoritmo risolveva piú volte lo stessosottoproblema3. Abbiamo aggiunto una tabella all’algoritmo, indicizzata dai possibilivalori input (=sottoproblemi) alle chiamate ricorsive.4. Abbiamo altresí aggiunto, prima di ogni chiamata ricorsivadell’algoritmo su di un particolare sottoproblema, un controllo sulla tabellaper verificare se la soluzione a quel sottoproblema era stata giá calcolatain precedenza. Nel caso affermativo, ritorniamo semplicemente lasoluzione al sottoproblema (senza ricalcolarla). Altrimenti, chiamiamoricorsivamente 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

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 13/37

Esempio 2: Calcolo di Combinazioni

Sia(

nr

)

il numero di modi con cui possiamo scegliere r oggetti da

un insieme di n elementi. Come possiamo calcolare(

nr

)

?

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

insieme di n− 1 elementi, e questo si potrá fare in(

n−1

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

un insieme di n− 1 elementi, e questo si potrá fare in(

n−1

r

)

modi).

Ció equivale a dire che(

nr

)

=(

n−1

r−1

)

+(

n−1

r

)

Universita 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

)

CHOOSE(n, r)

if r = 0 o n = r then return (1)

else return (CHOOSE(n− 1, r − 1)+ CHOOSE(n− 1, r))

Analisi: Sia T (n, r) il numero di operazioni effettuate dall’algoritmo

CHOOSE(n, r), e sia T (n) = maxr T (n, r).

Abbiamo

T (n) =

c se n = 1,

2T (n− 1) + d se n > 1

per qualche costante c e d.

Universita 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

...

= 2iT (n− i) + d

i−1∑

j=0

2j

= 2n−1T (1) + d

n−2∑

j=0

2j

= c2n−1 + d(2n−1 − 1) = (c+ d)2n−1 − d

Quindi T (n) = Θ(2n)

Universita 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 CHOOSE(6, 4))

(

6

4

)

(

5

3

) (

5

4

)

(

4

2

) (

4

3

) (

4

3

) (

4

4

)

(

3

1

) (

3

2

)

(

2

1

) (

2

2

)

(

1

0

) (

1

1

)

(

3

2

) (

3

3

)

(

2

1

) (

2

2

)

(

1

0

) (

1

1

)

(

3

2

) (

3

3

)

(

2

1

) (

2

2

)

(

1

0

) (

1

1

)

Universita 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 CHOOSE(n, r) per il calcolo

dei numeri(

nr

)

, 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

all’algoritmo una tabella T [i, j] che contenga i valori(

ij

)

man mano

che li calcoliamo.

Faremo precedere ad ogni chiamata ricorsiva dell’algoritmo

CHOOSE(i, j) un controllo per verificare se numero(

ij

)

é 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].

Universita 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

)

MEMCHOOSE(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]← MEMCHOOSE(n− 1, r − 1) +MEMCHOOSE(n− 1, r)

return (T [n, r])

Espandendo in un albero tutte le chiamate ricorsive sull’esempion = 6 e r = 4 (esercizio molto consigliato!) ci si rende conto chel’algoritmo MEMCHOOSE(6, 4) riempie le entrate della tabellaT [1...6, 0...4] a partire da quelle che appaiono sulle fogliedell’albero, e poi via via dal basso in alto, fino all’entrata T [6, 4] checonterrá l’output dell’algoritmo.

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 19/37

Algoritmo iterativo per il calcolo di(

n

r

)

Vogliamo calcolare tutti valori(

ij

)

, per i = 0, . . . n e j = 0, . . . r, e

metterli nella tabella T [i, j] (noi siamo interessati al valore T [n, r]).

La formula per(

ij

)

é(

ij

)

=(

i−1

j−1

)

+(

i−1

j

)

con le condizioni iniziali(

ii

)

= 1=(

i0

)

L’algoritmo iterativo é:

ITERCHOOSE(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])

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 20/37

Come funziona l’algoritmo

ITERCHOOSE(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 infattil’algoritmo correttamente procede in questo modo)

+

i− 1

j − 1 j

i

Universita 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 1 2 3 4 5 6 7 8 9 100

-1

-2

-3

-4

-5

-6

-7

-8

1

1

1

1

1

1

1

1

1

1

1

1

1

1

1

+

i− 1

j − 1 j

i

2

3

4

5

6

7

3

6

10

15

21

4

10

20

35

5

15

35

6

21 7

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 22/37

Analisi dell’algoritmo I TERCHOOSE(n, r)

ITERCHOOSE(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 conuna addizione dalle precedenti due (ció richiede tempo O(1)).Quindi l’algoritmo ha complessitá O(n2) (ricordiamo che il primo algoritmoricorsivo 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 bastasolo memorizzare la colonna j − 1 e j. Quindi l’algoritmo necessita solodi O(n) locazioni di memoria.

Universita 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

Universita 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

Universita 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

Universita 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:

CHOOSE(n, r)

if r = 0 oppure n = r then return (1)

else return (CHOOSE(n− 1, r − 1)+ CHOOSE(n− 1, r))

MEMCHOOSE(n, r)

if r = 0 oppure n = r then return (1)

else if T [n, r] non é definito

T [n, r]← MEMCHOOSE(n− 1, r − 1) +MEMCHOOSE(n− 1, r)

return (T [n, r])

Universita 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:

CHOOSE(n, r)

if r = 0 o n = r then return (1)

else return (CHOOSE(n− 1, r − 1)+ CHOOSE(n− 1, r))

Programmazione Dinamica:

ITERCHOOSE(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])

+

i− 1

j − 1 j

i

Universita 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

S

s5 s6 s1

s2 s3 s4

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 29/37

Divide et Impera: individuazione e risoluzione sottoprobl emi

Vogliamo progettare un algoritmo ZAINO(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á

ZAINO(n, S))

s1 s2 s3 s4. . .

si−1 si

j

Quando ZAINO(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.

Universita 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 ZAINO(i, j) restituirá valore True se e

solo se ZAINO(i− 1, j) restituisce valore True

s1 s2 s3 s4. . .

si−1

j

Universita 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 ZAINO(i, j) restituirá valore True se e solo se

ZAINO(i− 1, j − si) restituirá valore True

s1 s2 s3 s4. . .

si−1

j

j − si si

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 32/37

Il “codice”

ZAINO(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 ZAINO(i− 1, j) then return (True)

else if si ≤ j then

return (ZAINO(i− 1, j − si))

T (n) = tempo di esecuzione di ZAINO(n, S)

T (n) =

c se n = 1,

2T (n− 1) + d se n > 1

come prima, ha soluzione T (n) = Θ(2n)

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 33/37

Programmazione Dinamica

Memorizza ZAINO(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])

i− 1

j − si

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

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 34/37

L’algoritmo di Programmazione Dinamica

ZAINO(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)

Universita 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

1

2

3

4

5

6

7

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

T F F F F F F F F F F F F F F F

T T F F F F F F F F F F F F F F

T T T T F F F F F F F F F F F F

Universita 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[·, ·]?

Universita degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 37/37