!
!
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