4/19/2015E. Giovannetti -- OI09.1 Olimpiadi di Informatica 2010 Giornate preparatorie Dipartimento di Informatica Università di Torino marzo – Programmazione dinamica: distanza minima fra due sequenze di caratteri. (versione 19/04/2015)
19/04/ E. Giovannetti - AlgELab Lez.512 Distanza fra due sequenze di caratteri Si considerino le seguenti tre operazioni su una sequenza di caratteri (stringa): inserimento di un carattere all'interno della stringa (o in testa alla stringa); cancellazione di un carattere della stringa (con compatta- mento della stessa); sostituzione di un carattere della stringa con un altro carattere. Date due stringhe S1 ed S2, si definiscono: trasformazione di S1 in S2: una sequenza di operazioni dei tre generi precedenti che trasforma la sequenza S1 nella sequenza S2 ; distanza (minima) fra S1 ed S2: lunghezza della più corta trasformazione di S1 in S2, cioè numero mimimo di operazioni necessario per trasformare S1 in S2.
19/04/ E. Giovannetti - AlgELab Lez.513 Esempio trasformazione (non minimale) di RISOTTO in PRESTO: RISOTTO 1cancella RISOTTO 2cancella ISOTTO 3cancella SOTTO 4cancella OTTO 5cambia T in SSTO 6inserisci PPSTO 7inserisci RPRSTO 8inserisci EPRESTO distanza (non minima) = 8
19/04/ E. Giovannetti - AlgELab Lez.514 Esempi trasformazione minimale di RISOTTO in PRESTO: RISOTTO inserisci PPRISOTTO cambia I in EPRESOTTO cancella OPRESTTO (da PRESOTTO) cancella TPRESTO (da PRESTTO) distanza (minima) = 4 trasformazione di STUDENTE in LAUREATO: STUDENTE cambia S in LLTUDENTE cambia T in ALAUDENTE cambia D in RLAURENTE cambia N in ALAUREATE cambia E in OLAUREATO distanza (minima) = 5
19/04/ E. Giovannetti - AlgELab Lez.515 Esempi trasformazione di ACQUA in VINO: ACQUA cancella ACQUA cambia C in VVQUA cambia Q in IVIUA cambia U in NVINA cambia A in OVINO distanza (minima) = 5
19/04/ E. Giovannetti - AlgELab Lez.516 Esercizio Si specifichi, usando la tecnica della programmazione dinami- ca, l'algoritmo che, date due stringhe, trova la più corta tra- sformazione da una all'altra.
L’algoritmo ricorsivo Le due (sotto)stringhe terminano con lo stesso carattere: b 1 b 2...b i-1 c c 1 c 2...c j-1 c Allora la più corta trasformazione dalla prima alla seconda coincide con la più corta trasformazione b 1 b 2...b i-1 c 1 c 2...c j-1 Le due (sotto)stringhe terminano con caratteri diversi: b 1 b 2...b i-1 b c 1 c 2...c j-1 c Allora la più corta trasformazione dalla prima alla seconda è la più corta fra: b 1 b 2...b i-1 c 1 c 2...c j-1 seguita da CAMBIA b IN c; b 1 b 2...b i-1 b c 1 c 2...c j-1 seguita da INSERISCI c; b 1 b 2...b i-1 c 1 c 2...c j-1 c seguita da CANCELLA b; 19/04/ E. Giovannetti - AlgELab Lez.517
19/04/ E. Giovannetti - AlgELab Lez.518 Esempio: trasf(“ RIS ", “ PRES ") = trasf (“ RI ", “ PRE ") distanze: d(“ RIS ", “ PRES ") = d(“ RI ", “ PRE ") PRESTO R Id S d O T T O stringa di partenza stringa di arrivo
19/04/ E. Giovannetti - AlgELab Lez.519 Esempio: trasf(“ RISO ", “ PREST ") d(“ RISO ", “ PREST ") = min(d 1, d 2, d 3 ) + 1 PRESTO R I S d1d1 d2d2 Od3d3 T T O stringa di partenza stringa di arrivo
Base della ricorsione b 1 b 2...b i stringa vuota CANCELLA b 1, CANCELLA b 2,..., CANCELLA b i ; stringa vuota c 1 c 2...c j INSERISCI c 1, INSERISCI c 2,..., INSERISCI c j ; stringa vuota stringa vuota TRASFORMAZIONE NULLA Nota: Poiché in C(++) le stringhe e gli array sono indiciati a partire da 0, il numero di passi della trasformazione b 0 b 1...b i stringa vuota è i+1; analogamente il numero di passi della trasformazione stringa vuota c 0 c 1...c j è j+1; 19/04/ E. Giovannetti - AlgELab Lez.5110
Trovare solo la distanza: funzione ricorsiva … string s, t; int m, n; int min3(int x, int y, int z) { int min = x <= y ? x : y; if(z < min) min = z; return min; } int dis(int i, int j) { if(i == -1) return j+1; if(j == -1) return i+1; if(s.at(i) == t.at(j)) return dis(i-1,j-1); else return 1+min3(dis(i-1,j-1),dis(i,j-1),dis(i-1,j)); } Nel main:... cout << dis(m-1, n-1); 19/04/ E. Giovannetti - AlgELab Lez.5111
… inefficiente ! Soffre dello stesso grave difetto di fibonacci ricorsivo: il ricalcolo degli stessi valori rende “intrattabile” la complessità della procedura ! Come nel caso di fibonacci, occorre memorizzare i risultati delle chiamate ricorsive, in modo da non ricalcolare i valori già calcolati in qualche chiamata precedente. 19/04/ E. Giovannetti - AlgELab Lez.5112
Ricorsione con memorizzazione Poiché le chiamate ricorsive sono della forma dist(i, j), con 0 i m-1, 0 j n-1, per memorizzarne i risultati serve una matrice di dimensioni m n. Inizializziamo tutti gli elementi di tale matrice ad un valore impossibile della distanza di editing, ad es. -1. Nella funzione dist, prima di andare ad eseguire il calcolo con le chiamate ricorsive, si controlla se il corrispondente elemento della matrice non contiene già il risultato (cioè è diverso da -1), e in tal caso lo si restituisce senza rifare il calcolo. Se invece il risultato viene calcolato, prima della return lo si memorizza nella matrice. 19/04/ E. Giovannetti - AlgELab Lez.5113
Ricorsione con memorizzazione: codice C++. #define MAXL 100; string s, t; int m, n; int d[MAXL][MAXL];... int dis(int i, int j) { if(i == -1) return j+1; if(j == -1) return i+1; if(d[i][j] == -1) { if(s.at(i)==t.at(j)) d[i][j]= dis(i-1,j-1); else d[i][j] = 1+min3(dis(i-1,j-1),dis(i,j-1),dis(i-1,j)); } return d[i][j]; } 19/04/ E. Giovannetti - AlgELab Lez.5114
Versione iterativa Come nel caso di fibonacci, l’algoritmo precedente si può scrivere in forma iterativa: la funzione dist diventa una procedura contenente un ciclo che calcola ad uno ad uno gli elementi della matrice, riga per riga. Alla fine la soluzione si troverà nell’elemento più a sinistra in basso, che è l’ultimo calcolato. È inoltre conveniente utilizzare una matrice (m+1) (n+1), dove la colonna 0 rappresenta la stringa vuota come stringa iniziale, la riga 0 la stringa vuota come stringa finale. Occorre allora premettere alle stringhe un carattere fittizio (ad es. un blank): s = “ ” + s; t = “ ” + t; La soluzione si troverà quindi in d[m][ n]. Prima del ciclo di calcolo vero e proprio bisogna inizializzare la prima riga e la prima colonna. 19/04/ E. Giovannetti - AlgELab Lez.5115
19/04/ E. Giovannetti - AlgELab Lez.5116 Esempio: distanza ("RISOTTO", "PRESTO") PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1 I del 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo
Versione iterativa: codice C++. void dist() { // inizializzazione della riga 0 e della colonna 0 for(int i = 0; i <= m; i++) d[i][0] = i; for(int j = 1; j <= n; j++) d[0][j] = j; for(int i = 1; i <= m; i++) { for(int j = 1; j <= n; j++) { if(s.at(i) == t.at(j)) d[i][j] = d[i-1][j-1]; else d[i][j] = 1 + min3(d[i-1][j-1], d[i][j-1], d[i-1][j] ); } 19/04/ E. Giovannetti - AlgELab Lez.5117
Trovare la trasformazione più corta. Se invece della sola distanza minima si vuole anche trovare la corrispondente sequenza di operazioni che trasforma la prima stringa nella seconda, occorre costruire, a fianco della matrice delle distanze, una matrice delle operazioni. Ciascun elemento della matrice contiene l’ultima operazione della trasformazione fra le due sottostringhe corrispondenti all’elemento. Alla fine, partendo dall’ultimo elemento a sinistra in basso si ricostruisce all’indietro il cammino percorso, dove la casella precedente dipende dal contenuto della casella corrente: CAMBIA : vai alla casella indietro in diagonale CANCELLA : vai alla casella sopra in verticale INSERISCI : vai alla casella a sinistra in orizzontale NO OP : vai alla casella indietro in diagonale 19/04/ E. Giovannetti - AlgELab Lez.5118
19/04/ E. Giovannetti - AlgELab Lez.5119 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1no 1ins 2ins 3ins 4ins 5 I del 2 c 2 c 3... S del 3 c 3 no 2... O del 4 c 4 del 3 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("RISO", "PRES") = trasf("RIS", "PRES") + del O
Esempio di esecuzione Per semplicità, disegniamo le due matrici in sovrapposizione, come se fossero un’unica matrice in cui ogni casella contiene sia la distanza che l’operazione. 19/04/ E. Giovannetti - AlgELab Lez.5120
19/04/ E. Giovannetti - AlgELab Lez.5121 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1 I del 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("R", "P") = [Cambia R in P], lungh = 1
19/04/ E. Giovannetti - AlgELab Lez.5122 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1no 1 I del 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("R", "PR") = trasf("", "P")
19/04/ E. Giovannetti - AlgELab Lez.5123 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1 no 1ins 2 I del 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("R", "PRE") = trasf("R", "PR") + ins E
19/04/ E. Giovannetti - AlgELab Lez.5124 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1no 1ins 2ins 3ins 4ins 5 I del 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("R", "PRESTO") = trasf("R", "PREST") + ins O
19/04/ E. Giovannetti - AlgELab Lez.5125 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1no 1ins 2ins 3ins 4ins 5 I del 2 c 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("RI", "P") = trasf("R", "") + cambia I in P
19/04/ E. Giovannetti - AlgELab Lez.5126 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1no 1ins 2ins 3ins 4ins 5 I del 2 c 2 S del 3 O del 4 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("RI", "PR") = trasf("R", "P") + cambia I in R
19/04/ E. Giovannetti - AlgELab Lez.5127 PRESTO no 0ins 1ins 2ins 3ins 4ins 5ins 6 R del 1c 1no 1ins 2ins 3ins 4ins 5 I del 2 c 2 c 3... S del 3 c 3 no 2... O del 4 c 4 del 3 T del 5 T del 6 O del 7 stringa di partenza stringa di arrivo trasf("RISO", "PRES") = trasf("RIS", "PRES") + del O
eccetera Esercizio: completare a mano la tabella. 19/04/ E. Giovannetti - AlgELab Lez.5128
19/04/ E. Giovannetti - AlgELab Lez.5129 Ricostruzione della sequenza di operazioni La sequenza si ricostruisce seguendo all'indietro le frecce.