150 likes | 288 Views
Programmazione dinamica: problema della sottosequenza più lunga. Esempio problema Algoritmo di ricerca esaustiva Caratterizzazione soluzione ottima Algoritmo ricorsivo Stategia Bottom-up Costruzione soluzione ottima. Problema della sottosequenza più lunga.
E N D
Programmazione dinamica:problema della sottosequenza più lunga Esempio problema Algoritmo di ricerca esaustiva Caratterizzazione soluzione ottima Algoritmo ricorsivo Stategia Bottom-up Costruzione soluzione ottima
Problema della sottosequenza più lunga Input: vengono dati in ingresso due sequenze x[1…m] e y[1…n] Problema: Cercare una sottosequenza più lunga che sia comune ad entrambe le sequenze. (Longest Common Subsequence, LCS) Verso sequenza x: A B C B D A B BCBA= LCS(x,y) y: B D C A B A Sottosequenza: una sottosequenza di una sequenza Z è un sottoinsieme strettamente crescente di Z
Algoritmo di bruta forza Idea: per ogni sottosequenza di x[1…m] si controlla se è una sottosequenza di y[1…n]. Si memorizza la prima sottosequenza comune più lunga trovata. • Analisi complessità: • Ci vuole tempo O(n) per controllare se una sequenza è sottosequenza di y[1…n] • 2m sottosequenze a partire da x[1…m] (basta pensare ad un vettore binario lungo m, ogni 1 vuol dire che l’elemento appartiene alla sottosequenza) • Caso Peggiore: O(n 2m )→ tempo esponenziale
Soluzione in programmazione dinamica • Lo sviluppo di un algoritmo in programmazione dinamica può essere diviso in quattro fasi o passi: • Caratterizzazione della struttura di una soluzione ottima. • Definizione ricorsiva del valore di una soluzione ottima. • Calcolo del valore di una soluzione ottima con una strategia bottom-up. • Costruzione di una soluzione ottima a partire dalle informazioni calcolate.
Caratterizzazione soluzione ottima • Siano X[1…m] e Y[1…n] due sequenze e sia Z[z1, z2…zk] una LCS (sottosequenza più lunga), allora: • Se xm = ynallora xm = yn = zk e Z[z1, z2…zk-1] è LCS di X[1…m-1] e Y[1…n-1] • Se xm ≠ ynallora: • se xm ≠ zksi ha che Z[z1, z2…zk] è LCS di X[1…m-1] e Y[1…n] • se yn ≠ zksi ha che Z[z1, z2…zk] è LCS di X[1…m] e Y[1…n-1]
Caratterizzazione soluzione ottima • Dimostrazione: • xm = yn • Allora xm = yn = zk. Se non fosse così e xm = yn ≠ zk, allora si potrebbe estendere Z con xm = yn … assurdo! • Si ha dunque che Z[1…k-1] è sottosequenza lunga k-1 sia di X[1…m-1] sia di Y[1…n-1]. • Z[1…k-1] è anche la LCS di X[1…m-1] e di Y[1…n-1]. • Se non fosse così ci sarebbe Z’[1…t] con t > k-1 LCS di X[1…m-1] e di Y[1…n-1]. Allora Z’ potrebbe essere esteso con xm = yn, formando una sottosequenza comune a X[1…m] e Y[1…n] lunga t+1 > k … assurdo!
Caratterizzazione soluzione ottima Dimostrazione: 2. a. xm ≠ zk Z[1…k-1] è anche la LCS di X[1…m-1] e di Y[1…n]. Se non fosse così ci sarebbe Z’[1…t] con t > k LCS di X[1…m-1] e di Y[1…n]. Allora Z’ sarebbe una sottosequenza comune a X[1…m] e Y[1…n] lunga t > k … assurdo! 2. b. yn ≠ zk(speculare)
Definizione ricorsiva soluzione ottima • Siano X[1…i] e Y[1…j] due sequenze e sia Z[z1, z2…zk] una LCS (sottosequenza più lunga), allora: • Se xi = yj allora xi = yj = zk e Z[z1, z2…zk-1] è LCS di X[1…i-1] e Y[1…j-1] • Se xi ≠ yj allora: • se xi ≠ zk si ha che Z[z1, z2…zk] è LCS di X[1…i-1] e Y[1…j] • se yj ≠ zk si ha che Z[z1, z2…zk] è LCS di X[1…i] e Y[1…j-1]
Definizione ricorsiva soluzione ottima Siano X[1…i] e Y[1…j] due sequenze e sia Z[z1, z2…zk] una LCS (sottosequenza più lunga) con lunghezza c[i,j], allora: X: 1 m i 1 j n Y:
Algoritmo ricorsivo Siano X[1…i] e Y[1…j] due sequenze e sia Z[z1, z2…zk] una LCS (sottosequenza più lunga) con lunghezza c[i,j], allora: LCS(X,Y,i,j) • if i =0 and j= 0 • return 0 • if xi =yj • thenreturn LCS(X,Y,i-1,j-1)+1 • else return max(LCS(X,Y,i-1,j) , LCS(X,Y,i,j-1)) Attenzione: Nel caso xi≠yj vengono generate due chiamate ricorsive (LCS(X,Y,i-1,j) e LCS(X,Y,i,j-1)) che possono generare sottoproblemi comuni.
Algoritmo ricorsivo m = 3 n = 4 3,4 3,3 2,4 UGUALI ! Altezza m+n 1,4 2,3 2,3 3,2 1,3 2,2 1,3 2,2 Caso peggiore: xi≠yj sempre. Vengono generate due chiamate ricorsive (LCS(X,Y,i-1,j) e LCS(X,Y,i,j-1)) che possono generare sottoproblemi comuni.
Stategia bottom-up LCS(X,Y) • m ← length[X] • n ← length[Y] • fori ← 1 to m • do c[i,0] ← 0 • forj ← 1 to n • do c[j,0] ← 0 • fori ← 1 to m • doforj ← 1 to n • doif xi =yj • then c[i,j] ← c[i-1,j-1] +1 • b[I,j] ← ‘diag’ • else if c[i-1,j] ≥ c[i, j-1] • thenc[i,j] ← c[i-1,j] • b[I,j] ← ‘up’ • elsec[i,j] ← c[i,j-1] • b[I,j] ← ‘left’
Stategia bottom-up A B C B D A B Tempo = Θ(m n) Spazio = Θ(m n) = numero di sottosequenze per cui calcolare c[i,j] e b[i,j] B D C A B A
Costruzione di una soluzione ottima A B C B D A B b[i,j] serve per ricavare andando a ritroso la LCS trovata. Ogni “diag” corrisponde ad un elemento della LCS. LCS = “BCBA” B D C A B A
Costruzione di una soluzione ottima PRINT-LCS(b,X,Y,i,j) • ifi = 0 or j = 0 • then return • if b[i,j] = “diag” • thenPRINT-LCS(b,X,Y,i-1,j-1) • print “xi” • elseif b[i,j] = “up” • thenPRINT-LCS(b,X,Y,i-1,j) • elsePRINT-LCS(b,X,Y,i,j-1) Il tempo di esecuzione corrisponde a O(m+n), poiché al peggio deve ogni volta decrementare solo uno dei due valori (i oppure j).