Sommario della lezione Ulteriori esempi di applicazione della Programmazione Dinamica Esempio di applicazione n. 1: Cambio di monete Esempio di applicazione n. 2: Selezione di attività Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 1/36 Programmazione Dinamica: idee di base 1. Esprimi la soluzione al problema in questione in termini di soluzioni a sottoproblemi di dimensione minore 2. Calcola la soluzione ai distinti sottoproblemi una volta soltanto, 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. 2009/10 – p. 2/36 Programmazione Dinamica 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. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 3/36 Programmazione Dinamica Il secondo passo di prima 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 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 4/36 Esempio 1: Problema del cambio delle monete Input: Un valore monetario V , ed un vettore di valori di monete v[1 . . . n], con v[1] > v[2] > . . . > v[n] = 1 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, detto ai ≥ 0 il numero di monete di valore v[i] che usiamo, vogliamo minimizzare il numero totale di monete usate, pari a a1 + a2 + . . . + an sotto la condizione che n X ai v[i] = V i=1 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 5/36 Esempio Sia V = 26 (Euro), ed il vettore dei valori delle monete dato da v[1] = 10, v[2] = 5, v[3] = 2, v[4] = 1 Possibili soluzioni per esprimere 26 Euro con le monete a disposizione: 1. a1 = a2 = a3 = 0, a4 = 26 ⇒ il numero totale di monete usato P4 è a1 + a2 + a3 + a4 = 26, di valore totale i=1 ai v[i] = 26 2. a1 = 2, a2 = 0 a3 = 3, a4 = 0 ⇒ il numero totale di monete usato è a1 + a2 + a3 + a4 = 5 di valore totale P4 i=1 ai v[i] = 2 · 10 + 3 · 2 = 26 3. a1 = 2 a2 = 1 a3 = 0, a4 = 1 ⇒ il numero totale di monete usato è a1 + a2 + a3 + a4 = 4 di valore totale P4 i=1 ai v[i] = 2 · 10 + 1 · 5 + 1 · 1 = 26 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 6/36 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: 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 j ≤ V usando il minor numero di monete, ognuna delle quali di un possibile valore v[i] > v[i + 1] > . . . > v[n] = 1, i ≥ 1 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 7/36 Esempio Sia 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 l’esempio con V = 12, e monete di valore v[1] = 10, v[2] = 6, v[3] = 1. L’indice di riga i specifica che sono disponibili le monete di valore v[i], v[i + 1], . . . , v[n]. L’indice di colonna j specifica il valore monetario totale che si deve esprimere. j i 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 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. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 8/36 Risoluzione dei sottoproblemi C(i, j) C(i, j) = minimo numero di monete per esprimere la somma j ≤ V , usando solo monete di valore v[i] > v[i + 1] > . . . > v[n]. Per risolvere il sottoproblema C(i, j), possiamo o usare oppure non usare la moneta di valore v[i]. Se decidiamo di non usare la moneta di valore v[i], ne segue che per ottenere il valore j occorre risolvere il sottoproblema C(i + 1, j). Essendo questo un sottoproblema di taglia ancora inferiore a quello relativo a C(i, j) (in quanto meno monete vengono usate) possiamo supporre ricorsivamente che esso sia stato risolto, e quindi in questo caso C(i, j) = C(i + 1, j) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 9/36 Risoluzione dei sottoproblemi C(i, j) Se decidiamo invece di usare la moneta di valore v[i], allora ne segue che per completare la soluzione e ottenere il valore j, occorre ottenere preliminarmente il valore j − v[i], usando monete di valore v[i], v[i + 1], . . . , v[n]. Se usassimo un qualche numero k monete per ottenere il valore j − v[i], la nostra soluzione per ottenere il valore j userebbe k + 1 monete (poiché abbiamo giá usato una moneta di valore v[i]). Per minimizzare 1 + k occorre scegliere k il piú piccolo possibile, ovvero occorre usare il minimo numero di monete per risolvere il sottoproblema di ottenere il valore j − v[i] usando monete di valore v[i], v[i + 1], . . . , v[n]. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 10/36 quindi.... Occorre determinare minimo numero di monete per risolvere il sottoproblema di ottenere il valore j − v[i] usando monete di valore v[i], v[i + 1], . . . , v[n] Anche questo é un sottoproblema del problema relativo a C(i, j), in quanto il valore da esprimere, (cioé j − v[i]) é minore del valore j. Possiamo quindi supporre ricorsivamente che esso sia stato risolto, ed in questo caso (cioé che una moneta di valore v[i] venga scelta) varrebbe C(i, j) = 1 + C(i, j − v[i]) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 11/36 Cosa vale quindi? Se decidiamo di non usare la moneta di valore v[i], vale che C(i, j) = C(i + 1, j) Se decidiamo di usare la moneta di valore v[i], vale che C(i, j) = 1 + C(i, j − v[i]) Quindi, in generale, C(i, j) sará pari alla migliore di queste due alternative, ovvero: C(i, j) = C(i + 1, j) se v[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 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 12/36 Algoritmo ricorsivo per il calcolo di C(1, V ) C(i, j) = C(i + 1, j) se v[i] > j, min{C(i + 1, j), 1 + C(i, j − v[i])} se v[i] ≤ j con i casi base: C(n, j) = j, ∀j = 0, . . . , V R EC _C AMBIO M ONETE(v[i...n], j) % fá uso di una tabella T (i, j) if i = n then return (j) else if T (i, j) non é definito T (i, j) ← min(R EC _C AMBIO M ONETE(v[i + 1...n], j), 1 + R EC _C AMBIO M ONETE(v[i...n], j − v[i])) return (T (i, j)) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 13/36 Algoritmo iterativo per il calcolo di C(1, V ) C(i, j) = C(i + 1, j) se v[i] > j, min{C(i + 1, j), 1 + C(i, j − v[i])} se v[i] ≤ j con i casi base: C(n, j) = j, ∀j = 0, . . . , V C AMBIO M ONETE(v[1...n], V ) for j ← 0 to V do C(n, j) ← j for i ← n − 1 downto 1 do for j ← 0 to V do if (v[i] > j OR C(i + 1, j) < 1 + C(i, j − v[i])) do C(i, j) ← C(i + 1, j) else C(i, j) ← C(i, j − v[i]) + 1 return (C(1, V )) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 14/36 Analisi di C AMBIO M ONETE(v[1...n], V ) C AMBIO M ONETE(v[1...n], V ) 1. for j ← 0 to V do 2. C(n, j) ← j 3. for i ← n − 1 downto 1 do 4. for j ← 0 to V do 5. if (v[i] > j OR C(i + 1, j) < 1 + C(i, j − v[i])) do 6 C(i, j) ← C(i + 1, j) 7. else C(i, j) ← C(i, j − v[i]) + 1 8. return (C(1, V )) Analisi: Il for delle linee 1. e 2. prende tempo Θ(V ). Le istruzione nelle linee 5., 6., e 7. prendono tempo Θ(1). Il for delle linee 4., 5., 6., e 7. prende tempo Θ(V ). Il for delle linee da 3. a 7. prende tempo Θ(nV ). Pertanto, l’algoritmo C AMBIO M ONETE(v[1...n], V ) prende in totale tempo Θ(nV ) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 15/36 Esempio 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 i 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 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 La matrice verrebbe costruita riga per riga, dal basso in alto, e da sinistra a destra, secondo la regola: j j − v[i] = min( , ) i i+1 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 16/36 Esercizi L’algoritmo di Programmazione Dinamica per il problema del cambio delle monete usa un array di dimensione n × (V + 1). É possibile modificare l’algoritmo in modo tale che esso usi solo O(n) locazioni di memoria? Giustificare la risposta. L’algoritmo di Programmazione Dinamica per il problema del cambio delle monete restituisce in output il minimo numero di monete per esprimere il valore V , usando solo monete di dati valori v[i], i = 1, . . . n. Progettare un algoritmo che restituisca una tabella U (i, j), con 1 ≤ i ≤ n, e 0 ≤ j ≤ V , tale che U (i, j) = True ⇐⇒ la moneta di valore v[i] viene usata per esprimere il valore j con il minor numero di monete, sotto la condizione che solo monete di valore v[i], . . . , v[n] vengono impiegate. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 17/36 Esercizi L’algoritmo di Programmazione Dinamica per il problema del cambio delle monete restituisce in output il minimo numero di monete per esprimere il valore V , usando solo monete di dati valori v[i], i = 1, . . . n. Progettare un algoritmo che restituisca in output un insieme di cardinalitá minima di monete per esprimere il valore V , usando solo monete di dati valori v[i], i = 1, . . . n. Si usi la tabella U (i, j) calcolata nell’esercizio precedente. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 18/36 Problemi di Ottimizzazione 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[i] 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[i]). A ciascuna soluzione di una istanza di input, é 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) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 19/36 Esempio 2: Selezione di Attività Input del problema: Supponiamo di avere un insieme A = {A1 , A2 , . . . , An } di attivitá , dove ciascuna attivitá Ai ha un tempo di inizio si , un tempo di fine fi , con si < fi (in altre parole, l’attivitá Ai deve essere svolta nell’intervallo temporale [si , fi ]), ed un certo valore vi . Le attivitá in A devono essere eseguite da un server, sotto la condizione che Ai ed Aj possono essere entrambe eseguite se e solo se [si , fi ] ∩ [sj , fj ] = ∅ (in tal caso, diremo che l’attivitá Ai ed Aj 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) Output del problema: Calcolare un sottoinsieme di S ⊆ A di attivitá a due a due compatibili, di valore totale massimo. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 20/36 Esempio 2: Selezione di Attività 1. Attività i inizia al tempo si e termina al tempo fi 2. Attività compatibili se il loro intervallo di svolgimento non si sovrappone 3. Obiettivo: Sottoinsieme di attività compatibili di valore totale massimo v=4 v=2 v=3 v=5 v=3 v=5 v=5 0 1 2 3 4 5 6 7 8 v=7 9 10 11 12 tempo Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 21/36 La prima e l’ultima attività sono compatibili, di valore totale 11 1. Attività i inizia al tempo si e termina al tempo fi 2. Attività compatibili se il loro intervallo di svolgimento non si sovrappone 3. Obiettivo: Sottoinsieme di attività compatibili di valore totale massimo v=4 v=2 v=3 v=5 v=3 v=5 v=5 0 1 2 3 4 5 6 7 8 v=7 9 10 11 12 tempo Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 22/36 La seconda, terza e l’ultima attività sono compatibili, di valore totale 12 1. Attività i inizia al tempo si e termina al tempo fi 2. Attività compatibili se il loro intervallo di svolgimento non si sovrappone 3. Obiettivo: Sottoinsieme di attività compatibili di valore totale massimo v=4 v=2 v=3 v=5 v=3 v=5 v=5 0 1 2 3 4 5 6 7 8 v=7 9 10 11 12 tempo Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 23/36 Rinomina le attività in ordine di terminazione: f1 ≤ f2 ≤ . . . ≤ fn Definizione. Per ogni attività j, p(j) = più grande indice i < j tale che attività i è compatibile con attività j (p(i) = 0 se non cè) Es. p(8) = 5, p(7) = 3, p(2) = 0, 1 2 3 4 5 6 7 8 0 1 2 3 4 5 6 7 8 9 10 11 12 tempo Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 24/36 Sia O una soluzione ottima al problema in questione 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 Se n ∈ O allora O − {n} è una soluzione ottima per le attivitá {1, 2, . . . , p(n)} (che non intersecano n) (Perchè? Perchè se O − {n} non lo fosse, allora si potrebbe trovare una soluzione migliore che unita all’attività n sarebbe globalmente migliore di O stessa!) Se invece n ∈ / O, allora O è una soluzione ottima per l’insieme delle attività {1, 2, . . . , n − 1} Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 25/36 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 ∀1 ≤ j ≤ n, sia Oj una soluzione ottima per il sottoproblema costituito dalle attività {1, . . . , j}, e sia OPT(j) il valore di Oj (noi cerchiamo OPT(n)). Da quanto detto prima, o vale che j ∈ Oj (ed in tal caso Oj NON può contenere le attività p(j) + 1, . . . , j − 1) inoltre Oj − {j} è una soluzione ottima (ovvero di valore OPT(j)) per le attivitá {1, 2, . . . , p(j)} ⇒ OPT(j) = vj + OPT(p(j)) oppure vale che j ∈ / Oj (ed in tal caso OPT(j) = OPT(j − 1)) ⇒ OPT(j) = max{vj + OPT(p(j)), OPT(j − 1)} Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 26/36 In sintesi OPT(j) = 0 se j = 0, max{vj + OPT(p(j)), OPT(j − 1)} se j > 1 Il che ci suggerisce il seguente algoritmo Input: n, s1 , . . . , sn , f1 , . . . , fn , v1 , . . . , vn Ordina le attività in modo che f1 ≤ . . . ≤ fn Calcola p(1), . . . , p(n) Calcola-OPT(j) if (j = 0) return(0) else return(max{vj + Calcola-OPT(p(j)), Calcola-OPT(j − 1)}) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 27/36 Purtroppo l’algoritmo Calcola-OPT(n) ha complessità esponenziale 1 OPT(5) 2 OPT(4) 3 OPT(3) OPT(3) OPT(2) OPT(2) OPT(1) 4 5 OPT(2) p(1)=0, p(j)=j-2 OPT(1) OPT(1) OPT(0) OPT(1) OPT(0) OPT(1) OPT(0) # chiamate cresce (esponen# chiamate su parametro j =(# chiamate su parametro j-1) zialmente) +(# chiamate su parametro j-2) come i ⇑ OPT(j)=max{vj +OPT(p(j)), OPT(j-1)}= max{vj +OPT(j-2), OPT(j-1)} numeri di Fibonacci Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 28/36 Perché l’algoritmo di D&I Calcola-OPT(n) é esponenziale? Perché risolve lo stesso sottoproblema piú volte! Infatti, il numero di sottoproblemi distinti che Calcola-OPT(n) deve in realtá considerare é solo O(n) (tutte le distinte chiamate Calcola-OPT(j)), mentre Esempio: Albero delle chiamate ricorsive di Calcola-OPT(5) 1 OPT(5) 2 OPT(4) 3 OPT(3) OPT(3) OPT(2) OPT(2) OPT(1) 4 5 OPT(2) p(1)=0, p(j)=j-2 OPT(1) OPT(1) OPT(0) OPT(1) OPT(0) OPT(1) OPT(0) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 29/36 É il momento 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 Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 30/36 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”. Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 31/36 Programmazione Dinamica: primo approccio Memoization: Memorizza le soluzioni di ciascun sottoproblema, leggili all’occorrenza. Input: n, s1 , . . . , sn , f1 , . . . , fn , v1 , . . . , vn Ordina le attività in modo che f1 ≤ . . . ≤ fn Calcola p(1), . . . , p(n) for j = 1 to n M [j] ← vuoto M [1] ← 0 M_Calcola_OPT(j) if(M [j] è vuota) M [j] ← max{vj + M_Calcola_OPT(p(j)), M_Calcola_OPT(j−1)} return M [j] Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 32/36 Confrontiamo le ricorsioni nei due algoritmi Calcola-OPT(j) if (j = 0) return(0) else return( max{vj + Calcola-OPT(p(j)), Calcola-OPT(j − 1)}) for j = 1 to n M [j] ← vuoto M [1] ← 0 M_Calcola_OPT(j) if (M [j] è vuota) M [j] ← max{vj + M_Calcola_OPT(p(j)), M_Calcola_OPT(j−1)} return M [j] Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 33/36 E qual è la complessità dell’algoritmo che usa la memoization? Input: n, s1 , . . . , sn , f1 , . . . , fn , v1 , . . . , vn 1.Ordina le attività in modo che f1 ≤ . . . ≤ fn 2.Calcola p(1), . . . , p(n) 3.for j = 1 to n M [j] ← vuoto 4.M [1] ← 0 5. M_Calcola_OPT(j) if(M [j] è vuota) M [j] ← max{vj + M_Calcola_OPT(p(j)), M_Calcola_OPT(j − 1)} return M [j] L’istruzione 1. richiede tempo (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 ⇒ il tempo impiegato da M_Calcola_OPT(n) è O(n). Sommando tutto otteniamo che l’algoritmo ha complessità O(n log n). Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 34/36 E se vogliamo trovare la soluzione ottima? (e non solo il suo valore) Chiama M_Calcola_OPT(n) Chiama Trova_Soluzione(n) Trova_Soluzione(j) if (j = 0) output nulla else if (vj + M [p(j)] > M [j − 1]) print j Trova_Soluzione(p(j)) else Trova_Soluzione(j) Il numero di chiamate ricorsive è ≤ n ⇒ la complessità dell’algoritmo è O(n) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 35/36 PD: seconda versione (iterative e non ricorsiva) Calcoliamo i valori M [j] nell’ordine M [0], M [1], . . . , M [n]. Input: n, s1 , . . . , sn , f1 , . . . , fn , v1 , . . . , vn Ordina le attività in modo che f1 ≤ . . . ≤ fn Calcola p(1), . . . , p(n) Iterative_Compute_Opt M [0] ← 0 for j = 1 to n M [j] ← max{vj + M [p(j)], M [j − 1]} Complessità = O(n) Universitá degli Studi di Salerno – Corso di Algoritmi – Prof. Ugo Vaccaro – Anno Acc. 2009/10 – p. 36/36