Note per la Lezione 7 Ugo Vaccaro

Documenti analoghi
Programmazione dinamica

Note per la Lezione 4 Ugo Vaccaro

Introduzione alla tecnica di Programmazione Dinamica

Riassumiamo le proprietà dei numeri reali da noi utilizzate nel corso di Geometria.

Un tipico esempio è la definizione del fattoriale n! di un numero n, la cui definizione è la seguente:

Algoritmi e strutture di dati 2

Dati e Algoritmi I (Pietracaprina) Esercizi sulle Nozioni di Base

PROGRAMMAZIONE DINAMICA

Algoritmi e Strutture Dati (Mod. B) Algoritmi Greedy (parte I)

Esercizi di Algoritmi e Strutture Dati

Tecniche Algoritmiche: divide et impera

5. DIVIDE AND CONQUER I

Lezione 4. Problemi trattabili e soluzioni sempre più efficienti. Gianluca Rossi

Algoritmi e Strutture Dati

Metodi per la risoluzione di sistemi lineari

Sistemi lineari. Lorenzo Pareschi. Dipartimento di Matematica & Facoltá di Architettura Universitá di Ferrara

Tempo e spazio di calcolo (continua)

1 Definizione di sistema lineare omogeneo.

Il problema dello zaino

Due numeri naturali non nulli a, b tali che MCD(a,b) = 1 si dicono coprimi o relativamente primi.

Cammini minimi fra tutte le coppie

