Capitolo 6 Programmazione dinamica 6.4 Il problema della distanza di edit tra due stringhe x e y chiede di calcolare il minimo numero di operazioni su singoli caratteri (inserimento, cancellazione e sostituzione) per trasformare x in y (o viceversa). Fornire un algoritmo quadratico di programmazione dinamica per calcolare la distanza di edit tra x e y. 6.11 Progettare l’algoritmo per stampare il massimo insieme indipendente di un albero. 6.17 Progettare un algoritmo di programmazione dinamica per trovare la sequenza di moltiplicazioni che minimizzi il costo complessivo del prodotto A∗ = A0 × A1 × · · · × An−1 di n matrici, dove la loro taglia è specificata mediante una sequenza di n+ 1 interi positivi d0 , d1 , . . . , dn : la matrice Ai ha taglia di × di+1 per 0 � i � n − 1. Ipotizzare che il costo della moltiplicazione di due matrici di taglia r × s e s × t sia proporzionale a r × s × t. Soluzioni 6.4 Applichiamo il paradigma della programmazione dinamica per il calcolo della distanza di edit, in tempo polinomiale O(mn) dove m = |x| e n = |y|. Definiamo una notazione per la distanza di edit tra un prefisso di x e uno di y D(i, j) = edit(x[0, i − 1], y[0, j − 1]) dove 0 � i � m e 0 � j � n (adottiamo la convenzione che x[0, −1] sia vuota e che x[0, −1] sia vuota). Osserviamo che D(m, n) fornisce la soluzione al nostro problema e definiamo i sotto-problemi elementari come D(i, 0) = i e D(0, j) = j, in quanto se (almeno) PA 18 Capitolo 6 – Programmazione dinamica una delle sequenze è vuota, allora occorrono un numero di operazioni di edit pari alla lunghezza dell’altra stringa. Forniamo la definizione ricorsiva in termini dei sotto-problemi D(i, j) per i > 0 e j > 0, secondo la seguente regola: max{i, j} D(i − 1, j − 1) D(i, j) = min D(i − 1, j − 1) + 1 D(i, j − 1) + 1 D(i − 1, j) + 1 se i = 0 o j = 0 se i, j > 0 e x[i − 1] = y[j − 1] se i, j > 0 e x[i − 1] �= y[j − 1] se j > 0 e x[i − 1] �= y[j − 1] se i > 0 e x[i − 1] �= y[j − 1] (6.1) La prima riga della regola in (6.1) riporta i valori per i sotto-problemi elementari (i � 0 o j � 0). Le successive quattro righe in (6.1) descrivono come ricombinare le soluzioni dei sotto-problemi (i, j > 0): • x[i − 1] = y[j − 1]: se k = D(i − 1, j − 1) è l’edit distnace per x[0, i − 2] e y[0, j − 2], allora k + 1 lo è per x[0, i − 1] e y[0, j − 1], in quanto il loro ultimo elemento è uguale; • x[i− 1] �= y[j− 1]: se D(i− 1, j− 1), D(i, j− 1) e D(i− 1, j) sono le distanze di edit, allora la minima tra queste più uno fornisce la distanza D(i, j), perché dobbiamo sicurante effettuare una sostituzione, una cancellazione o una inserzione. 1 EDIT( a, b ): �pre: x e y sono di lunghezza m e n� 2 for (i = 0; i <= m; i = i+1) 3 distanza[i][0] = i; 4 for (j = 0; j <= n; j = j+1) 5 distanza[0][j] = j; 6 for (i = 1; i <= m; i = i+1) 7 for (j = 1; j <= n; j = j+1) { 8 if (x[i-1] == y[j-1]) { 9 distanza[i][j] = distanza[i-1][j-1]; 10 } else { 11 distanza[i][j] = 1 + MIN{ distanza[i-1][j-1], 12 13 14 } distanza[i][j-1], distanza[i-1][j] }; } return distanza[m][n]; 6.11 La modifica è semplice poiché basta stabilire se il nodo corrente contribuisce o meno al massimo insieme indipendente di un albero. Ipotizziamo per PA Programmare algoritmi semplicità che ogni nodo u abbia due campi dove è stato memorizzato il corrispondente valore di sizeT e sizeF. La chiama iniziale è con u uguale alla radice dell’albero ed escluso posto a false. 1 Stampa( u, escluso ): 2 if (u.primo == null) return; 3 if (escluso) { 4 for (v = u.primo; v != NULL; v = v.fratello) { 5 Stampa( v, false ); 6 } 7 } else { 8 if (u.sizeT >= sizeF) { 9 print u; 10 for (v = u.primo; v != NULL; v = v.fratello) { 11 Stampa( v, true ); 12 } 13 } else { 14 for (v = u.primo; v != NULL; v = v.fratello) { 15 Stampa( v, false ); 16 } 17 } 18 } 6.17 Applichiamo la programmazione dinamica al calcolo della sequenza ottima di moltiplicazioni di n > 2 matrici A∗ = A0 × A1 × · · · × An−1 . Ai fini della nostra discussione, utilizziamo l’algoritmo immediato che moltiplica due matrici di taglia r × s e s × t con l’ipotesi semplificativa che tale algoritmo richieda un numero di operazioni esattamente pari a r × s × t, notando che quanto descritto si applica anche agli algoritmi più veloci come quello di Strassen. Dovendo eseguire n − 1 moltiplicazioni per ottenere A∗ , osserviamo che l’ordine con cui le eseguiamo può cambiare il costo totale quando le matrici hanno taglia differente: nel seguito indichiamo con di × di+1 la taglia della matrice Ai , dove 0 � i � n − 1. Date ad esempio n = 4 matrici tali che d0 = 100, d1 = 20, d2 = 1000, d3 = 2 e d4 = 50, nella seguente tabella mostriamo con le parentesi tutti i possibili ordini di valutazione del loro prodotto A∗ = A0 × A1 × A2 × A3 (di taglia d0 × d4 ), riportando il corrispettivo costo totale di esecuzione per ottenere A∗ : 19 PA 20 Capitolo 6 – Programmazione dinamica (A0 × (A1 × (A2 × A3 )) (A0 × ((A1 × A2 ) × A3 ) ((A0 × A1 ) × (A2 × A3 )) (((A0 × A1 ) × A2 ) × A3 ) ((A0 × (A1 × A2 )) × A3 ) d2 d3 d4 + d1 d2 d4 + d0 d1 d4 = 1.200.000 d1 d2 d3 + d1 d3 d4 + d0 d1 d4 = 142.000 d0 d1 d2 + d2 d3 d4 + d0 d2 d4 = 7.100.000 d0 d1 d2 + d0 d2 d3 + d0 d3 d4 = 2.210.000 d1 d2 d3 + d0 d1 d3 + d0 d3 d4 = 54.000 Per esempio, la quarta riga corrisponde all’ordine naturale di moltiplicazione da sinistra verso destra: la moltiplicazione A0 × A1 richiede d0 × d1 × d2 operazioni e restituisce una matrice di taglia d0 × d2 ; la successiva moltiplicazione per A2 richiede d0 × d2 × d3 operazioni e restituisce una matrice di taglia d0 × d3 ); l’ultima moltiplicazione per A3 richiede d0 × d3 × d4 operazioni. Sommando tali costi e sostituendo i valori di d0 , . . . , d4 , otteniamo un totale di 2.210.000 operazioni. Le altre righe della tabella mostrano che il costo complessivo del prodotto può variare in dipendenza dell’ordine in cui sono effettuate le singole moltiplicazioni per ottenere lo stesso risultato: in questo caso, appare molto più conveniente effettuare le moltiplicazioni nell’ordine indicato nella quinta riga, che fornisce un costo di sole 54.000 operazioni. Il problema che ci poniamo è quello di trovare la sequenza di moltiplicazioni che minimizzi il costo complessivo del prodotto A∗ = A0 × A1 × · · · × An−1 per una data sequenza di n + 1 interi positivi d0 , d1 , . . . , dn (ricordiamo la nostra ipotesi che il costo della moltiplicazione di due matrici di taglia r × s e s × t sia pari a r × s × t). Possiamo ottenere un modo efficiente di affrontare il problema considerando il sotto-problema di trovare il costo per effettuare il prodotto di un gruppo consecutivo di matrici, Ai × Ai+1 × · · · × Aj , dove 0 � i � j � n − 1, indicando con M(i, j) il corrispondente costo minimo: chiaramente, in tal modo siamo anche in grado di risolvere il problema iniziale calcolando M(0, n − 1). Possiamo immediatamente notare che M(i, i) = 0 per ogni i, in quanto ha costo nullo calcolare il prodotto della sequenza composta dalla sola matrice Ai . Se passiamo al caso di M(i, j) con i < j, osserviamo che possiamo ottenere la matrice Ai × Ai+1 × · · · × Aj (di taglia di × dj+1 ) fattorizzandola come una moltiplicazione tra Ai × · · · × Ar (di taglia di × dr+1 ) e Ar+1 × · · · × Aj (di taglia dr+1 × dj+1 ), per un qualunque intero r tale che i � r < j. Il costo di tale moltiplicazione è pari a di × dr+1 × dj+1 , a cui vanno aggiunti i costi minimi M(i, r) e M(r + 1, j) per calcolare rispettivamente Ai × · · · × Ar e Ar+1 × · · · × Aj . Supponiamo di aver già calcolato induttivamente quest’ultimi costi per tutti i possibili valori r con i � r < j: allora il costo minimo M(i, j) sarà dato dal minimo, al variare di r, tra i valori M(i, r) + M(r + 1, j) + di dr+1 dj+1 . Da PA Programmare algoritmi 1 CostoMinimoIterativo( ): 2 for (i = 0; i < n; i = i+1) { 3 costi[i][i] = 0; 4 } 5 for (diagonale = 1; diagonale < n; diagonale = diagonale+1) { 6 for (i = 0; i < n-diagonale; i = i+1) { 7 j = i + diagonale; 8 costi[i][j] = +∞; 9 for (r = i; r < j; r = r+1) { 10 costo = costi[i][r] + costi[r+1][j]; 11 costo = costo + d[i] × d[r+1] × d[j+1]; 12 if (costo < costi[i][j]) { 13 costi[i][j] = costo; 14 indice[i][j] = r; 15 } 16 } 17 } 18 } 19 return costi[0][n-1]; Codice 6.1 Algoritmo iterativo per il calcolo dei costi minimi di moltiplicazione M(i, j). ciò deriva la seguente regola ricorsiva: M(i, j) = � 0 � � se i � j mini�r<j M(i, r) + M(r + 1, j) + di dr+1 dj+1 altrimenti (6.2) Calcoliamo una sola volta i valori M(i, j) memorizzandoli in una tabella dei costi, realizzata mediante un array bidimensionale costi di taglia n× n, in modo tale che costi[i][j] = M(i, j) per 0 � i � j � n − 1 (gli elementi della tabella corrispondenti a valori di i e j tali che i > j non sono utilizzati dall’algoritmo). L’algoritmo descritto nel Codice 6.1 effettua il calcolo dei valori M(i, j) secondo quanto appena illustrato e, basandosi sulla regola in (6.2), riempie l’array costi dal basso in alto, a partire dai valori costi[i][i], per 0 � i < n, fino a ottenere il valore costi[0][n − 1] da restituire. A partire dai valori noti costi[i][i] = 0 sulla diagonale 0 (righe 2–3), l’algoritmo determina tutti i valori costi[i][j] sulla diagonale 1 (con 0 � i < n − 1 e j = i + 1), poi quelli sulla diagonale 2 (con 0 � i < n − 2 e j = i + 2), e cosı̀ via, fino al valore costi[0][n − 1] sulla diagonale n − 1 (righe 5–18): come possiamo notare, gli elementi su una data diagonale sono identificati nel codice dai 21 PA 22 Capitolo 6 – Programmazione dinamica due indici i e j tali che 0 � i < n − diagonale e j = i + diagonale. A ogni iterazione, il ciclo alle righe 9–16 calcola il minimo costo. Osserviamo che la complessità dell’algoritmo nel Codice 6.1 è polinomiale, in quanto esegue tre cicli annidati, ciascuno di n iterazioni al più, per un totale di O(n3 ) operazioni (chiaramente, le istruzioni all’interno di tali cicli possono essere eseguite in tempo costante rispetto al numero n di matrici da moltiplicare). Nel corso della sua esecuzione, inoltre, l’algoritmo usa le matrici costi e indice, aventi ciascuna n righe e n colonne, e una quantità costante di altre locazioni di memoria. Da ciò possiamo concludere che la complessità temporale dell’algoritmo è O(n3 ), mentre la sua complessità spaziale è O(n2 ).