Allineamento di stringhe e sottosequenza palindroma più lunga
In questa pagina 4
Due esercitazioni sulla programmazione dinamica (Programmazione dinamica - Fibonacci, memoizzazione e schema del paradigmala programmazione dinamica (DP) evolve il divide and conquer: invece di risolvere di nuovo le stesse sottoistanze, le salva in una tabella. Fibonacci ricorsivo costa F_n - 1 addizioni (esponenziale), quello con tabella n - 1. Tre condizioni: sottostruttura, sottoistanze ripetute, spazio dei sottoproblemi ristretto. Bottom-up o memoizzazione (ricorsione + tabella globale). Per i problemi di ottimizzazione lo schema è: sottostruttura ottima, ricorrenza sui costi, informazione addizionale, calcolo della tabella, ricostruzione.Programmazione dinamica - Fibonacci, memoizzazione e schema del paradigma →) che riusano la struttura della Longest Common Subsequence - ricorrenza, tabella e ricostruzionela LCS di X (m caratteri) e Y (n caratteri) è la sottosequenza comune più lunga. Sottoistanze = coppie di prefissi (Theta(mn)). Ricorrenza: l(i,j) = 0 se i = 0 o j = 0; 1 + l(i-1,j-1) se x_i = y_j; max{l(i-1,j), l(i,j-1)} altrimenti. Bottom-up con tabelle l e b (freccia): mn confronti; ricostruzione in O(m+n). Il D&C sulla stessa ricorrenza costa C(m+n,m) - 1 nodi interni nel caso peggiore (esponenziale). Memoizzazione: stessa complessità nel caso peggiore, ma molto meno lavoro su stringhe simili. Spazio ridotto a Theta(min{m,n}) se serve solo la lunghezza.Longest Common Subsequence - ricorrenza, tabella e ricostruzione →: la prima con lo stesso spazio dei sottoproblemi (coppie di prefissi), la seconda con uno spazio diverso (sottostringhe).
Allineamento di stringhe di DNA
L'alfabeto del DNA è (i quattro nucleotidi); per l'allineamento si estende con un carattere di spazio, . Si risolve un problema di ottimizzazione molto simile alla LCS e il costo ottimo è una metrica di similarità tra le due stringhe.
Definizione del problema
Definizione (allineamento). Date e su , un allineamento di lunghezza è una coppia di stringhe , su , con , tali che:
- è sottosequenza di e è sottosequenza di (esistono con , e analogamente per );
- le posizioni che non realizzano caratteri di (rispettivamente ) contengono solo ;
- in ogni posizione non ci sono due : .
In pratica ogni posizione (colonna) dell'allineamento è di uno di tre tipi: carattere di sopra carattere di ; carattere di sopra uno spazio; spazio sopra carattere di .
Esempio. , . Due allineamenti:
Z^X = A G T T Z^X = A G T T
Z^Y = A - T T Z^Y = A T T -Nel secondo, la posizione realizza insieme e .
Costo. Una matrice di score dà un intero per ogni coppia ordinata di nucleotidi (di solito simmetrica, positiva sulla diagonale per i match, negativa altrove); la penalità di gap (di solito negativa) è il peso di una posizione con uno spazio. Il peso di una posizione e il costo dell'allineamento sono
Problema dell'allineamento ottimo. Date , e , determinare il costo massimo di un allineamento delle due stringhe.
Esempio (seguito). , per , . Il primo allineamento costa ; il secondo . Il primo è ottimo (controllato enumerando tutti i allineamenti ammissibili delle due stringhe: nessuno supera ).
Sottostruttura ottima
Appena si sceglie come finiscono i due allineamenti, il resto è un allineamento di prefissi. Lo spazio dei sottoproblemi è quindi quello della LCS: .
Casi base. Se il prefisso è vuoto: per la condizione 3 non possono esserci due spazi nella stessa posizione, quindi l'unico allineamento ammissibile di è ( spazi), , di costo . Simmetricamente per il costo è ; per la coppia di stringhe vuote ha costo .
Casi non di base (, ). Sia un allineamento ottimo di , di lunghezza . L'ultima posizione può essere solo di tre tipi:
- e (l'ultima posizione realizza gli ultimi caratteri di entrambi). Allora le prime posizioni sono un allineamento ottimo di — altrimenti, con il cut-and-paste, se ne potrebbe sostituire uno migliore e ottenere un allineamento di di costo superiore — e .
- e (solo l'ultimo carattere di ). Le prime posizioni sono un allineamento ottimo di e .
- e , simmetrico: allineamento ottimo di e .
I tre allineamenti ottenuti dai tre punti a partire dagli ottimi dei sottoproblemi sono tutti ammissibili, quindi vince quello di costo massimo.
Ricorrenza sui costi
Con il costo dell'allineamento ottimo di , per e :
Per ricostruire l'allineamento si salva, come nella LCS, una freccia: , o secondo quale dei tre termini realizza il massimo.
Codice bottom-up
Il grafico delle dipendenze è identico a quello della LCS (la cella dipende da , e ), quindi si riempie per righe.
ALIGN(X, Y, S, g)
m <- |X|; n <- |Y|
for i <- 0 to m do C[i, 0] <- i * g; b[i, 0] <- '↑' { b[0, 0] non viene letta }
for j <- 1 to n do C[0, j] <- j * g; b[0, j] <- '←'
for i <- 1 to m do
for j <- 1 to n do
d <- C[i-1, j-1] + S[x_i, y_j]
u <- C[i-1, j] + g
l <- C[i, j-1] + g
C[i, j] <- max(d, u, l)
if C[i, j] = d then b[i, j] <- '↖'
else if C[i, j] = u then b[i, j] <- '↑'
else b[i, j] <- '←'
return (C[m, n], b)
PRINT_ALIGN(X, Y, b, i, j) { stampa le colonne dell'allineamento da sinistra a destra }
if i = 0 and j = 0 then return
if b[i, j] = '↖' then
PRINT_ALIGN(X, Y, b, i-1, j-1); stampa la colonna (x_i sopra y_j)
else if b[i, j] = '↑' then
PRINT_ALIGN(X, Y, b, i-1, j); stampa la colonna (x_i sopra '-')
else
PRINT_ALIGN(X, Y, b, i, j-1); stampa la colonna ('-' sopra y_j)Il costo è (confronti e addizioni tra interi) e la ricostruzione ha chiamate.
Esempio eseguito
, , , per , . In grassetto il percorso della ricostruzione.
| A | G | C | T | ||
|---|---|---|---|---|---|
| 0 | −2← | −4← | −6← | −8← | |
| A | −2↑ | 1↖ | −1← | −3← | −5← |
| C | −4↑ | −1↑ | −2↖ | 0↖ | −2← |
| C | −6↑ | −3↑ | −4↖ | −1↖ | −3↖ |
| G | −8↑ | −5↑ | −2↖ | −3↑ | −4↖ |
| T | −10↑ | −7↑ | −4↑ | −5↖ | −2↖ |
Celle a mano:
- Riga e colonna : e .
- : , quindi ; le altre due scelte danno e . Massimo , .
- : e sono diversi: ; ; . Massimo , .
- : : , contro e . Vale .
- : , . ; ; . Massimo con : è allineata a uno spazio.
Risultato: . Ricostruzione: colonna ; colonna ; colonna ; colonna ; colonna . Letto in avanti:
Z^X = A C C G T
Z^Y = A G C - T costo: 1 + (-3) + 1 + (-2) + 1 = -2La tabella e la ricostruzione sono state confrontate con una enumerazione di tutti gli allineamenti su coppie casuali di stringhe corte e matrici di score casuali.
Spazio lineare
Le stringhe di DNA sono molto lunghe e uno spazio è inaccettabile. Se serve solo il costo ottimo (nessuna ricostruzione) basta una sola riga, anzi un solo vettore , supponendo (altrimenti si scambiano i ruoli). All'inizio dell'iterazione interna all'iterazione , contiene la riga già calcolata, e la riga . Calcolando servono , e . Il primo termine va conservato prima di sovrascrivere : lo si tiene nella variabile prev.
ALIGNMENT_COST(X, Y, S, g) { vincolo: |X| >= |Y| }
m <- |X|; n <- |Y|
for j <- 0 to n do C[j] <- j * g { riga 0 }
for i <- 1 to m do
prev <- C[0] { C(i-1, 0), serve alla colonna j = 1 }
C[0] <- i * g { C(i, 0) }
for j <- 1 to n do
new <- max(prev + S[x_i, y_j], C[j] + g, C[j-1] + g)
prev <- C[j] { C(i-1, j), per la colonna j+1 }
C[j] <- new
return C[n]Nell'esempio, dopo l'iterazione il vettore vale la riga della tabella: dopo è , dopo è , e alla fine . Il tempo è , lo spazio ausiliario ; verificato su stringhe casuali contro la tabella completa.
Memoizzazione e spazio. Il codice memoizzato non permette di risparmiare spazio, perché le sottoistanze sono generate in ordine dinamico e non ordinato. In compenso, su sequenze molto simili fa molto meno lavoro del bottom-up. Il compromesso: se lo spazio quadratico non è un problema, la memoizzazione è preferibile; con spazio lineare serve il bottom-up (più lento su DNA molto simili).
Sottosequenza palindroma più lunga (LPS)
Definizione. Una stringa è palindroma se per , cioè è uguale alla stringa letta al contrario. La stringa vuota è palindroma (). Esistono palindrome di lunghezza pari () e dispari ().
Data , si cerca la sua Longest Palindrome Subsequence, : la sottosequenza palindroma più lunga. Per esempio ha .
Lo spazio dei sottoproblemi
Se è una sottosequenza palindroma di , realizzata dagli indici , allora anche è palindroma, ed è sottosequenza di (se non è vuota), perché e . Si toglie un carattere da ciascun estremo: servono quindi le sottostringhe , non i prefissi. Lo spazio è , di cardinalità .
Sottostruttura ottima
Sia una LPS di .
- : (base).
- : se allora , altrimenti oppure (base).
- (almeno tre caratteri):
- se , allora e è una LPS di ;
- se , allora è la più lunga tra una LPS di e una LPS di .
Dimostrazione dei punti 1 e 2: banali. Punto 3, . Siano gli indici che realizzano . Si dimostra prima . Se o , allora sarebbe ancora palindroma (i due estremi sono uguali tra loro) e sottosequenza di (usa gli indici e , perché implica che né né siano tra gli indici di ), più lunga di di caratteri: contraddizione. Quindi e . è palindroma ed è sottosequenza di . Se non fosse una LPS di , si prenderebbe una LPS di e sarebbe una sottosequenza palindroma di più lunga di (cut-and-paste).
Punto 3, . Poiché è palindroma, non può essere uguale sia a sia a . Se , l'indice non è usato e è una sottosequenza di ; se , è sottosequenza di (può valere anche entrambi). In ciascun caso è palindroma e, per cut-and-paste, deve essere una LPS della sottostringa corrispondente. Poiché le LPS di e sono anche sottosequenze palindrome di , è la più lunga delle due.
Ricorrenza e informazione addizionale
Con , per :
Informazione addizionale, per i casi : se ; se ; se .
Codice bottom-up per diagonali
Sia la lunghezza della sottostringa. I casi base stanno sulle diagonali e . Per , la cella dipende da (lunghezza ) e da e (lunghezza ): una scansione per diagonali crescenti è coerente. Sono valide anche la scansione per righe dalla alla (colonne crescenti) e quella per colonne dalla alla (righe decrescenti).
COMPUTE_P(X)
n <- |X|
for i <- 1 to n-1 do
P[i, i] <- 1
if x_i = x_{i+1} then P[i, i+1] <- 2 else P[i, i+1] <- 1
P[n, n] <- 1
for l <- 3 to n do
for i <- 1 to n - l + 1 do
j <- i + l - 1
if x_i = x_j then
P[i, j] <- P[i+1, j-1] + 2; B[i, j] <- '↙'
else if P[i+1, j] >= P[i, j-1] then
P[i, j] <- P[i+1, j]; B[i, j] <- '↓'
else
P[i, j] <- P[i, j-1]; B[i, j] <- '←'
return (P[1, n], B)Costo. Spazio per e . Un confronto tra caratteri per ogni sottostringa di lunghezza , cioè per ogni coppia :
Se interessa solo la lunghezza, le tabelle si possono sostituire con tre vettori (diagonali , e ), spazio lineare, come per la LCS (verificato con Python).
Esempio eseguito
(). Ogni cella con contiene e la freccia; in grassetto le celle visitate dalla ricostruzione.
| 1: A | 2: B | 3: C | 4: B | 5: D | 6: A | 7: C | 8: B | |
|---|---|---|---|---|---|---|---|---|
| 1: A | 1 | 1 | 1↓ | 3↓ | 3↓ | 5↙ | 5← | 5↓ |
| 2: B | 1 | 1 | 3↙ | 3← | 3← | 3↓ | 5↙ | |
| 3: C | 1 | 1 | 1↓ | 1↓ | 3↙ | 3↓ | ||
| 4: B | 1 | 1 | 1↓ | 1↓ | 3↙ | |||
| 5: D | 1 | 1 | 1↓ | 1↓ | ||||
| 6: A | 1 | 1 | 1↓ | |||||
| 7: C | 1 | 1 | ||||||
| 8: B | 1 |
Alcune celle:
- : , quindi ().
- : ; e sono uguali, e per il del codice si sceglie .
- : , quindi ().
- : ; , quindi .
Ricostruzione (cammino evidenziato): passa a , che è e stampa ; è e stampa ; è e va a , dove e : si stampa ; risalendo si stampano e . La LPS è , di lunghezza (la stessa dei controlli con forza bruta e con ).
PRINT_LPS(X, B, i, j) { prima chiamata: PRINT_LPS(X, B, 1, n) }
if i = j then print(x_i); return
if j = i + 1 then
if x_i = x_j then print(x_i x_j) else print(x_i)
return
if B[i, j] = '↙' then
print(x_i)
PRINT_LPS(X, B, i+1, j-1)
print(x_j)
else if B[i, j] = '↓' then PRINT_LPS(X, B, i+1, j)
else PRINT_LPS(X, B, i, j-1)(Nelle dispense i return e il ramo else dei casi base sono omessi: senza di essi, dopo il caso base il codice leggerebbe , non definito.) Il numero di caratteri stampati è . L'albero delle chiamate ha un solo nodo per livello e la differenza (all'inizio ) decresce di o di a ogni livello, fino a o : i confronti sono .
Relazione con la LCS. ha la stessa lunghezza di , con la stringa rovesciata (verificato per stringhe casuali). Il codice dedicato è però più economico: usa il solo triangolo superiore ( confronti, invece degli della LCS).
def lps_len(x):
n = len(x)
p = [[0] * n for _ in range(n)]
for i in range(n):
p[i][i] = 1
for i in range(n - 1):
p[i][i + 1] = 2 if x[i] == x[i + 1] else 1
for l in range(3, n + 1):
for i in range(n - l + 1):
j = i + l - 1
p[i][j] = p[i + 1][j - 1] + 2 if x[i] == x[j] else max(p[i + 1][j], p[i][j - 1])
return p[0][n - 1]
assert lps_len("ABCBDACB") == 5 and lps_len("SOBS") == 3Domande d'esame
- (Risposta numerica.) Per qual è e quanti confronti tra caratteri esegue
COMPUTE_Psu una stringa di caratteri? Traccia: ; confronti. - (Quesito aperto.) Perché per la LPS lo spazio dei sottoproblemi è quello delle sottostringhe e non dei prefissi? Come si scandisce la tabella? Traccia: a ogni passo si tolgono caratteri da entrambi gli estremi, quindi servono sottostringhe (); dipende da , , , di lunghezza e : per diagonali crescenti.
- (Calcolo.) Con , e , per e quanto vale ? Qual è l'allineamento? Traccia: , , ; , con : , costo .
Esercizi svolti di esame su problemi dello stesso tipo (ricorrenza sulle sottostringhe, scansione per diagonali): Esercizio - sottosequenza quasi palindroma di lunghezza massima (compito dell'8 luglio 2024, es. 2) e Esercizio - sottosequenza antipalindroma di lunghezza massima (compito del 9 luglio 2025, es. 2).
Versione ripasso
- Allineamento. . Coppia di lunghezza : sottosequenza di , di , spazi solo fuori dai caratteri di e , mai due spazi nella stessa posizione. Peso di una posizione: se nessuno spazio, altrimenti; costo = somma; si cerca il massimo. Esempio , , , , : costa (ottimo); costa .
- Sottostruttura (prefissi , ultima posizione di tre tipi, cut-and-paste): da , da , da . Casi base: l'unico allineamento di ha spazi, costo .
- Ricorrenza: , , . Frecce ; tempo , ricostruzione .
- Un solo vettore (solo costo, ):
prev <- C[0]; C[0] <- i*g; for j: new <- max(prev + S[x_i,y_j], C[j] + g, C[j-1] + g); prev <- C[j]; C[j] <- new. Spazio . Memoizzato: niente risparmio di spazio, meno lavoro su sequenze simili.
| A | G | C | T | ||
|---|---|---|---|---|---|
| 0 | −2← | −4← | −6← | −8← | |
| A | −2↑ | 1↖ | −1← | −3← | −5← |
| C | −4↑ | −1↑ | −2↖ | 0↖ | −2← |
| C | −6↑ | −3↑ | −4↖ | −1↖ | −3↖ |
| G | −8↑ | −5↑ | −2↖ | −3↑ | −4↖ |
| T | −10↑ | −7↑ | −4↑ | −5↖ | −2↖ |
, : , allineamento (costo ). Celle: : ; : ; : con .
ALIGNMENT_COST(X, Y, S, g) { |X| >= |Y|, un solo vettore C[0..n] }
for j <- 0 to n do C[j] <- j*g
for i <- 1 to m do
prev <- C[0]; C[0] <- i*g
for j <- 1 to n do
new <- max(prev + S[x_i,y_j], C[j] + g, C[j-1] + g)
prev <- C[j]; C[j] <- new
return C[n]- LPS. Palindroma: , anche . Spazio: sottostringhe (), perché si tolgono i due estremi. Struttura per : implica e è LPS di ; : la più lunga tra LPS e LPS.
- Ricorrenza: ; se , altrimenti ; per : se , altrimenti . Frecce . Scansione per diagonali (valgono anche righe da a o colonne). Confronti: ; stampa .
- Codice.
ALIGN: C[i,0] <- i*g; C[0,j] <- j*g; per i, j >= 1: d <- C[i-1,j-1] + S[x_i,y_j]; u <- C[i-1,j] + g; l <- C[i,j-1] + g
C[i,j] <- max(d,u,l); b[i,j] <- '↖' se = d, '↑' se = u, altrimenti '←'
COMPUTE_P(X): P[i,i] <- 1; P[i,i+1] <- 2 se x_i = x_{i+1} altrimenti 1
for l <- 3 to n do for i <- 1 to n-l+1 do
j <- i+l-1
if x_i = x_j then P[i,j] <- P[i+1,j-1] + 2; B[i,j] <- '↙'
else if P[i+1,j] >= P[i,j-1] then P[i,j] <- P[i+1,j]; B[i,j] <- '↓'
else P[i,j] <- P[i,j-1]; B[i,j] <- '←'- Esempio LPS: (), . Celle con (in grassetto le visitate dalla ricostruzione):
| 1: A | 2: B | 3: C | 4: B | 5: D | 6: A | 7: C | 8: B | |
|---|---|---|---|---|---|---|---|---|
| 1: A | 1 | 1 | 1↓ | 3↓ | 3↓ | 5↙ | 5← | 5↓ |
| 2: B | 1 | 1 | 3↙ | 3← | 3← | 3↓ | 5↙ | |
| 3: C | 1 | 1 | 1↓ | 1↓ | 3↙ | 3↓ | ||
| 4: B | 1 | 1 | 1↓ | 1↓ | 3↙ | |||
| 5: D | 1 | 1 | 1↓ | 1↓ | ||||
| 6: A | 1 | 1 | 1↓ | |||||
| 7: C | 1 | 1 | ||||||
| 8: B | 1 |
Ricostruzione: , (stampa ), (stampa ), , base: ; poi , : LPS . Celle: , . ha la lunghezza di , ma il codice dedicato usa metà delle celle.
- Prova per : se fosse (o ), sarebbe palindroma e più lunga di ; se non fosse una LPS di , si sostituirebbe con una LPS (cut-and-paste). Per : non è uguale sia a sia a , quindi vive in o in .
- Stampa:
PRINT_LPS(X,B,i,j): stampa e termina; stampa se uguali, altrimenti , e termina; : stampa , ricorre su , stampa ; : ricorre su ; : su . Un nodo per livello, cala di o : confronti. - Errori tipici: usare i prefissi invece delle sottostringhe per la LPS; scansione per righe crescenti; dimenticare che per si somma e non ; sovrascrivere prima di salvare
prev; stampare i casi base senzareturn.