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