LEZIONE 4. { x + y + z = 1 x y + 2z = 3

NOTE DI ALGEBRA LINEARE v = a 1 v a n v n, w = b 1 v b n v n

Studio degli algoritmi

2. ALGORITMO DEL SIMPLESSO

Programmazione dinamica

Applicazioni eliminazione di Gauss

A lezione sono stati presentati i seguenti passi per risolvere un problema:

3/10/ Divisibilità e massimo comun divisore

COME CALCOLARE LA COMBINAZIONE DI MINIMO COSTO DEI FATTORI

Ricerca Operativa. G. Liuzzi. Lunedí 20 Aprile 2015

Algoritmi e Strutture Dati

COMPLESSITÀ COMPUTAZIONALE DEGLI ALGORITMI

Possibile applicazione

4 0 = 4 2 = 4 4 = 4 6 = 0.

3.4 Metodo di Branch and Bound

1 Esercizi di Matlab. L operatore : permette di estrarre sottomatrici da una matrice assegnata. Vediamo alcuni esempi.

Anno Scolastico 2014/15 - Classe 1D Verifica di matematica dell 11 Maggio Soluzioni degli esercizi. 2(x 2) 2(x 1) + 2 = 3x

= elemento che compare nella seconda riga e quinta colonna = -4 In generale una matrice A di m righe e n colonne si denota con

Algoritmi di ordinamento

2.6 Calcolo degli equilibri di Nash

Tecniche avanzate di sintesi di algoritmi: Programmazione dinamica Algoritmi greedy

Note sull implementazione in virgola fissa di filtri numerici

RISOLUZIONE IN LOGICA PROPOSIZIONALE. Giovanna D Agostino Dipartimento di Matemaica e Informatica, Università di Udine

Sui determinanti e l indipendenza lineare di vettori

Giovanna Carnovale. October 18, Divisibilità e massimo comun divisore

Esercizi sulla complessità di frammenti di pseudo-codice

Fondamenti di Informatica

Metodi e Modelli per l Ottimizzazione Combinatoria Cover inequalities

ax 1 + bx 2 + c = 0, r : 2x 1 3x = 0.

1 IL LINGUAGGIO MATEMATICO

11.4 Chiusura transitiva

Zeri di funzioni e teorema di Sturm

SOLUZIONI DEL 1 0 TEST DI PREPARAZIONE ALLA 1 a PROVA INTERMEDIA

Algoritmi e Strutture Dati

Sistemi di 1 grado in due incognite

Corso di Informatica di Base

a + 2b + c 3d = 0, a + c d = 0 c d

Divide et impera. Divide et impera. Divide et impera. Divide et impera

Laboratorio di Python

Esercitazione 6 - Soluzione

Monomi L insieme dei monomi

Esercizi Capitolo 11 - Strutture di dati e progettazione di algoritmi

Problemi, istanze, soluzioni

Anno 5 Regole di derivazione

1 Cambiamenti di riferimento nel piano

ESERCIZI SULLE MATRICI

m = a k n k + + a 1 n + a 0 Tale scrittura si chiama rappresentazione del numero m in base n e si indica

SISTEMI LINEARI. x y + 2t = 0 2x + y + z t = 0 x z t = 0 ; S 3 : ; S 5x 2y z = 1 4x 7y = 3

Equazioni di primo grado

A.A CORSO DI ALGEBRA 1. PROFF. P. PIAZZA, E. SPINELLI. SOLUZIONE ESERCIZI FOGLIO 5.

Equazioni esponenziali e logaritmi

Geometria e Topologia I (U1-4) 2006-mag-10 61

Corso di Analisi Numerica

Progettazione di Algoritmi

Linguaggi di programmazione - Principi e paradigmi 2/ed Maurizio Gabbrielli, Simone Martini Copyright The McGraw-Hill Companies srl

Elementi di Algebra e di Matematica Discreta Numeri interi, divisibilità, numerazione in base n

Transcript:

Progettazione di Algoritmi Anno Accademico 2016 2017 Note per la Lezione 7 Ugo Vaccaro Sempre sulla tecnica Programmazione Dinamica Ricordiamo che lo sviluppo di algoritmi basati sulla Programmazione Dinamica prevede generalmente due passi separati: Formulare il problema in termini ricorsivi: ovvero scrivere una espressione per la soluzione all intero problema che sia una combinazione di soluzioni a sottoproblemi di taglia minore Calcolare la soluzione globale al problema in modo ricorsivo, facendo precedere ciascuna chiamata ricorsiva con un controllo per verificare se la soluzione al relativo sottoproblema è stata già calcolata. Gli algoritmi di Programmazione Dinamica hanno bisogno di memorizzare le soluzioni ai sottoproblemi intermedi. Spesso (ma non sempre) ciò viene effettuato memorizzandole in tabelle. Il secondo passo prima descritto può essere sostituito in algoritmi iterativi con il seguente: Calcolare le soluzioni ai sottoproblemi in maniera bottom-up : scrivere un algoritmo che parta con i casi base della ricorrenza e proceda via via considerando (e risolvendo) problemi di taglia sempre maggiore, considerandoli nell ordine corretto Applichiamo la tecnica di Programmazione Dinamica al seguente problema. Cambio delle monete. Input: Un valore monetario V, un insieme di monete che denoteremo con l insieme {1,...,n, i cui valori (ad es., in Euro)) sono contenuti nel vettore v = v[1]...v[n], con v[1] > v[2] >... > v[n] = 1. In altri termini, la moneta generica i vale v[i] Euro. Output: Il minimo numero di monete il cui valore totale sia esattamente pari a V. (Assumiamo di avere a disposizione un numero illimitato di monete di valore v[i], per ogni i) In altri termini, indicato con a i 0 il numero di monete di valore v[i] che usiamo (che può anche essere pari a zero, nel senso che la moneta i-esima non viene utilizzata), vogliamo minimizzare il numero totale di monete usate, pari a a 1 +a 2 +...+a n sotto la condizione che Vediamo un esempio. n a i v[i] = V i=1 1

Sia V = 26 (Euro), l insieme delle monete pari a {1,2,3,4 ed il vettore dei valori delle monete dato da v[1] = 10,v[2] = 5,v[3] = 2,v[4] = 1 In altri termini, abbiamo a disposizione monete di valore pari a 10 Euro, monete di 5 Euro, monete di 2 Euro e monete del valore di 1 Euro. Vogliamo cambiare un assegno di 26 Euro, usando il minor numero di monete possibili. Vi sono diverse possibili soluzioni per esprimere 26 Euro con le monete a disposizione: 1. a 1 = a 2 = a 3 = 0, a 4 = 26 il numero totale di monete usato è a 1 +a 2 +a 3 +a 4 = 26, di valore totale 4 i=1 a iv[i] = 26 2. a 1 = 2, a 2 = 0 a 3 = 3, a 4 = 0 il numero totale di monete usato è a 1 +a 2 +a 3 +a 4 = 5 di valore totale 4 i=1 a iv[i] = 2 10+3 2 = 26 3. a 1 = 2 a 2 = 1 a 3 = 0, a 4 = 1 il numero totale di monete usato è a 1 +a 2 +a 3 +a 4 = 4 di valore totale 4 i=1 a iv[i] = 2 10+1 5+1 1 = 26 Passo 1 di PD: Formulare il problema ricorsivamente: Ovvero: scrivere una formula per la soluzione all intero problema che sia una combinazione di soluzioni a sottoproblemi di taglia minore Domanda: E quali sono i sottoproblemi del problema di partenza (che chiede di esprimere il valore V usando il minor numero di monete, ognuna delle quali di un possibile valore v[1] > v[2] >... > v[n] = 1)? Risposta: Sono tutti i sottoproblemi che si ottengono qualora si voglia esprimere un qualsiasi valore 0 j V usandoilminornumerodimonete, ognunadellequalidiunpossibilevalorev[i] > v[i+1] >... > v[n] = 1, i 1 In altri termini, un generico sottoproblema è individuato dal generico valore j, per j = 0,...,V e dal sottoinsieme di monete {i,...,n, ovvero dal sottovettore dei loro valori v[i]...v[n]. Denotiamo con C(i,j) il minimo numero di monete necessario per esprimere la somma j V, usando monete di valore v[i] > v[i+1] >... > v[n], (noi siamo interessati a C(1,V)) Consideriamo un esempio con V = 12, e monete di valore v[1] = 10, v[2] = 6, v[3] = 1. Nella matrice di seguito riportata l indice di riga i specifica che sono disponibili le monete di valore v[i],...,v[3]. L indice di colonna j specifica il valore monetario totale che si deve esprimere. La generica entrata nella riga i e colonna j della matrice indica il minor numero di monete necessario per poter esprimere il valore monetario j, 0 j 12 con le monete di valore v[i],...,v[3], con 1 i 3. j 0 1 2 3 4 5 6 7 8 9 10 11 12 1 0 1 2 3 4 5 1 2 3 4 1 2 2 i 2 0 1 2 3 4 5 1 2 3 4 5 6 2 3 0 1 2 3 4 5 6 7 8 9 10 11 12 Ad esempio, C(2,8) = 3, dovendo necessariamente usare una moneta di valore 6 e due monete di valore 1. 2

Vediamo ora come esprimere in maniera ricorsiva i valori C(i, j). Ricordiamo che C(i,j) è il minimo numero di monete necessario per esprimere la somma j V, usando monete di valore v[i] > v[i+1] >... > v[n]. Possono accadere due casi: Nella soluzione che ci fornisce il minimo numero di monete C(i,j) per esprimere la somma j V, usando monete di valore v[i] > v[i+1] >... > v[n], di fatto non compare la moneta i-esima di valore v[i]. Ne segue che C(i,j) può essere calcolata tenendo in cosniderazione solo le monete di valore v[i + 1] >... > v[n]. Detto in altri termini, in questo caso vale che C(i,j) = C(i+1,j). Può invece accadere che nella soluzione che ci dà il minimo numero di monete C(i,j) per esprimere la somma j V, usando monete di valore v[i] > v[i + 1] >... > v[n], la moneta i-esima di valore v[i] appare. In questo caso, vuol dire che C(i,j) = 1 + k, dove l 1 conta l apparenza della moneta di valore v[i], e il k conta le monete restanti usate, siamo esse i 1,...,i k (ricordiamo che alcune di queste possono anche essere uguali). I valori di tali k monete restanti devono necessariamente sommare a j v[i] (visto che poi sommando la moneta di valore v[i], che sappiamo esserci, arriviamo al valore totale j). L osservazione chiave è che che k non è un numero sconosciuto, ma k è proprio uguale a C(i,j v[i]). Detto in altre parole, k è il minimo numero di monete per esprimere la somma j v[i] V, usando monete di valore v[i] > v[i+1] >... > v[n]. Infatti, se k fosse > C(i,j v[i]), allora potremmo sostituire le monete i 1,...,i k con le C(i,j v[i]) monete che sò, per definizione di C(, ), avere come somma di valori uguale a j v[i], ed ottenere, usando poi la moneta di valore v[i], un valore totale pari a (j v[i])+v[i] = j, avendo impiegato un numero di monete pari a 1 + C(i,j v[i]) < 1 + k = C(i,j), contro l ipotesi che C(i,j) è il minimo numero di monete per esprimere la somma j V, usando monete di valore v[i] > v[i + 1] >... > v[n]. Se invece k fosse < C(i,j v[i]), l assurdo sarebbe ancora più immediato. Infatti, avremmo che con le monete i 1,...,i k (che sono in numero inferiore a C(i,j v[i])) riusciremmo ad esprimere il valore j v[i], contro l ipotesi che C(i,j v[i]) è il minimo numero di monete per esprimere la somma j v[i] V, usando monete di valore v[i] > v[i + 1] >... > v[n]. Riassumendo, abbiamo dimostrato che nel caso in cui nella soluzione che ci dà il minimo numero di monete per esprimere la somma j V, usando monete di valore v[i] > v[i+1] >... > v[n], la moneta i-esima di valore v[i] appare, allora vale che C(i,j) = 1+C(i,j v[i]). Poichè noi a priori non sappiamo se nella soluzione che ci dà il minimo numero di monete C(i,j) per esprimere la somma j V, usando monete di valore v[i] > v[i+1] >... > v[n], la moneta i-esima di valore v[i] appare o non appare, ci calcoliamo le sottosoluzioni C(i+1,j) e 1+C(i,j v[i]) ad entrambi i sottoproblemi e ci prendiamo la migliore (ovvero la migliore soluzione tra quella che contiene la moneta di valore v[i] e quella che non contiene v[i]). Detto in altri termini, vale che { C(i+1,j) se v[i] > j, C(i,j) = min{c(i+1,j),1+c(i,j v[i]) se v[i] j con i casi base della ricorrenza pari a: C(n,j) = j, j = 0,...,V. Il secondo passo nella applicazione della tecnica Programmazione Dinamica consisterà in: Passo 2 di PD: Introduci una tabella dove memorizzare il computo delle sottosoluzioni al problema di partenza: Ovvero: calcola la soluzione ai distinti sottoproblemi una volta soltanto, memorizza ciascuna sottosoluzione nella entrata opportuna di una tabella T[i,j], in modo tale che esse possano essere usate nel seguito, se occorre. 3

Abbiamo quindi il seguente algoritmo ricorsivo per il calcolo di C(1, V): Rec CambioMonete(v[i]...v[n],j) % fà uso di una tabella T(i,j) 1. IF (i==n) { 2. RETURN j 3. ELSE { 4. IF (T(i,j) non è definito) { 5. IF (v[i] j){ 6. T(i,j) = min(rec CambioMonete(v[i+1...n],j),1+Rec CambioMonete(v[i...n],j v[i])) ELSE { 7. T(i,j) = Rec CambioMonete(v[i+1...n],j) 8. RETURN(T(i, j)) Per l analisi della complessità di tempo di Rec CambioMonete(v[1...n],V), osserviamo che ciascuna delle nv entrate della matrice T[, ] viene calcolata una ed una sola volta, ed il tempo necessario per il calcolo di una arbitraria entrata è O(1). Per cui l algoritmo Rec CambioMonete(v[1...n], V) ha complessità O(nV). Sull esempio con V = 12, e monete di valore v[1] = 10, v[2] = 6, v[3] = 1 l algoritmo costruirebbe la matrice seguente, e produrrebbe in output il valore C(1,12) = 2 j 0 1 2 3 4 5 6 7 8 9 10 11 12 1 0 1 2 3 4 5 1 2 3 4 1 2 2 i 2 0 1 2 3 4 5 1 2 3 4 5 6 2 3 0 1 2 3 4 5 6 7 8 9 10 11 12 Il problema del Cambio di Monete è il primo esempio di Problema di Ottmizzazione che abbiamo visto. Informalmente, un Problema di Ottimizzazione è caratterizzato dal fatto che ad ogni possibile istanza di input (ad es., il valore V ed i valori v[1],...,v[n] delle monete nel problema precedente), è possibile associare piú soluzioni (ad es., i diversi modi di esprimere il valore V con le monete di valore v[1],...,v[n]). A ciascuna possibile soluzione è associato un costo (ad es., il numero di monete per esprimere V). Ciò che noi cerchiamo è una soluzione di minimo costo (o di massimo costo, se esso rappresenta un guadagno per noi). I problemi di ottimizzazione sono quindi caratterizzati dal fatto che ogni istanza di input può avere diverse possibili soluzioni, e noi cerchiamo quella che ottimizza il costo (ovvero, lo minimizza o lo massimizza, a seconda dello specifico problema in questione). Applichiamo ora la tecnica di Programmazione Dinamica al seguente problema. Input del problema: Supponiamo di avere un insieme A = {A 1,A 2,...,A n di attività, dove ciascuna attività A i ha un tempo di inizio s i, un tempo di fine f i, con s i < f i (in altre parole, l attività A i deve essere svolta nell intervallo temporale [s i,f i ]), ed un certo valore v(a i ) = v i. Le attività in A devono essere eseguite da un server, sotto la condizione che A i ed A j possono essere entrambe eseguite se e solo se [s i,f i ] [s j,f j ] = (in tal caso, diremo che l attivitá A i ed A j sono compatibili). In altri termini, possono essere eseguite dal server solo attività il cui svolgimento temporale non si sovrappone (si pensi, ad esempio, alle attività come dei job che un sistema operativo deve far eseguire da una CPU che può eseguire una sola attività alla volta ed una volta iniziata un attività questa non può essere interrotta ma bensì eseguita fino alla sua terminazione). Output del problema: Calcolare un sottoinsieme di S A di attività a due a due compatibili, di valore totale 4

A S v(a) massimo. Ovvero, vogliamo calcolare max S A:S è composto da attività mutualmente compatibili v(a). Per semplicità, indichiamo l insieme delle attività {A 1,A 2,...,A n semplicemente con l insieme {1,2,...,n. Vediamo un esempio. A S v = 4 v = 2 v = 3 v = 5 v = 3 v = 5 v = 5 v = 7 0 1 2 3 4 5 6 7 8 9 10 11 12 tempo Si vede che la prima e l ultima attivita (di colore blu) sono compatibili, di valore totale 11. Però, anche la seconda, quinta (di colore rosso) e l ultima attività sono compatibili, di valore totale 12. Al fine di progettare un algoritmo per il problema dello Scheduling di Attività, rinomiano innanzitutto le attività in ordine di terminazione (oovero ordiniamole in base ai numeri f i ) per cui varrà: f 1 f 2... f n. Inoltre, per ogni attività j, sia p(j) = il più grande indice i < j tale che attività i è compatibile con l attività j (p(j) = 0 se tale indice non esiste) Nell esempio precedente, avremmo che dopo aver ordinato la situazione è quella riportata nella figura di sotto. Inoltre, avremmo che p(8) = 5, p(7) = 3, p(2) = 0. 1 3 2 4 5 6 0 1 2 3 4 5 6 7 8 9 10 11 12 tempo 7 8 5

Sia O una soluzione di valore ottimo al problema in questione, ovvero sia O un sottoinsieme delle attività A = {A 1,...,A n composta da attività a due a due compatibili, di valore totale A Ov(A) massimo possibile. Sicuramente, o vale che n (l ultima attività) O, oppure vale che n / O Se n O allora tutte le attività p(n)+1,p(n)+2,...,n 1 intersecano n, quindi esse non possono essere in O. Inoltre, se n O allora O {n è una soluzione ottima per le attività {1,2,...,p(n) (che non intersecano l attività n). Perchè? Perchè se O {n non fosse ottima relativamente all insieme delle attività {1,2,...,p(n), allora si potrebbe trovare una soluzione in {1,2,...,p(n) migliore di O {n. Tale soluzione, unita all attività n sarebbe una soluzione relativamente all insieme delle attività {1, 2,..., n globalmente migliore di O stessa! Ciò è contro l ipotesi di partenza che O è un sottoinsieme di A composta da attività a due a due compatibili, di valore totale A Ov(A) massimo possibile. Se invece l ultima attività non è presente nella soluzione ottima O, allora O è chiaramente anche una soluzione ottima per l insieme delle attività {1,2,...,n 1 Applichiamo ora il primo passo per la risoluzione del problema in questione, utilizzando la tecnica Programmazione Dinamica, ovvero formuliamo la soluzione del prooblema in termini ricorsivi, cioè scriviamo una formula per la soluzione all intero problema che sia una combinazione di soluzioni a sottoproblemi di taglia minore. 1 j n, sia O j una soluzione ottima per il sottoproblema costituito dalle attività {1,...,j, e sia OPT(j) il valore di O j (noi cerchiamo OPT(n)). Da quanto detto prima, o vale che j O j (ed in tal caso O j non può contenere le attività p(j)+1,...,j 1). Inoltre O j {j è una soluzione ottima (ovvero di valore OPT(j)) per le attivitá {1,2,...,p(j) In simboli Oppure vale che j / O j, ed in tal caso vale che Tutto ciò vuol dire che OPT(j) = v j +OPT(p(j)). OPT(j) = OPT(j 1). OPT(j) = max{v j +OPT(p(j)),OPT(j 1). In sintesi OPT(j) = { 0 se j = 0, max{v j +OPT(p(j)),OPT(j 1) se j > 1 Il che ci suggerisce il seguente algoritmo Input: n,s 1,...,s n,f 1,...,f n,v 1,...,v n 1. Ordina le attività in modo che f 1... f n 2. Calcola p(1),...,p(n) 3. Calcola-OPT(j) 4. IF(j == 0) { 5. RETURN 0 6. ELSE { 7. RETURN max{v j +Calcola-OPT(p(j)),Calcola-OPT(j 1) 8. 6

Noi siamo interessati alla esecuzione di Calcola-OPT(n). Purtroppo, l algoritmo Calcola-OPT(n) ha complessità esponenziale nel caso peggiore. Supponiamo infatti che i tempi di inizio e fine di ogni attività siano tali che ogni attività A j inizia prima che l attività A j 1 sia terminata, ma dopo la terminazione di A j 2, per j = 2,...,n. Ciò comporta che p(j) = j 2, per j = 2,...,n. Ma se questo è il caso, si vede dalla linea 7. dell algoritmo prima riportato, che Calcola-OPT(n) effettua due chiamate ricorsive a se stesso, ovvero a Calcola-OPT(n 2) e Calcola-OPT(n 1). Per cui, detta T(n) la complessità di Calcola-OPT(n), ciò comporta che T(n) = T(n 2)+ T(n 1). Già sappiamo che questa equazione di ricorrenza ha soluzione esponenziale, purtroppo! Perchè l algoritmo di D&I Calcola-OPT(n) è esponenziale? Perchè risolve stessi problemi più volte, come si può vedere, as esempio, guardando l albero delle chiamate ricorsive di Calcola-OPT(5). 1 OPT(5) 2 OPT(4) OPT(3) 3 OPT(3) OPT(2) OPT(2) OPT(1) 4 5 OPT(2) OPT(1) p(1) = 0,p(j) = j 2 OPT(j)=max{v j+opt(p(j)), OPT(j-1) OPT(1) OPT(0) OPT(1) OPT(0) OPT(1) OPT(0) É il momento, quindi, di usare la Programmazione Dinamica. Ricordiamo che esistono due approcci per trasformare un inefficiente algoritmo di Divide et Impera in un efficiente algoritmo. La prima, basata sulla tecnica della Memoization, che aggiunge all algoritmo una tabella in cui vengano memorizzate le soluzioni ai sottoproblemi giá risolti. Viene altresí addottata l addizionale accortezza che prima di ogni chiamata ricorsiva dell algoritmo su di un particolare sottoproblema, debba essere effettuato un controllo sulla tabella per verificare se la soluzione a quel sottoproblema è stata già calcolata in precedenza. La seconda tecnica risolve semplicemente tutti i sottoproblemi del problema di partenza, in maniera iterativa ed in modo bottom-up, ovvero risolvendo prima i sottoproblemi di taglia piccola e poi via via quelli di taglia maggiore fino a risolvere l intero problema di partenza Quale approccio è migliore? Dipende... Entrambi hanno i loro meriti. L approccio basato sulla memorizzazione preserva la struttura ricorsiva tipica degli algoritmi basati su Divide et Impera (che sono in generale semplici ed eleganti). Per contro, vi è un aggiunta di lavoro, tipo gestione stack, etc., che in certe situazioni può diventare significativo. L approccio iterativo bottom-up è in generale efficiente. Tuttavia, gli algoritmi basati su questo approccio tendono a calcolare la soluzione a tutti i sottoproblemi del problema originale, anche quelli che potrebbero non concorrere alla soluzione ottima del problema di partenza. Ciò non accade per gli algoritmi basati sul primo approccio, che risolvono solo i sottoproblemi strettamente necessari. Vediamo in dettaglio l applicazione della tecnica Programmazione Dinamica mediante il primo approccio, ovvero usando la Memoization. Ovvero, memorizziamo le soluzioni di ciascun sottoproblema, e le ri-leggiamo all occorrenza. 7

Input: n,s 1,...,s n,f 1,...,f n,v 1,...,v n % fà uso di un array M 1. Ordina le attività in modo che f 1... f n 2. Calcola p(1),...,p(n) 4. M Calcola OPT(n) 5. IF(M[j] non è definito) { 6. M[j] = max{v j +M Calcola OPT(p(j)),M Calcola OPT(j 1) 7. RETURN M[n] L istruzione 1. richiede tempo O(n log n) per ordinare. L istruzione 2. richiede tempo O(n) (una volta aver ordinato). Il FOR in 3. richiede tempo O(n). L istruzione 4. richiede tempo O(1). M Calcola OPT(n) effettua chiamate al suo interno a M Calcola OPT(j), j < n. Ogni chiamata richiede tempo O(1) e o ritorna un valore M[j] già calcolato, oppure calcola un nuovo valore M[j] facendo due chiamate a valori già calcolati. Il numero totale di chiamate sarà quindi al più pari a 2n, da cui segue che il tempo impiegato da M Calcola OPT(n) è O(n). Sommando tutto otteniamo che l algoritmo ha complessità O(n log n). E se vogliamo trovare la soluzione ottima? (e non solo il suo valore). Il seguente algoritmo lo fà. 1. M Calcola OPT(n) 2. Trova Soluzione(n) dove Trova Soluzione(j) 1. IF(j==0) { 2. RETURN nulla 3. ELSE { 4. IF(v j +M[p(j)] > M[j 1]) { 5. stampa j 6. Trova Soluzione(p(j)) 7. ELSE {Trova Soluzione(j 1) Il numero di chiamate ricorsive è n. Di conseguenza, la complessità dell algoritmo è O(n). 8