! ! Programmazione dinamica Algoritmi e Strutture Dati ✦ ! Capitolo 13 - Programmazione dinamica Università di Trento ! ! ! This work is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-sa/2.5/ or send a letter to Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA. 1 ✦ Il numero di modi di scegliere k oggetti da un insieme di n oggetti ✦ I coefficienti di un’equazione di grado n ( ✓ ◆ 1 n n! C(n, k) = = = k k! · (n k)! C(n (a + b) = n X C(n, k)an ✦ Approccio top-down (problemi divisi in sottoproblemi) “Il pesce grosso mangia tutti i pesci più piccoli”. In altri termini, se non isisottoproblemi sa con esattezza quali sottoproblemi risolvere, si Vantaggioso solo quando sono indipendenti risolverli tutti egliconservare i risultati ottenuti perrisolti poterli ✦ Altrimenti, stessi sottoproblemi possono venire più usare volte successivament il problema di dimensione più grande. Questa tecnica è detta programmazione ✦ Programmazione dinamica ché indica un processo di programmazione (o pianificazione) di un metodo ris ✦ Tecnica iterativa applicabile dinamicamente per dimensioni via via crescenti del problema. Risp ✦ et-impera, la programmazione dinamica presenta tre grandi differenze: è iter Approccio bottom-up corsiva, affrontaquando i sottoproblemi “dal basso verso l’alto” anziché “dall’alto ver ✦ Vantaggiosa ci sono sottoproblemi in comune memorizza i risultati dei sottoproblemi in una tabella. Comunque, mentre il di ✦ Esempio semplice: il triangolo di Tartaglia risulta vantaggioso quando i sottoproblemi più piccoli da risolvere sono indipe grammazione dinamica è conveniente quando i sottoproblemi non sono indipe condividono altri. Procedendo dal basso verso l’alto, la programmazione dinam 2 sottoproblema comune una sola volta, mentre il divide-et-impera lo risolverebb © Alberto Montresor k)!) rapprese di modi di scegliere k oggetti da un insieme di n oggetti, con 0 ⇥ k ⇥ n, ✦ Versione ricorsiva ricorsivamente come segue: Coefficienti binomiali n Tecnica ricorsiva Triangolo Tartaglia Esempiodi13.1 (Coefficiente binomiale). C(n, k) = n!/(k!(n Coefficienti binomiali ✦ ✦ ✦ ! ! ! ! Alberto Montresor © Alberto Montresor Divide-et-impera Una filosofia simile al divide-et-impera, ma portata all’estremo, si basa sulla 1, k ✦ ✦ 1) + C(n Domanda ✦ se k = 0 _ k = n 1, k) altrimenti Deriva direttamente dalla definizione C(n, k) = Complessità? 1 C(n 1, k 1) + C(n se k = 0 ⇤ n = k 1, k) altrimenti Un algoritmo ricorsivo divide-et-impera si ottiene immediatamente dalla pr nizione ricorsiva: k k b integer C(integer n, k) if n = k or k = 0 then return 1 else return C(n k=0 1, k 1) + C(n 1, k) 225 © Alberto Montresor 3 © Alberto Montresor 4 mate ricorsive, che è proprio uguale a C(n, k). Questo è dovuto all’elevato numero di sottoTriangolo diidentici Tartaglia applicare la programmazione dinamica? problemi che vengono risolti più volte. Per esempio già C(n 2, k Quando 1) è richiamata due volte, sia per calcolare C(n 1, k) che C(n 1, k 1), mentre al decrescere dei valori ✦ Versione iterativa ✦ Sottostruttura ottima degli argomenti ciascun sottoproblema è risolto per un numero sempre maggiore di volte. Un ✦ Basata su programmazione dinamica ✦ E' possibile combinare le soluzioni dei sottoproblemi per trovare la soluzione di algoritmo di programmazione dinamica, invece, risolve i sottoproblemi per valori crescenti deun problema più grande ✦gli Domanda argomenti secondo il ben noto schema del “triangolo di Tartaglia”, memorizzando i risultati ✦ PS: In tempo polinomiale! Complessità? una✦ volta per tutte in una matrice intera C[0 . . . n][0 . . . n] in ⇥(n2 ) tempo. ✦ Le decisioni prese per risolvere un problema rimangono valide quando esso diviene un sottoproblema di un problema più grande tartaglia(integer n, integer[ ][ ] C) ✦ for i 0 to n do C[i, 0] 1 C[i, i] 1 for i 2 to n do for j 1 to i 1 do C[i, j] C[i 1, j Sottoproblemi ripetuti ✦ ✦ Spazio dei sottoproblemi ✦ 1] + C[i Un sottoproblema può occorrere più volte Deve essere polinomiale 1, j] © Alberto Montresor 5 © Alberto Montresor 6 In questo modo, il valore Programmazione dinamica di C(m, k), per ogni coppia di interi m, k tali che 0 dikmoltiplicazione m Catena di matrici n, può essere successivamente letto direttamente dalla matrice C in tempo O(1). ✦ Caratterizzare la struttura di una soluzione ottima ✦ Problema: La programmazione dinamica è stata introdotta da Bellman (1957) per progettare algoritmi ✦ Data una sequenza di matrici A , A , A , …, A , compatibili 2 a 2 al prodotto, 1 2 3 n ✦ Definire ricorsivamente il valore di una soluzione ottima che risolvono problemi di ottimizzazione. Perché sia applicabile, occorre che: vogliamo calcolare il loro prodotto. ✦ La soluzione ottima ad un problema contiene le soluzioni ottime ai sottoproblemi (1) sia possibile combinare le soluzioni dei sottoproblemi per trovare la soluzione di un pro✦ Cosa vogliamo ottimizzare Calcolare il valore di una soluzione ottima “bottom-up” blema più grande; ✦ La moltiplicazione di matrici si basa sulla moltiplicazione scalare come (cioè calcolando prima le soluzioni ai casi più semplici) operazione elementare. (2) ✦ le decisioni prese per risolvere in modo ottimo un sottoproblema rimangano valide quando Si usa una tabella per memorizzare le soluzioni dei sottoproblemi ✦ Vogliamo calcolare il prodotto impiegando il numero minore possibile di il sottoproblema diviene un “pezzo” di un problema più grande; ✦ Evitare di ripetere il lavoro più volte, utilizzando la tabella moltiplicazioni (3) una tecnica divide-et-impera richieda di risolvere più volte gli stessi sottoproblemi e sia ✦ Costruire la (una) soluzione ottima. pertanto inutilizzabile da un punto di vista computazionale. ✦ Attenzione: ✦ ✦ Il prodotto di matrici non è commutativo... Le proprietà (1) e (2) prendono il nome di sottostruttura ottima. Ma non sono sufficienti; ✦ ...ma è associativo: (A1 ⋅ A2) ⋅ A3 = A1 ⋅ (A2 ⋅ A3) in pratica, affinché un algoritmo basato sulla programmazione dinamica abbia complessità polinomiale, occorre inoltre che: (4) ci sia un numero polinomiale di sottoproblemi da risolvere; © Alberto Montresor 7 © Alberto Montresor 8 Catena di moltiplicazione tra matrici ✦ 3 matrici: A 100x1 ! (A ⋅ B ) ((A ⋅ B ) ⋅ C ) (B ⋅ C) (A ⋅ (B ⋅ C)) Catena di moltiplicazione tra matrici B 1x100 C 100x1 ✦ # Moltiplicazioni Memoria 100×1×100 = 10000 100×100×1 = 10000 20000 10000 100 10100 1×100×1 100×1×1 = = 100 100 200 ((( A ⋅ B ) ⋅ C ) ⋅ D ) (( A ⋅ ( B ⋅ C )) ⋅ D ) (( A ⋅ B ) ⋅ ( C ⋅ D )) ( A ⋅ (( B ⋅ C ) ⋅ D )) ( A ⋅ ( B ⋅ ( C ⋅ D ))) 9 ✦ D 30x5 : 87500 moltiplicazioni : 34500 moltiplicazioni : 36000 moltiplicazioni : 16000 moltiplicazioni : 10500 moltiplicazioni 10 © Alberto Montresor ✦ Definizione: Una parentesizzazione Pi,j del prodotto Ai · Ai+1 · · · Aj consiste ✦ Caratterizzare la struttura di una soluzione ottima ✦ ✦ Definire ricorsivamente il valore di una soluzione ottima ✦ Calcolare il valore di una soluzione ottima “bottom-up” (dal basso verso l’alto) nella matrice Ai, se i = j; nel prodotto di due parentesizzazioni (Pi,k · Pk+1,j), altrimenti. (A1·(A2·A3 ))·(A4·(A5·A6 )) ! Costruzione di una soluzione ottima ✦ Nei lucidi successivi: ✦ C 40x30 Parentesizzazione Le fasi principali: ✦ B 10x40 ((( A ⋅ B ) ⋅ C ) ⋅ D ) : 87500 ! (A⋅B) 50×10×40 = 20000 (( A ⋅ B ) ⋅ C ) 50×40×30 = 60000 (( A ⋅ B ) ⋅ C ) ⋅ D 50×30× 5 = 7500 87500 Applicare la programmazione dinamica ✦ A 50x10 1 100 101 © Alberto Montresor ✦ 4 matrici: Esempio: ✦ Vediamo ora una ad una le quattro fasi del processo di sviluppo applicate al problema della parentesizzazione ottima ✦ (A1·(A2·A3 )) · (A4·(A5·A6 )) → k=3 “Ultima moltiplicazione” (A1·(A2·A3 )) A1 (A2·A3 ) A2 © Alberto Montresor 11 © Alberto Montresor A3 (A4·(A5·A6 )) A4 (A5·A6 ) A5 A6 12 Parentesizzazione ottima ✦ Parentesizzazione ottima ✦ ✦ ✦ ✦ Determinare il numero di moltiplicazioni scalari necessari per i prodotti tra le matrici in ogni parentesizzazione Scegliere una delle parentesizzazioni che richiedono il numero minimo di moltiplicazioni L'ultima moltiplicazione può occorrere in n-1 posizioni diverse ✦ Fissato l'indice k dell'ultima moltiplicazione, abbiamo ✦ P(k) parentesizzazioni per A1 · A2 · A3 ··· Ak ✦ P(n-k) parentesizzazioni per Ak+1 · Ak+2 ··· An P (n) = n=3 → 2, n=4 → 5, n=5 → ??? 13 Parentesizzazione ottima ⇢ Pn 1 1 k=1 P (k)P (n k) n > 1 n=1 P (n) = ! ⇢ Pn 1 1 k=1 P (n) = C(n P (k)P (n ! 1) = 1 2 3 4 5 6 7 P(n) 1 1 2 5 14 42 132 8 9 10 429 1430 4862 14 © Alberto Montresor ✦ k) n > 1 n=1 Soluzione: n-1-esimo “numero catalano” ! n Definizioni Equazione di ricorrenza: ! ✦ ✦ Quante sono le parentesizzazioni possibili? © Alberto Montresor ✦ P(n): numero di parentesizzazioni per n matrici A1 · A2 · A3 ···An Vale la pena di spendere un po' di tempo per cercare la parentesizzazione migliore, per risparmiare tempo dopo ✦ ✦ ✦ Domanda ✦ ✦ Definiamo una relazione di ricorrenza Motivazione: ✦ ✦ Parentesizzazione ottima ✓ 1 2n n+1 n ◆ =⌦ ✓ 4 n3/2 n ◆ Denoteremo nel seguito con: ✦ A1 · A2 · A3 ··· An il prodotto di n matrici da ottimizzare ✦ ci-1 il numero di righe della matrice Ai ✦ ci il numero di colonne della matrice Ai ✦ A[i..j] il prodotto Ai · Ai+1 ··· Aj ✦ P[i..j] una parentesizzazione di A[i..j] (non necessariamente ottima) Domanda: Più semplicemente, dimostrare che P(n) = Ω(2n) Conseguenza: la “forza bruta” (tentare tutte le possibili parentesizzazioni) non funziona © Alberto Montresor 15 © Alberto Montresor 16 Struttura di una parentesizzazione ottima ✦ Sia A[i..j] = Ai · Ai+1 ··· Aj una sottosequenza del prodotto di matrici ✦ ✦ ✦ Struttura di una parentesizzazione ottima Si consideri una parentesizzazione ottima P[i..j] di A[i..j] ✦ Quali sono le caratteristiche delle due sotto-parti P[i..k] e P[k+1..j] ? ✦ ( P[i..k] ) · ( P[k+1..j] ) P[i..k] ✦ P[k+1..j] ✦ ? © Alberto Montresor 17 Struttura di una parentesizzazione ottima ✦ Il teorema afferma che esiste una sottostruttura ottima: Ogni soluzione ottima al problema della parentesizzazione contiene al suo interno le soluzioni ottime dei due sottoproblemi Programmazione dinamica: ✦ ✦ Supponiamo esista un parentesizzazione ottima P'[i..k] di A[i..k] con costo inferiore a P[i..k] Allora, P'[i..k] · P[k+1..j] sarebbe una parentesizzazione di A[i..j] con costo inferiore a P[i..j], assurdo. 18 © Alberto Montresor ✦ Definizione: sia M[i,j] il numero minimo di prodotti scalari richiesti per calcolare il prodotto A[i,j] Come calcolare M[i,j]? ✦ ✦ ✦ Ragionamento per assurdo Definire ricorsivamente il valore di una soluzione ottima In altre parole: ✦ Se P[i..j] = P[i..k] · P[k+1..j] è una parentesizzazione ottima del prodotto A[i..j], allora P[i..k] e P[k+1..j] sono parentesizzazioni ottime dei prodotti A[i..k] e A[k+1..j], rispettivamente. Dimostrazione ✦ ? ✦ Teorema (sottostruttura ottima) Esiste una “ultima moltiplicazione”: in altre parole, esiste un indice k tale che P[i..j] = P[i..k] · P[k+1..j] Domanda: ✦ ✦ L'esistenza di sottostrutture ottime è una delle caratteristiche da cercare per decidere se la programmazione dinamica è applicabile Caso base: i=j . Allora, M[i,j] = 0 Passo ricorsivo: i < j. Esiste una parentesizzazione ottima P[i..j] = P[i..k] · P[k+1..j]; sfruttiamo la ricorsione: M[i,j] = M[i,k] + M[k+1,j] + ci-1 · ck · cj Prossima fase: ✦ Prodotti per P[i..k] Definire ricorsivamente il costo di una soluzione ricorsiva © Alberto Montresor 19 © Alberto Montresor Prodotti per P[k+1..j] Prodotto di una coppia di matrici: u n. righe: prima matrice u n. colonne: ultima matrice 20 Definire ricorsivamente il valore di una soluzione ottima ✦ ✦ Esempio Ma qual è il valore di k? Li \ R j 1 Non lo conosciamo.... 1 0 ✦ ... ma possiamo provarli tutti! 2 - 0 ✦ k può assumere valori fra i e j-1 3 - - 0 4 - - - 0 5 - - - - 0 6 - - - - - ✦ La formula finale: ✦ M[i,j] = mini ≤ k < j { M[i,k] + M[k+1, j] + ci-1 · ck · cj } 2 3 4 5 6 0 M[1,2] = min1 ≤ k < 2 { M[1,k] + M[k+1,2] + c0ckc2 } = M[1,1] + M[2,2] + c0c1c2 = c0c1c2 21 © Alberto Montresor Esempio Esempio Li \ R j 1 2 3 4 5 1 0 2 - 0 3 - - 0 4 - - - 0 5 - - - - 0 6 - - - - - 6 0 M[2,4] = min2≤k<4{ M[2,k] + M[k+1,4] + c1ckc4 } = min { M[2,2] + M[3,4] + c1c2c4, M[2,3] + M[4,4] + c1c3c4 } © Alberto Montresor 22 © Alberto Montresor Li \ R j 1 2 3 4 5 1 0 2 - 0 3 - - 0 4 - - - 0 5 - - - - 0 6 - - - - - 6 0 M[2,5] = min2≤k<5{ M[2,k] + M[k+1,5] + c1ckc5 } = min { M[2,2] + M[3,5] + c1c2c5, M[2,3] + M[4,5] + c1c3c5, M[2,4] + M[5,5] + c1c4c5 } 23 © Alberto Montresor 24 Esempio Esempio Li \ R j 1 2 3 4 5 1 0 2 - 0 3 - - 0 4 - - - 0 5 - - - - 0 6 - - - - - 6 0 25 Calcolo “bottom-up” del valore della soluzione ✦ ✦ ✦ ✦ Il meccanismo ricorsivo individua i sottoproblemi da risolvere 5 1 0 2 - 0 3 - - 0 4 - - - 0 5 - - - - 0 6 - - - - - 6 0 if i = j then return 0 else min ⇥ +⇤ for integer k ⇥ i to j 1 do integer q ⇥ recPar(c, i, k) + recPar(c, k + 1, j) + c[i if q < min then min ⇥ q return min Proviamo... male non fa ;-) ✦ 4 26 integer recPar(integer[ ] c, integer i, integer j) Ma la definizione ricorsiva di M[i,j] suggerisce di utilizzare un approccio ricorsivo top-down per risolvere il problema: Lanciamo il problema sulla sequenza completa [1,n] 3 Soluzione ricorsiva top-down “calcolare in modo bottom-up il valore della soluzione ottima” ✦ 2 © Alberto Montresor Passiamo ora al terzo passo della programmazione dinamica: ✦ 1 M[1,6] = min1≤k<6{ M[1,k] + M[k+1,6] + c0ckc6 } = min { M[1,1] + M[2,6] + c0c1c6 , M[1,2] + M[3,6] + c0c2c6 , M[1,3] + M[4,6] + c0c3c6 , M[1,4] + M[5,6] + c0c4c6 , M[1,5] + M[6,6] + c0c5c6 } M[1,5] = min1≤k<5{ M[1,k] + M[k+1,5] + c0ckc5 } = min { M[1,1] + M[2,5] + c0c1c5 , M[1,2] + M[3,5] + c0c2c5 , M[1,3] + M[4,5] + c0c3c5 , M[1,4] + M[5,5] + c0c4c5 } © Alberto Montresor Li \ R j 1] · c[k] · c[j] Input: un array c[0..n] con le dimensioni delle matrici, ✦ c[0] è il numero di righe della prima matrice ✦ c[i] è il numero di colonne della matrice Ai © Alberto Montresor u 27 Domanda: Complessità risultante? © Alberto Montresor 28 Critica all'approccio top-down ✦ ✦ Calcolare la soluzione ottima in modo bottom-up Alcune riflessioni ✦ ✦ La soluzione ricorsiva top-down è Ω(2n) ✦ Non è poi migliore dell'approccio basato su forza bruta! ✦ uno per ogni scelta di i e j (con 1 ≤ i ≤ j ≤ n): ! Qual è il problema? ✦ E' interessante notare che il numero di possibili problemi è molto inferiore a 2n ! Il problema principale è che molti problemi vengono risolti più volte ✦ 1…4 Sottoproblemi con i ≠ j ✓ ◆ n + n = ⇥(n2 ) 2 Ogni sottoproblema Sottoproblemi con i = j ✦ È risolvibile le soluzioni dei sottoproblemi che sono state CAPITOLO 13. utilizzando PROGRAMMAZIONE DINAMICA eventualmente già calcolate e memorizzate nell'array 1…1 1…2 2…4 3…4 1…3 4…4 della programmazione dinamica: k Idea che chiave rappresenta il punto di moltiplicazione fra le due sottoparentesizzazio ✦ Mai calcolare più dipossiamo una volta lachiamare soluzione ad sottoproblema questa informazione, la un procedura stampaPar(S, 1, n) per rappresentazione della parentesizzazione in tempo O(n). Si noti che, a differenza dei problemi precedenti, in questo caso la tabella ag il costo di stampaPar(). In assenza della tabella, infatti, avremmo dovuto cerca il valore k dell’ultima moltiplicazione per ogni posizione visitata da stampaP 30 suo costo ad O(n2 ). ✦ 2…2 3…4 2…3 4…4 1…1 2…2 3…3 4…4 1…1 2…3 1…2 3…3 3…3 4…4 2…2 3…3 2…2 3…3 1…1 2…2 29 © Alberto Montresor Calcolare la soluzione ottima in modo bottom-up ✦ Algoritmo parentesizzazione(integer[ ] c, integer[ ][ ] M , integer[ ][ ] S, integer n) L’algoritmo parentesizzazione() ✦ ✦ integer i, j, h, k, t for i 1 to n do M [i, i] 0 prende in ingresso un array c[0..n] con le dimensioni delle matrici ✦ c[0] è il numero di righe della A1 ✦ c[i] è ✦ il numero di righe della matrice Ai+1 ✦ il numero di colonne della matrice Ai M[i,j] che contiene i costi minimi dei sottoproblemi A[i..j] ✦ S[i,j] che contiene il valore di k che minimizza il costo per il sottoproblema © Alberto Montresor h varia sulle diagonali sopra quella principale i e j assumono i valori delle for h 2 to n do celle nella diagonale h for i 1 to n h + 1 do j i+h 1 M [i, j] +1 for k i to j 1 do t M [i, k] + M [k + 1, j] + c[i 1] · c[k] · c[j] if t < M [i, j] then L 1 2 3 4 M [i, j] t 1 0 S[i, j] k 2 0 utilizza (e ritorna) due matrici n·n ausiliarie: ✦ © Alberto Montresor 31 Calcola tutti i possibili valori e conserva solo il più piccolo © Alberto Montresor 3 4 5 6 0 0 5 0 6 32 0 M[ ] Li \i Rj 1 2 3 4 5 6 i ci Li \ Rj 1 2 3 4 5 6 i ci 1 0 224 176 218 276 350 0 7 1 0 1 1 3 3 3 0 7 2 - 0 64 112 174 250 1 8 0 2 3 3 3 1 8 3 - - 0 24 70 138 2 4 3 0 3 3 3 2 4 4 - - - 0 30 90 3 2 4 0 4 5 3 2 5 - - - - 0 90 4 3 5 0 5 4 3 6 - - - - - 0 5 5 6 0 5 5 6 6 6 6 M[1,4] = min1 ≤ k ≤ 3{ M[1,k] + M[k+1,4] + c0ckc4 } = min { M[1,1] + M[2,4] + c0c1c4, M[1,2] + M[3,4] + c0c2c4, M[1,3] + M[4,4] + c0c3c4 } = min { 0 + 112 + 7 * 8 * 3, 224 + 24 + 7 * 4 * 3, 176 + 0 + 7 * 2 * 3 } = min { 280, 332, 218 } = 218 © Alberto Montresor S[ ] M[1,4] = min1 ≤ k ≤ 3{ M[1,k] + M[k+1,4] + c0ckc4 } = min { M[1,1] + M[2,4] + c0c1c4, M[1,2] + M[3,4] + c0c2c4, M[1,3] + M[4,4] + c0c3c4 } = min { 0 + 112 + 7 * 8 * 3, 224 + 24 + 7 * 4 * 3, 176 + 0 + 7 * 2 * 3 } = min { 280, 332, 218 } = 218 33 Calcolare la soluzione ottima in modo bottom-up ✦ © Alberto Montresor ✦ Costo computazionale: O(n3) Possiamo definire un algoritmo che costruisce la soluzione a partire dall'informazione calcolata da parentesizzazione(). ✦ ✦ Nota ✦ Lo scopo della terza fase era “calcolare in modo bottom-up il valore della soluzione ottima” ✦ ✦ ✦ ✦ Questo valore si trova in M[1,n] Per alcuni problemi ✦ ✦ 34 Costruire una soluzione ottima Considerazioni sull'algoritmo ✦ 2 ✦ La matrice S ci permette di determinare il modo migliore di moltiplicare le matrici. S[i,j]=k contiene infatti il valore k su cui dobbiamo spezzare il prodotto A[i..j] Ci dice cioè che per calcolare A[i..j] dobbiamo prima calcolare A[i..k] e A[k+1..j] e poi moltiplicarle tra loro. Ma questo è un processo facilmente realizzabile tramite un algoritmo ricorsivo E' anche necessario mostrare la soluzione trovata Per questo motivo registriamo informazioni sulla soluzione mentre procediamo in maniera bottom-up © Alberto Montresor 35 © Alberto Montresor 36 Costruire una soluzione ottima Costruire una soluzione ottima stampaPar(integer[ ][ ] S, integer i, integer j) integer[ ][ ] multiply(integer[ ][ ] S, integer i, integer j) if i = j then print “A[”; print i; print “]” else print “(” stampaPar(S, i, S[i, j]) print “·” stampaPar(S, S[i, j] + 1, j) print “)” if i = j then return A[i] else integer[ ][ ] X multiply(S, i, S[i, j]) integer[ ][ ] Y multiply(S, S[i, j] + 1, j) return matrix-multiplication(X, Y ) 6 37 © Alberto Montresor Esempio di esecuzione A1…6 = A1…k×Ak+1…6 = A1…3×A4…6 A1…3 = A1…k×Ak+1…3 =A1×A2…3 A4…6 = A4…k×Ak+1…6 =A4..5×A6 A2…3 = A2…k×Ak+1…3 = A2×A3 A4…5 = A4…k×Ak+1…5 =A4×A5 © Alberto Montresor 38 Numeri di Fibonacci ✦ S[ ] Li \ Rj 1 2 3 4 5 6 1 0 1 1 3 3 3 0 2 3 3 3 0 3 3 3 0 4 5 0 5 2 3 4 5 6 ✦ ✦ Definiti ricorsivamente ✦ F(0) = F(1) = 1 ✦ F(n) = F(n-2)+F(n-1) Un po' di storia ✦ Leonardo di Pisa, detto Fibonacci ✦ Utilizzati per descrivere la crescita di una popolazione di conigli (!) In natura: ✦ 0 ✦ A1…6 = ( ( A1× (A2×A3) )×( (A4×A5 ) ×A6) ) Pigne, conchiglie, parte centrale dei girasoli, etc. In informatica: ✦ Alberi AVL minimi, Heap di Fibonacci, etc. 6 © Alberto Montresor 39 © Alberto Montresor 40 T (n 1) + T (n T (n) = (1) Implementazione ricorsiva Implementazione iterativa per n > 1. Una soluzione divide-et-impera basata sulla definizione ricorsiva tende a risolvere uninteger gran numero di(integer problemin)ripetuti. È infatti possibile dimostrare che il valore di Fn è circa fibonacci integer fibonacci(integer n) n (1.6) e pertanto la soluzione divide-et-impera richiede un numero di chiamate che cresce if n ⇥ 1 then integer[ ] F ⇤ new integer[0 . . . max(n, 1)] anch’esso come1 (1.6)n . return F [0] ⇤ F [1] ⇤ 1 Una soluzione basata su programmazione dinamica memorizza tutti i numeri di Fibonacci else for integer i ⇤ 2 to n do fino ad return Fn , evitando cosı̀(nproblemi ripetuti. (n Calcolando i valori in maniera iterativa, si ottiene fibonacci 1) + fibonacci 2) F [i] ⇤ F [i 1] + F [i 2] un algoritmo il cui costo è O(n), sia in termini di tempo che di spazio. return F [n] ✦ Complessità computazionale ⇢ integer fibonacci (integer n) !T (n) = T (n 1) + T⇤(n 2) + ⇥(1) n > 1 integer[ ] F ⇥(1) ⇥ new integer[0 . . . max(n, 1)] n = 1 n 0 1 2 3 4 5 6 7 T (n 1) + T (n 2) + (1) n > 1 T (n) = F [0] ⇥ F [1] ⇥ 1 ! f [] 1 1 2 3 5 8 13 21 (1) n=1 for integer i ⇥ 2 to n do F [i] ⇥ F [i 1] + F [i 2] ✦ Soluzione return F [n] ✦ T(n) = O(2n) 2) + (1) n > 1 n=1 ✦ Complessità ✦ In tempo: O(n) ✦ In spazio: O(n) ✦ Array di n elementi integer fibonacci(integer n) Una domanda importante è la seguente: è veramente necessario un vettore di n posiziointeger[ ] Fla⇤ new integer[0 . . . max(n, 1)] utilizzata dopo aver calcolato F [i + 2]. In ni? In realtà, posizione F [i] smette di essere F [0] non ⇤ Fserve [1] ⇤un1 vettore di dimensione n, ma bastano tre variabili F , F , F . Queste pratica, 0 1 2 for integer i ⇤ 2 to n do 41 di F©0Alberto Montresor © variabili Alberto Montresor vedono scorrere i numeri di Fibonacci in modo che ad ogni iterazione il valore F [i] ⇤ F [i 1] + F [i 2] viene scartato e sostituito con F1 , F1 viene sostituito con F2 e il nuovo termine delle serie di return viene F [n] calcolato Implementazione iterativa memoria Fibonacci in -Frisparmio . La complessità è uguale, lo spazio questa volta è costante. Zaino 42 2 7 integer fibonacci(integer n) Input Complessità integer F0 , F1 , F2 ✦ Un intero positivo C - la capacità dello zaino ✦ In tempo: O(n) ⌅ F0 ⇥ F1 ⇥⇥F2 ⇥ 1 ✦ In spazio: O(1) 1 se k = 0 ⇧ k = n ✦ n oggetti, tali che l’oggetto i-esimo è caratterizzato da: n n! fork) integer C(n, = i ⇥=2 to n do = ✦ un profitto p e k! · (n k)! C(n 1, k 1) + C(n✦ 3 variabili 1, k) altrimenti i F0 ⇥ Fk1 ✦ F1 ⇥ F 2 un volume vi , entrambi interi positivi F2 ⇥ F 0 + F 1 ✦ Problema n return F2 ⇧ n n k k ✦ trovare un sottoinsieme S di {1, . . . , n} di oggetti tale che il volume totale non (a + b) = C(n, k)a b k=0 superi la capacità massima e il profitto totale sia massimo nIl problema 0 1di Fibonacci 2 3 può 4 sembrare 5 6 un 7caso particolare, non applicabile ad altri proble✦ ✦ mi. In realtà, sotto certe condizioni, una tecnica7 simile può essere applicata anche al problema F0 1 1 2 3 5 8 v(S) dello string matching approssimato. 1 1 che1non2sia necessario 3 5 8produrre 13 in output l’occorrenza con k errori, ma si FSupponiamo 1 p(S) voglia F2 solamente 1 1 ottenere 2 3il minimo 5 8 valore 13 k21(analogamente, si voglia ottenere solamente la distanza di editing). Questo significa che al termine del problema saremmo interessati solo all’ultima riga della tabella, per individuare il valore minimo. Durante l’esecuzione, per calco© lare Alberto Montresor © Alberto Montresor una riga è sufficiente avere a disposizione i soli tre valori illustrati in Fig. 13.3. È43quindi = X i2S = X vi C pi i2S 44 per assurdo che ciò non sia vero; allora esiste un insieme S soluzione ottima di P (i, c) tale 0 0 che 0p(S > p(S(i, e 0v(S c, contraddicendo l’ipotesi di ottimalità di S(i, che p(S ) > )p(S(i, c)) ec)) v(S ) )c,contraddicendo l’ipotesi di ottimalità di S(i, c). c). Zaino Zaino ✦ Caratterizzazione del problema ✦ ✦ ✦ P(i, c) è il sottoproblema dato dai primi i oggetti da inserire in uno zaino con capacità c Il problema originale corrisponde a P(n, C) Teorema - sottostruttura ottima ✦ Sia S(i, c) una soluzione ottima per il problema P(i, c) ✦ Possono darsi due casi: ✦ ✦ Se i ∈ S(i, c), allora S(i, c)-{i} è una soluzione ottima per il sottoproblema P(i-1, c-vi ) Se i ∉ S(i, c), allora S(i, c) è una soluzione ottima per il sottoproblema P(i−1, c) Detto il profitto massimo ottenuto il problema (i,la c),proprietà la proprietà di sottost Detto D[i,D[i, c] il c] profitto massimo ottenuto per ilper problema P (i,Pc), di sottostrut✦ Tabella per programmazione dinamica tura ottima ci suggerisce la seguente definizione ricorsiva: tura ottima ci suggerisce la seguente definizione ricorsiva: ✦ D[i, c] contiene il profitto massimo ottenibile per il problema P(i,c) 8 8 > ! > > 1 1 se cse <c0< 0 > < < ! D[i, D[i, c] =c] =0 >0 se i se = i0= _ 0c _ =c0= 0 > > > : :max{D[i max{D[i1, c],1,D[i c], D[i1, c 1, cvi ] + vip[i]} ] + p[i]} altrimenti altrimenti ! ✦ Alcune considerazioni Possiamo facilmente ottenere procedura basata programmazione dinamica Possiamo facilmente ottenere una una procedura basata sullasulla programmazione dinamica con ✦ O(nC). problema, tuttavia, differisce da quelli precedenti in quanto è de Costo diQuesto un problema, algoritmo di programmazione dinamica bottom-up: O(nC) costocosto O(nC). Questo tuttavia, differisce da quelli precedenti in quanto nonnon è detto che sia necessario risolvere tutti i sottoproblemi. Dipende in realtà dai particolari valori che sia necessario risolvere tuttii problemi i sottoproblemi. in realtà dai particolari valori dei ✦ Non è detto che tutti debbano Dipende essere risolti volumi e dalla capacità. volumi e dalla capacità. L’approccio iterativo, basso verso l’alto” programmazione dinamica risolve L’approccio iterativo, “dal“dal basso verso l’alto” delladella programmazione dinamica risolve obbligatoriamente tutti i problemi. Esiste un approccio alternativo alla programmazione dina bligatoriamente tutti i problemi. Esiste un approccio alternativo alla programmazione dinamica, ricorsivo e “dall’alto verso il basso”, prende il nome di memoization (o annotazion ✦ Dimostrazione ca, ricorsivo e “dall’alto verso il basso”, che che prende il nome di memoization (o annotazione). L’idea è scrivere procedura utilizza la tabella come cache memorizzare le L’idea è scrivere una una procedura che che utilizza la tabella come una una cache per per memorizzare le so✦ per assurdo luzioni dei problemi, controllando ogni volta se un problema è stato risolto; se sì, si riutili dei problemi, controllando ogni volta se un problema è stato risolto; se sì, si riutilizza 45 luzioni 46 © Alberto Montresor © Alberto Montresor il risultato; se lo no,risolve lo risolve (chiamando ricorsivamente la procedura sottoproblemi), il risultato; se no, (chiamando ricorsivamente la procedura sui sui sottoproblemi), ne memorizza il risultato nella tabella e lo restituisce al chiamante. La procedura zaino () riso memorizza risultato nella tabella e lo restituisce al chiamante. La procedura zaino() risolve Memoization Zaino il“annotato” il problema zaino utilizzando memoization; prende in input vettori e v contenen il problema dellodello zaino utilizzando memoization; prende in input due due vettori p e vp contenenti i profitti e i volumi, un indice i e la capacità residua c. Inoltre, prende in input anche un punta ✦ Memoization (annotazione) ✦ Note profitti e i volumi, un indice i e la capacità residua c. Inoltre, prende in input anche un puntatosulla soluzione re alla tabella D. prima La prima invocazione procedura prende la forma zaino (p,n,v,C, n,D), C, D re alla tabella La invocazione delladella procedura la forma zaino (p, v, ✦ Tecnica che fonde l'approccio di memorizzazione della programmazione dinamica èD. un valore speciale per indicare che un certoprende problema non è stato risolto le posizioni matrice D inizializzate ?, valore un valore speciale indica con con tuttetutte le posizioni delladella matrice D inizializzate a ?,a un speciale che che indica che ch il con l'approccio top-down di divide-et-impera sottoproblema non è stato ancora risolto. ✦ Gli elementi della tabella D sono inizializzati con il valore sottoproblema non è stato ancora risolto. ✦ Quando un sottoproblema viene risolto per la prima volta, integer zaino (integer[ p, integer[ v, integer i, integer c, integer[ ][ ] D) viene memorizzato in una tabella integer zaino (integer[ ] p, ]integer[ ] v, ]integer i, integer c, integer[ ][ ] D) ✦ ✦ ✦ ogni volta che si deve risolvere un sotto-problema, si controlla nella tabella se è già stato risolto precedentemente ✦ SI: si usa il risultato della tabella ✦ NO: si calcola il risultato e lo si memorizza In tal modo, ogni sottoproblema viene calcolato una sola volta e memorizzato come nella versione bottom-up © Alberto Montresor if c0 < 0 then if c < then return return 1 1 if 0 i= 0 then if i = or0c or = c0 = then return 0 return 0 if D[i, ? then if D[i, c] =c]?=then D[i, c] zaino(p, v, i 1, c, D), zaino(p, v, i 1, c v[i], D) + p[i]) D[i, c] max(max( zaino(p, v, i 1, c, D), zaino(p, v, i 1, c v[i], D) + p[i]) return return D[i,D[i, c] c] Il codice risultante è molto elegante e simile definizione ricorsiva; l’unica differenz Il codice risultante è molto elegante e simile alla alla definizione ricorsiva; l’unica differenza è la presenza del controllo sul valore ?, evitare per evitare di risolvere sottoproblemi già risolti. Vi so la presenza del controllo sul valore ?, per di risolvere sottoproblemi già risolti. Vi sono 47 48 tuttavia alcune considerazioni da fare per valutare l’efficacia della tecnica di memoization. © Alberto Montresor tuttavia alcune considerazioni da fare per valutare l’efficacia della tecnica di memoization. Discussione su memoization ✦ ✦ Caso pessimo ✦ Nel caso pessimo, è comunque O(nC) ✦ Una stringa di molecole chiamate basi ✦ Quando si verifica? ✦ Solo quattro basi: Adenina, Citosina, Guanina, Timina Inizializzazione ✦ ✦ ✦ E’ necessario inizializzare D - costo O(nC) Se il costo dell’inizializzazione è asintoticamente inferiore al costo di ricombinare i risultati, si ottiene un guadagno ✦ 49 Una è sottostringa dell'altra? ✦ Distanza di editing: costo necessario per trasformare una nell'altra ✦ La più lunga sottosequenza (anche non contigua) comune ad entrambe © Alberto Montresor ✦ Una sequenza T è una sotto-sequenza di P se T è ottenuta da P rimuovendo uno o più elementi Definizione: ✦ Alternativamente: T è definito come il sottoinsieme degli indici l'insieme di elementi di P che compaiono anche in T Gli elementi rimanenti devono comparire nello stesso ordine, anche se non devono essere necessariamente contigui in P ✦ Scriviamo Z ∈ CS(P, T) ✦ “Common Subsequence”, o CS Definizione: Esempio ✦ ✦ P = “AAAATTGA” , T = “AAATA” Nota ✦ Date due sequenze P e T, una sequenza Z è una sottosequenza comune di P e T se Z è sottosequenza sia di P che di T ✦ ✦ ✦ 50 Caratterizzazione del problema Definizione ✦ ✦ La complessità O(nC) è polinomiale nella dimensione dell’input? Caratterizzazione del problema ✦ Due esempi di DNA: AAAATTGA, TAACGATAG Date due sequenze, è lecito chiedersi quanto siano “simili” Altrimenti: è possibile utilizzare una tabella hash © Alberto Montresor ✦ Esempi ✦ Complessità pseudo-polinomiale ✦ ✦ DNA ✦ ✦ ✦ Bioinformatica Date due sequenze P e T, una sequenza è una sottosequenza comune massimale di P e T, se Z ∈ CS(P, T) e non esiste una sequenza W ∈ CS(P, T) tale che |W| > |Z| ✦ Scriviamo Z ∈ LCS(P, T) ✦ “Longest Common Subsequence”, o LCS La sequenza nulla è una sotto-sequenza di ogni sequenza © Alberto Montresor 51 © Alberto Montresor 52 Caratterizzazione del problema ✦ ✦ Risoluzione tramite enumerazione integer LCS-esaustivo(I TEM[ ] P , I TEM[ ] T ) integer max ⇥ 0 I TEM[ ] ⇥ nil foreach sottosequenza Z di P do if Z è una sottosequenza di T then if |Z| > max then max ⇥ |Z| ⇥Z Problema LCS ✦ Input: due sequenze di simboli, P e T ✦ Output: Trovare la più lunga sottosequenza Z comune a P e T Esempio ✦ P = “AAAATTGA” ✦ T = “TAACGATA” ✦ LCS(P,T) = ???? ! ✦ ! Prima di provare con la programmazione dinamica, proviamo di “forza bruta”... u © Alberto Montresor 53 Caratterizzazione della soluzione ottima ✦ ✦ Domanda: Quante sono le sotto-sequenze di P? 54 © Alberto Montresor Caratterizzazione della soluzione ottima Data una sequenza P=(p1, …, pn): ✦ return ✦ denoteremo con P(i) l’i-esimo prefisso di P, cioè la sotto-sequenza ( p1, … , pi ) Teorema (Sottostruttura ottima) ✦ Esempio: Date le due sequenze P=(p1,…, pm) e T=(t1, …, tn), sia Z=(z1,…,zk ) una LCS di P e T zk = pm = tn e Z(k-1) ∈ LCS( P(m-1), T(n-1) ) 1. pm= tn → P(0) denota la sotto-sequenza nulla 2. pm ≠ tn e zk ≠ pm → Z ∈ LCS( P(m-1), T ) ✦ P(3) = ABD 3. pm ≠ tn e zk ≠ tn → Z ∈ LCS( P, T(n-1) ) ✦ P(6) = ABDCCA ✦ P = ABDCCAABD ✦ © Alberto Montresor ✦ 55 Dimostrazione © Alberto Montresor 56 se C = O(nk ) per qualche k, la soluzione proposta è praticabile. Algoritmi di questo tip Dimostrazione vengono detti pseudopolinomiali [cfr. § ??]. Dimostrazione ✦ Punto 1 ✦ ✦ Allora esiste W ∈ LCS( P(m-1), T(n-1) ) tale che |W| > |Z(k-1)| ✦ Quindi Wpm ∈ CS(P,T) e |Wpm| > |Z|, assurdo a a P[1,…,m-1] T[1,…,n-1] Z[1,…,k-1] a © Alberto Montresor Cosa ci dice il teorema? La lunghezza della LCS può essere utilizzata come una misura della similitudine fra du sequenze. Il problema può essere risolto in modo molto simile al problema della distanza di editin 57 58 © Alberto Montresor Sia D[i, j] la lunghezza della LCS di P (i) e T (j). Ovviamente, se i = 0 oppure j = tale valore è parisua programmazione 0 (non esiste infatti alcun carattere in comune con la sequenza vuota LCS basato dinamica Altrimenti, possono darsi due casi: Se pm = tn, dobbiamo risolvere un sottoproblema ✦ LCS( P(m-1), T(n-1) ) ✦ La definizione ricorsiva è la seguente: ✦ ✦ Se pm ≠ tn, dobbiamo risolvere due sottoproblemi LCS( P(m-1), T ) ✦ A questo punto, dobbiamo scegliere la LCS più lunga fra le 2 ✦ LCS( P, T(n-1) ) ✦ © Alberto Montresor Formulazione ricorsiva Queste considerazioni possono essere riassunte dalle seguenti relazioni di ricorrenza: La definizione ricorsiva è la seguente: ✦ Definiamo una tabella per memorizzare la lunghezza della LCS associati ai (1) se psottoproblemi: i = tj , la lunghezza della LCS di P (i) e T (j) è pari alla lunghezza della LCS P (i ✦ 1) e T (j 1) più 1. Questo perché l’ultimo carattere fa sicuramente parte della LC D[0 ... m, 0 ... n] → tabella di (m+1) · (n+1) elementi, dove |P| = m, |T| = n di P (i) e P (j). ✦ D[i,j] → lunghezza della LCS di P(i) e T(j) (2) se pi ⇥= tj , vi sono due sottocasi: o l’ultimo carattere di P (i) non fa parte della LC oppure l’ultimo carattere di T (j) non ne fa parte. Nel primo caso, possiamo ricondurci ✦ Goal finale: calcolare la LCS di P (i 1) e T (j); nel secondo caso, possiamo ricondurci a calcolare ✦ Calcolare D[m,n] → lunghezza della LCS di P e T LCS di P (i) e T (j 1). LCS(P,T) = LCS( P(m-1), T(n-1) ) pm ✦ Per assurdo ipotizziamo che Z ∉ LCS(P(m-1), T) allora esiste W ∈ LCS(P(m-1), T) tale che: | W | > | Z | S OTTOSEQUENZA COMUNE MASSIMALE (L ONGEST COMMON SUBSEQUENCE ). Data un ✦ Allora è anche vero che W ∈ LCS(P, T), contraddicendo l'ipotesi sequenza X, è possibile ottenere una sottosequenza da X rimuovendone uno o più caratte (non usiamo il termine sottostringa perché normalmente associato all’idea di una sottos quenza di caratteri contigui). Date due sequenzeP[1,…,m-1] P e T , X è una sottosequenza comune s P[1,…,m] a è sottosequenza sia di P che di T . Una sequenzaT[1,…,n] X è una sottosequenza comune massima b T[1,…,n] b (in inglese, longest common subsequence o LCS) di P e T se non esistono sottosequenze co Z[1,…,k] muni a P e Z[1,…,k] T più lunghe di X.? Il problema consiste nel calcolare la ?lunghezza delle LCS e eventualmente produrne una. Supponiamo per assurdo che Z(k-1) ∉ LCS( P(m-1), T(n-1) ) Z[1,…,k] ✦ ✦ Consideriamo un’ulteriore variante dello string matching: Quindi zk = pm = tn P[1,…,m] T[1,…,n] ✦ ✦ Si consideri W=Zpm. Allora W ∈ CS(P,T) e | W | > | Z |, assurdo ✦ Punto 2 (Punto 3 simmetrico) 1.7 Reality Se zk ≠ pcheck m, allora Z ∈ CS(P(m-1), T) Supponiamo per assurdo che zk ≠ pm = tn ✦ ✦ ✦ LCS(P,T) = longest( LCS( P(m-1), T ) , LCS( P, T(n-1) ) ) ⌅ ⌅ ⇤0 D[i, j] = D[i 1, j ⌅ ⌅ ⇥max{D[i 59 1] + 1 1, j], D[i, j se i = 0 ⌅ j = 0 se i > 0 ⇤ j > 0 ⇤ pi = tj 1]} se i > 0 ⇤ j > 0 ⇤ pi ⇥= tj La prima invocazione della procedura prende la forma lcs(D, P, T, m, n), con tutte le po 60 © Alberto Montresor Il problema può essere risolto in modo molto simile al problema della distanza di editing. numero di bit) è d = ⇧log C⌃, il che significa che il costo è esponenziale in d. Tuttavia, Sia D[i, j] la lunghezza della LCS di P (i) e T (j). Ovviamente, j 0 1 se 2i = 30 oppure 4 5 j =6 0, k programmazione dinamica C = LCS O(nbasato ) per su qualche k, la soluzione proposta è praticabile. Algoritmi di questo tipo tale valore è pari a 0 (non esiste infatti alcun carattere in comune con la sequenza vuota). gono detti pseudopolinomiali [cfr. § ??]. i A T B C B D Altrimenti, possono darsi due casi: • TACCBT ✦ Definiamo una tabella per memorizzare informazioni necessarie ad ottenere la 0 0 0 0 0 0 0 0 stringa finale • ATBCBD (1) se pi = tj , la lunghezza della LCS di P (i) e T (j) è pari alla lunghezza della LCS di ✦ B[0 ... m, 0 ... n] → tabella di (m+1) · (n+1) elementi, dove |P| = m, |T| = n Reality check 1 T 0 ↓0 ➘ 1 →1 →1 →1 →1 deriva i-1,j-1 P (i ➘ 1) e T (jda1) più 1. Questo perché l’ultimo carattere fa sicuramente parte della LCS ✦ B[i,j] → “puntatore” alla entry della tabella stessa che identifica il sottoproblema 2 A 0 ➘1 ↓1 ↓1 ↓1 ↓1 ↓1 di P↓(i)deriva e P (j).da i-1,j Consideriamo un’ulteriore variante dellodel string matching: ottimo scelto durante il calcolo valore D[i,j] deriva da i,j-1 (2) se p→ P (i) non fa parte della LCS, i ⇥= tj , vi sono due sottocasi: o l’ultimo carattere di 3 C 0 ↓ 1 ↓ 1 ↓ 1 ➘ 2 →2 →2 TOSEQUENZA COMUNE MASSIMALE (L ONGEST COMMON SUBSEQUENCE ). Data una oppure l’ultimo carattere di T (j) non ne fa parte.DINAMICA Nel primo caso, possiamo ricondurci a CAPITOLO 13. PROGRAMMAZIONE ✦ Valori possibili: 0 ↓ 1 ricondurci ↓ 1 ↓ 1 ➘a2 calcolare ↓ 2 →2 la 4 caso, C possiamo uenza X, è possibile ottenere una sottosequenza da X rimuovendone uno o più caratteri calcolare la LCS di P (i 1) e T (j); nel secondo n usiamo il termine sottostringa perché normalmente associato all’idea di una sottose- LCS di P (i) e T (j 1). ↓ 1 ↓ 1 ➘ 2 ↓ 2 ➘ 3 →3 5 riassunte B 0 dalle ➘ deriva da i-1,j-1 Queste considerazioni possono essere seguenti relazioni di ric nza di caratteri contigui). Date due sequenze P e T , X è una sottosequenza comune se ↓ deriva da i-1,j 0 ↓ 1 ➘ 2 ↓di 2 ricorrenza: ↓2 ↓3 ↓3 ttosequenza sia di da P che 8 essere riassunte 6dalleTseguenti relazioni → deriva i,j-1di T . Una sequenza X è una sottosequenza comune massimale Queste considerazioni possono > nglese, longest common subsequence o LCS) di P e T se non esistono sottosequenze cose i = 0 _ j = 0 > <0 ⌅0 i a P e T più lunghe di X. Il problema consiste nel calcolare la lunghezza delle LCS ed ⌅ D[i, ⇤ j] = D[i 1, j 1] + 1 se i = 0 ⌅ j = se0i > 0 ^ j > 0 ^ pi = > ntualmente produrne una. D[i, j] = D[i 1,> 1] + 1 se i > 0 ⇤ j > 0 ⇤ pi = tj :jmax{D[i 1, j], D[i, j 1]} se i > 0 ^ j > 0 ^ pi 6= ⌅ ⌅ ⇥ La lunghezza della LCS può essere utilizzata come una misura della similitudine fra due max{D[i 1, j], D[i, j 1]} se i > 0 ⇤ j > 0 ⇤ pi ⇥= tj uenze. 61 62 c © Alberto Montresor © Alberto Montresor La prima invocazione della procedura prende la forma lcs(D, P, T, m, n), Il problema può essere risolto in modo molto simile al problema della distanza di editing. La prima invocazione della procedura prende la forma lcs(D, P, T, m, n), con tutte le posizioni della matrice D inizializzate a nil, un valore speciale che indica che il D[i, j] la lunghezza della LCS di P (i) e T (j). Ovviamente, 0, della matrice D inizializzate a nil,ottima un valore speciale che indica che il sottoproblema j 0 1 se 2i = 30 oppure 4 5j =6sizioni Calcolo del valore della soluzione non è stato ancora risolto. Una versione basata su memoization è la seguente: valore è pari a 0 (non esiste infatti alcun carattere in comune con la sequenza vuota). i B E B E D E 16 imenti, possono darsi due casi: • ABDDBE 0 0 0 0 0 0 0 integer lcs(integer[ ][ ] D, I TEM[ ] P, I TEM[ ] T, integer i, integer j) 0 • BEBEDE se pi = tj , la lunghezza della LCS di P (i) e T (j) è pari alla lunghezza della LCS di if i = 0 or j = 0 then 1 A 0 ↓0 ↓0 ↓0 ↓0 ↓0 ↓0 deriva i-1,j-1 P (i ➘ 1) e T (jda1) più 1. Questo perché l’ultimo carattere fa sicuramente parte della LCS return 0 2 B 0 ➘1 →1 ➘ 1 →1 →1 →1 di P↓(i)deriva e P (j).da i-1,j if D[i, j] = ? then deriva da i,j-1 se p→ P (i) non fa parte della LCS, i ⇥= tj , vi sono due sottocasi: o l’ultimo carattere di 3 D 0 ↓ 1 ↓ 1 ↓ 1 ↓ 1 ➘2 →2 if P [i] = T [j] then oppure l’ultimo carattere di T (j) non ne fa parte. Nel primo caso, possiamo ricondurci a D[i, j] lcs(D, P, T, i 1, j 1) + 1 0 ↓ 1 ricondurci ↓ 1 ↓ 1 ↓ a1 calcolare ➘ 2 ↓ 2 la 4 caso, D possiamo calcolare la LCS di P (i 1) e T (j); nel secondo else LCS di P (i) e T (j 1). 5 B 0 ➘ 1 ↓ 1 ➘2 →2 ↓ 2 ↓ 2 D[i, j] max(lcs(D, P, T, i 1, j), lcs(D, P, T, i, j 1)) 0 ↓relazioni 1 ➘2 ↓di2 ricorrenza: ➘ 3 →3 ➘ 3 6 Eseguenti Queste considerazioni possono essere riassunte dalle ⌅ ⌅ ⇤0 D[i, j] = D[i 1, j ⌅ ⌅ ⇥max{D[i © Alberto Montresor 1] + 1 1, j], D[i, j se i = 0 ⌅ j = 0 se i > 0 ⇤ j > 0 ⇤ pi = tj 1]} se i > 0 ⇤ j > 0 ⇤ pi ⇥= tj return D[i, j] In molti testi di algoritmi si menziona il fatto che questo è un tipico pro informatica, applicato a sequenze di basi del DNA tratte dall’alfabeto {A, T, G, timina, guanina e citosina. Ma esiste un’applicazione informatica molto più co all’uso quotidiano: il comando Unix diff, che esamina due file di testo, 64 en © Alberto Montresor Il problema può essere risolto in modo molto simile al problema della distanza di editing. Il problema può essere risolto in modo molto simile al problema della distanza di editing. D[i, j] la lunghezza della LCS di P (i) e T (j). Ovviamente, 0, D[i, j] la lunghezza della LCS di P (i) e T (j). Ovviamente, j j 0 1 se 2i = 30 oppure 4 5j =6Sia 0 1 se 2i = 30 oppure 4 5 j =6 0, valore è pari a 0 (non esiste infatti alcun carattere in comune con la sequenza vuota). tale valore è pari a 0 (non esiste infatti alcun carattere in comune con la sequenza vuota). i i B E B E D E A T B C B D imenti, possono darsi due casi: Altrimenti, possono darsi due casi: • ABDDBE • TACCBT 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 • BEBEDE • ATBCBD se pi = tj , la lunghezza della LCS di P (i) e T (j) è pari alla lunghezza della LCS(1) di se pi = tj , la lunghezza della LCS di P (i) e T (j) è pari alla lunghezza della LCS di 1 A 0 ↓0 ↓0 ↓0 ↓0 ↓0 ↓0 1 T 0 ↓0 ➘ 1 →1 →1 →1 →1 deriva i-1,j-1 deriva i-1,j-1 P (i ➘ 1) e T (jda1) più 1. Questo perché l’ultimo carattere fa sicuramente parte della LCS P (i ➘ 1) e T (jda1) più 1. Questo perché l’ultimo carattere fa sicuramente parte della LCS 2 B 0 ➘1 →1 ➘ 1 →1 →1 →1 di P↓(i)deriva 2 A 0 ➘1 ↓1 ↓1 ↓1 ↓1 ↓1 di P↓(i)deriva e P (j).da i-1,j e P (j).da i-1,j deriva da i,j-1 deriva da i,j-1 se p→ (2) se p→ P (i) non fa parte della LCS, i ⇥= tj , vi sono due sottocasi: o l’ultimo carattere di i ⇥= tj , vi sono due sottocasi: o l’ultimo carattere di 1 non ↓ 1 fa ↓ 1parte ↓ 1 della ➘2 LCS, →2 3 D 0 P↓(i) 3 C 0 ↓ 1 ↓ 1 ↓ 1 ➘ 2 →2 →2 oppure l’ultimo carattere di T (j) non ne fa parte. Nel primo caso, possiamo ricondurci a oppure l’ultimo carattere di T (j) non ne fa parte. Nel primo caso, possiamo ricondurci a 0 ↓ 1 ricondurci ↓ 1 ↓ 1 ↓ a1 calcolare ➘ 2 ↓ 2 la calcolare la LCS di P (i 1) e T (j); nel secondo 0 ↓ 1 ricondurci ↓ 1 ↓ 1 ➘a2 calcolare ↓ 2 →2 la 4 caso, D possiamo 4 caso, C possiamo calcolare la LCS di P (i 1) e T (j); nel secondo LCS di P (i) e T (j 1). 5 B 0 ➘ 1 ↓ 1 ➘2 →2 ↓ 2 ↓ 2 LCS di P (i) e T (j 1). 5 B 0 ↓ 1 ↓ 1 ➘ 2 ↓ 2 ➘ 3 →3 0 ↓relazioni 1 ➘2 ↓di2 ricorrenza: ➘ 3 →3 ➘ 3 6 Eseguenti Queste considerazioni possono essere riassunte dalle ⌅ ⌅ ⇤0 D[i, j] = D[i 1, j ⌅ ⌅ ⇥max{D[i 1] + 1 1, j], D[i, j 0 ↓relazioni 1 ➘ 2 ↓di 2 ricorrenza: ↓2 ↓3 ↓3 Queste considerazioni possono essere riassunte 6dalleTseguenti ⌅ ⌅ ⇤0 D[i, j] = D[i 1, j ⌅ ⌅ ⇥max{D[i se i = 0 ⌅ j = 0 se i > 0 ⇤ j > 0 ⇤ pi = tj 1]} se i > 0 ⇤ j > 0 ⇤ pi ⇥= tj © Alberto Montresor se i = 0 ⌅ j = 0 1] + 1 1, j], D[i, j se i > 0 ⇤ j > 0 ⇤ pi = tj 1]} se i > 0 ⇤ j > 0 ⇤ pi ⇥= tj 66 © Alberto Montresor e Strutture La prima invocazione della procedura prende la forma lcs(D, P, T, m, n), con tutte le po- La 8prima invocazione della procedura prende la forma lcs(D, P, T, m,Algoritmi n), con tutte le pooni della matrice D inizializzate a nil, un valore speciale che indica che il sottoproblema sizioni della matrice D inizializzate a nil, un valore speciale che indica che il sottoproblema Alcune ottimizzazioni Stampa della soluzione ottima ✦ La matrice B può essere eliminata: 16 ✦ ✦ ✦ integer printLCS(I TEM[ ] T , I TEM[ ] 16 P , integer[ ][ ] D, integer i, integer j) Il valore di D[i,j] dipende solo dai valori D[i-1,j-1], D[i,j-1] e D[i-1,j]. if i ⇥= 0 and j ⇥= 0 then if T [i] = P [j] then printLCS(T, P, D, i 1, j 1) print P [i] else if D[i 1, j] > D[i, j 1] then printLCS(T, P, D, i 1, j) else printLCS(T, P, D, i, j 1) In tempo costante si può quindi determinare quale di questi tre è stato usato, e perciò quale sia il tipo di freccia Se ci serve solo calcolare la lunghezza della LCS, possiamo ridurre la tabella D[i,j] a due sole righe di lunghezza min{ n , m } ✦ ✦ Ad ogni istante (cioè per ogni coppia i,j), ci servono i valori D[i-1,j-1], D[i,j-1] e D[i-1,j] Costo computazionale ! A ogni passo, almeno uno fra i e j viene decrementato: θ(m+n) Esercizio © Alberto Montresor 67 © Alberto Montresor 68 LCS e diff String matching approssimato CAPITOLO 13. PROGRAMMAZIONE DINAMICA ✦ diff! ✦ ✦ ✦ 243 ✦ Esercizio 13.8 (Allineamento semiglobale). Il problema dell’allineamento semiglobale è siEsamina di testo, e neglobale evidenzia le differenze a livello riga. che non mile a due quellofile dell’allineamento dell’Esercizio 13.7, con l’unica di differenza si penalizzano gli eventuali spazi inseriti prima dell’inizio e/o dopo la fine di una stringa. Lavorare a livello di riga significa che i confronti fra simboli sono in realtà Si indichi come modificare le relazioni di programmazione dinamica in modo da risolvere il confronti fra righe, e che n ed m sono il numero di righe dei due file problema. ✦ Esercizio 13.9 (Allineamento locale). Il problema dell’allineamento locale è anch’esso una Ottimizzazioni variante dell’allineamento globale dell’Esercizio 13.7, dove però si vuole trovare l’allinea✦ diff mento di punteggiosoprattutto massimo tra sottostringhe P e T . Si mostri come modificare relazioni è utilizzato per codice disorgente; è possibile applicarele euristiche di programmazione dinamica in modo da risolvere il problema. sulle righe iniziali e finali ✦ una stringa P = p1 ··· pm (pattern) ✦ una stringa T = t1 ··· tn Questo è il testo nuovo alcune linee non dovrebbero cambiare mai altre invece vengono cancellate altre vengono aggiunte come questa ✦ Un’occorrenza k-approssimata di P in T , con 0 ≤ k ≤ m, è una copia della stringa P nella stringa T in cui sono ammessi k “errori” (o differenze) tra caratteri di P e caratteri di T , del seguente tipo: (1) i corrispondenti caratteri in P e in T sono diversi (sostituzione) (2) un carattere in P non è incluso in T (inserimento) - Questo è il testo originale + Questo è il testo nuovo alcune linee non dovrebbero cambiare mai altre invece vengono - rimosse + cancellate altre vengono aggiunte + come questa Figura 13.4: Il file original.txt (a sinistra); il file new.txt (al centro); l’output di diff original.txt new.txt (a destra). (testo), con m ≤ n Definizione Esercizio 13.10 (diff). Scrivere una procedura printdiff() che, data la tabella D calcolata © Alberto Montresor (3) un carattere in T non è incluso in P (cancellazione) ✦ Problema: ✦ 69 Esercizio 13.11. Harry Potter Al celebre maghetto Harry Potter è stata regalata una scopa Esempiovolante modello Nimbus3000 e tutti i suoi compagni del Grifondoro gli chiedono di poterla ✦ ✦ per dalla distinguire - utilizzo di ilhash tabledifferenze come illustrato in Fig. 13.4. proceduralelcsrighe (), produca in output file delle Questo è il testo originale alcune linee non dovrebbero cambiare mai altre invece vengono rimosse altre vengono aggiunte ✦ Input Trovare un’occorrenza k-approssimata di P in T per cui k sia minimo. 70 © Alberto Montresor Sottostruttura ottima provare. Il buon Harry ha promesso che nei giorni a venire soddisferà le richieste di tutti, ma Input ogni ragazzo è impaziente e vuole provare la scopa il giorno stesso. Ognuno propone ad Harry un intervallo di tempo della giornata durante il quale, essendo libero da lezioni di magia, può ✦ questoèunoscempio! fare un giro sulla scopa, e per convincerlo gli offre una certa quantità di caramelle Tuttigusti+1. Allora Harry decide di soddisfare, tra tutte le richieste dei ragazzi, quelle che gli procureranno ✦ unesempio! la massima quantità di caramelle (che poi spartirà coi suoi amici Ron e Hermione). Aiutalo a trovare la migliore soluzione possibile. ✦ Definizione ✦ ✦ Domanda Tabella D[0...m, 0...n], i cui elementi D[i,j] contengono il minimo valore k per cui esiste una occorrenza k-approssimata di P(i) in T(j) D[i,j] può essere uguale a Qual è il minimo valore k per cui si trova una occorrenza k-approssimata? 13.9 Soluzioni ✦ ✦ D[i-1,j-1], se pi = tj avanza su entrambi i caratteri (uguali) A partire da Esercizio dove? 13.2 Soluzione ✦ ✦ D[i-1,j-1]+1, se pi ≠ tj avanza su entrambi i caratteri (sostituzione) ✦ Si definisce una matrice D[i, j], con 0 ⇥ i ⇥ m e 0 ⇥ j ⇥ n, come segue: D[i, j] è il numero dei caratteri della sottostringa comune al prefisso i-esimo di ConP quali errori? e al prefisso j-esimo di T . Chiaramente, D[0, j] = 0, per 0 ⇥ j ⇥ n, poiché il prefisso 0-esimo di T è vuoto e non ci sono caratteri in comune; anche D[i, 0] = 0, per 0 ⇥ i ⇥ m, perché il prefisso 0-esimo di P è vuoto. Valgono le seguenti relazioni di programmazione dinamica: D[i, j] = 0 D[i 1, j ✦ D[i-1,j]+1 228 ✦ D[i,j-1]+1 avanza sul pattern Algoritmi (inserimento) e Strutture di Dati avanza sul testo (cancellazione) t j-1 pi-1 se pi ⇤= tj © Alberto Montresor D[i-1, j-1] 71 © Alberto Montresor D[i-1, j] +0/+1 1] + 1 se pi = tj pi tj D[i, j-1] Figura 13.1: Calcolo di D[i, j] = min{D[i 1, j +1 +1 D[i, j] 1] + , D[i 1, j] + 1, D[i, j 1] + 1}, dove = 0, 72 (2) D[i 1, j] + 1, perché “avanzando” di un carattere solo nel pattern il numero di errori aumenta di uno inottima quanto pi non compare in T (errore di tipo (2)); Sottostruttura (3) D[i, j 1]+1, perché “avanzando” di un carattere t j-1 solotnel testo t j il numero di errori aumenta t j j-1 di uno in quanto tj non compare in P (errore di tipo (3)). ✦ Ricostruzione della soluzione finale Definizione ✦ La ricorsione termina quando una delle due stringhe diventa vuota. Se il pattern è vuoto, ✦ Tabella D[0...m, 0...n], i cui elementi D[i,j] contengono il minimo valore k per pi-1 D[i-1, D[i-1, j] posizione di T ; se il pi-1la D[i-1, j-1] j-1] D[i-1, j] in ogni il numero di errori è 0, perché stringa vuota P (0) occorre 230 cui esiste una occorrenza k-approssimata di+1 P(i) in T(j) +0/+1 +1 +0/+1 testo è vuoto, invece, il numero di errori è pari alla lunghezza della stringa pattern, perché questi caratteri devono essere tutti cancellati. Riassumendo, si ottengono pertanto la seguenti ✦ Ricordate che cerchiamo il minimo: pi D[i, D[i, j] j-1] relazioni di programmazionepdinamica, perD[i, 0 ⇥ ij-1] ⇥ m D[i, e 0 ⇥j] j ⇥ n: +1 +1 Si noti che: ✦ ✦ D[m,j] = k se e solo se c’è un’occorrenza k-approssimata di P in T che Algoritmi termina in tj e Strutture la soluzione del problema è data dal valore di D[m,j] più piccolo, per 0 ≤ j ≤ n i ⌅ se i = 0 ⌅ ⇤0 D[i, j] = i se j = 0 ⌅ ⌅ Figura 13.1: Calcolo di D[i, j] = min{D[i 1, j 1] + , D[i 1, j] + 1, D[i, j 1] + dove 0, ⇥ Figura 13.1: Calcolo di D[i,1, j] j= min{D[i 1] + + 1,, D[i D[i,altrimenti j1}, 1] + 1},=dove = 0, min{D[i 1] + , D[i1, j1, j] D[i, j 1, j]1]++1,1} se se pi p =i t= , oppure = 1, se p = ⇥ t . j tj , oppure = 1, ise pij ⇥= tj . dove = 0, se pi = tj , oppure = 1, se pi ⌅= tj . Non abbiamo fornito una dimostraziodove = 0, se pi = tj , oppure = 1, se pi ⌅= tj . Non abbiamo fornito una dimostrazione formale, ma è chiaro che tale definizione illustra la proprietà di sottostruttura ottima del ne formale, ma è chiaro che tale definizione illustra la proprietà di sottostruttura ottima del problema. problema. Si noti che D[m, j] = k se e solo se c’è un’occorrenza k-approssimata di P in T che Si noti che D[m, j] = k se e solo se c’è un’occorrenza k-approssimata di P in T che termina in tj e che la soluzione del problema è data dal valore di D[m, j] più piccolo, per termina e che lastringMatching soluzione del() problema è data dal di D[m, j] più di piccolo, per 0⇥ j ⇥ n.inLatjfunzione calcola la matrice D evalore restituisce la posizione T 0 ⇥ j ⇥ n. La funzione stringMatching () calcola la matrice D e restituisce la posizione di T73 in ©cui termina Alberto Montresor un’occorrenza approssimata di P col numero minimo di errori. La complessità in cui termina approssimata di P col numero minimo di di errori. della funzione è un’occorrenza (mn), poiché sono valutati esplicitamente tutti gli elementi D. La complessità Figura della funzioneString è (mn), poichéapprossimato sono valutati esplicitamente tutti gli elementi di D. Algoritmo matching integer stringMatching(I TEM[ ] P, I TEM[ ] T, integer m, integer n) integer][stringMatching (I TEM[.].P, I TEM ] T, integer m, integer n) integer[ ] D ⇤ new integer[0 . m][0 . . .[n] forinteger[ integer ][ i⇤ 1 to m do D[i, 0] ⇤ i ] D ⇤ new integer[0 . . . m][0 . . . n] forfor integer j ⇤i 0⇤to1ntodomD[0, j] ⇤0]0 ⇤ i integer do D[i, forfor i ⇤integer 1 to mjdo⇤ 0 to n do D[0, j] ⇤ 0 forij⇤ ⇤11to tom n do for do integer t ⇤ D[i 1, j 1] + iif(P [i] = T [j], 0, 1) for j ⇤ 1 to n do t ⇤integer min(t, D[i 1, j] + 1) t ⇤ D[i 1, j 1] + iif(P [i] = T [j], 0, 1) t ⇤ min(t, D[i, j 1] + 1) t ⇤ min(t, D[i 1, j] + 1) D[i, j] ⇤ t t ⇤ min(t, D[i, j 1] + 1) integer minD[i, ⇤ D[m, j] ⇤ 0] t integer pos ⇤ 0 min ⇤ D[m, 0] forinteger j ⇤ 1 to n do integer pos ⇤ 0 then if D[m, j] < min for jmin ⇤ 1⇤toD[m, n doj] ifpos D[m, ⇤ jj] < min then return posmin ⇤ D[m, j] pos ⇤ j return pos © Alberto Montresor A B A B A T P 0 0 0 0 0 0 B 1 1 0 1 0 1 A 2 1 1 0 1 0 B 3 2 1 1 0 1 © Alberto Montresor 74 13.2: Risoluzione dello string matching approssimato con la programmazione dinam String matching approssimato ✦ Variante dello string matching approssimato: intervallo i, sia pDistanza di di i, dove j < i è il massimo indice tale che [aj , editing (Distanza Levenshtein) i = j ildipredecessore interseca [ai , bi [ (seDate nondue esiste j, allora pi conoscere = 0). il numero minimo di operazioni stringhe, vogliamo ✦ (sostituzione, inserimento, cancellazione) necessarie per trasformare una nell’altra Sia P [i] il sottoproblema dato dai primi i intervalli e sia S[i] una sua soluzione ot (o viceversa, visto che inserimento e cancellazione sono simmetriche) peso D[i]. Se l’intervallo i-esimo non fa parte di tale soluzione, allora deve valere D Domanda: D[i 1], dove si assume D[0] = 0; altrimenti, deve essere D[i] = wi + D[pi ]. Esempio: ✦ ✦ complessità? distanza e yahoo? ottima per P [i 1]; se cosı̀ non fosse, esis Infatti, se i ⇥ / S[i], S[i]fraè google anche soluzione una soluzione ottima S [i 1] per P [i 1] di peso D [i 1] > D[i]. Ma tale so Algoritmo? sarebbe ottima anche per(e il P [i], visto che i non è incluso, e questo è assur Come se)problema dobbiamo modificare le condizioni iniziali? invece i ⇥ S[i], S[i] {i} una soluzione per Pricorsiva? [pi ]; se cosı̀ non fosse, esis Come (e se) èdobbiamo modificareottima la definizione una soluzione ottima S [pi ] per P [pi ] di peso D [pi ] > D[i] wi . Ma allora bas sostituire S [pi ] a S[i] {i} nella nostra soluzione ottima per il problema P [i] e otter una soluzione con costo D [pi ] + wi > D[i] wi + wi > D[i], assurdo. ✦ ✦ ✦ ✦ 75 © Alberto Montresor 76 A 2 1 1 0 1 0 B 3 2 1 1 0 1 Insieme indipendente di intervalli pesati ✦ Input Pre-elaborazione ✦ ✦ Siano dati n intervalli distinti [a , b [, ..., [a ,b [ della retta reale, aperti a destra, 1 1 n n 13.2: Risoluzione dello string matching approssimato dove all’intervallo i è associato un peso wi, 1con ≤ i ≤lan.programmazione dinamica. Per poter applicare la programmazione dinamica, è necessario effettuare una pre-elaborazione ✦ Ordiniamo gli intervalli per estremi finali non decrescenti ✦ ✦ Definizione ✦ i e j si dicono disgiunti ≤ ai oppure bi ≤ tale aj che [aj , bj [ non i, sia pi✦ =Due j ilintervalli predecessore di i, dove j < ise: è ilbjmassimo indice ai , bi [ (se non esisteaj, bi i allora pi = 0). i] il sottoproblema dato dai primi i intervalli sua soluzione ottima di aj e sia S[i] buna j ✦ Output: Se l’intervallo i-esimo non fa parte di tale soluzione, allora deve valere D[i] = dove si✦assume = 0; altrimenti, deve essere D[i] = wi +unD[p i ]. TrovareD[0] un insieme indipendente di peso massimo, ovvero sottoinsieme di intervalli disgiunti tra di ottima loro taleper chePla[isomma pesinon degli intervalli nel se i ⇥ / S[i], S[i] ètutti anche soluzione 1]; sedei cosı̀ fosse, esisterebbe sottoinsieme sia la più grande possibile one ottima S [i 1] per P [i 1] di peso D [i 1] > D[i]. Ma tale soluzione tima per il problema P [i], visto che i non è incluso, e questo è assurdo. Se ✦ anche Esempio: ⇥ S[i], ✦S[i] {i} è una soluzione ottimainper [pi ]; se cosı̀ non fosse, esisterebbe Prenotazione di una sala conferenza un P hotel one ottima S [pi ] per P [pi ] di peso D [pi ] > D[i] wi . Ma allora basterebbe 77 © Alberto Montresor S [pi ] a S[i] {i} nella nostra soluzione ottima per il problema P [i] e otterremmo one Definizione con costo Dricorsiva [pi ] + wi > D[i] wi + wi > D[i], assurdo. o prova che il problema ha sottostruttura ottima; pertanto, la relazione di program✦ Definizione ricorsiva del peso di una soluzione ottima inamica che si ottiene è: ✦ D[n] è il problema originario ! D[i] = max{D[i ✦ 1], wi + D[pi ]}, i = 1, . . . , n. Costo della procedura risultante ✦ O(n e il costo dellalogsoluzione ottima del problema originario, partendo dalla quale è fan) per l’ordinamento re a ritroso glilogintervalli di tale degli soluzione. i p1 , . . . , pn si possono precalcolare ✦ O(n n) per il calcolo indici pTutti i O(n) con una singola scansione degli intervalli. Basta ordinare i 2n estremi degli ✦ O(n) per il riempimento della tabella {a1 , . . . , an , b1 , . . . , bn } in modo non decrescente, utilizzare una variabile i inizia✦ O(n) per la ricostruzione della soluzione e scandire gli estremi da sinistra verso destra: se l’elemento scandito è un estremo er esempio aj , allora si assegna i a pj , mentre se l’elemento è un estremo finale, per ✦ Esercizio: si assegna j a i. Dopo la computazione dei predecessori, ciascun D[j] si j , allora ✦ Scrivere algoritmo per il calcolo degli indici p i empo costante. La procedura maxinterval() ha quindi costo totale O(n log n), dovuto mento. cedura mostra che, con una preelaborazione non troppo costosa, è possibile risolvere 79 © Alberto Montresor ✦ b1 ≤ b2 ≤ ⋅ ⋅ ⋅ ≤ bn Per ogni intervallo i, sia pi = j il predecessore di i, dove j < i è il massimo indice tale che [aj , bj [ è disgiunto da [ai , bi [ (se non esiste j, allora pi = 0) Teorema sottostruttura ottima ✦ Sia P[i] il sottoproblema dato dai primi i intervalli e sia S[i] una sua soluzione ottima di peso D[i] ✦ ✦ Se l’intervallo i-esimo non fa parte di tale soluzione, allora deve valere D[i] = D[i − 1], dove si assume D[0] = 0; altrimenti, deve essere D[i] = wi + D[pi] © Alberto Montresor CAPITOLO 13. PROGRAMMAZIONE DINAMICA 78 Codice S ET maxinterval(integer[ ] a, integer[ ] b, integer[ ]w, integer n) { ordina gli intervalli per estremi di fine crescenti } { calcola pj } integer[ ] D new integer[0 . . . n] D[0] 0 for i 1 to n do D[i] max(D[i 1], w[i] + D[pi ]) i n S ET S Set() while i > 0 do if D[i 1] > w[i] + D[pi ] then i i 1 else S.insert(i) i pi return S © Alberto Montresor 80