Memoizzazione di ricorrenze triangolari - conteggio esatto e diagonali costanti
In questa pagina 4
Esercitazione sulla memoizzazione (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 →) con una ricorrenza definita sul triangolo , come quella della sottosequenza palindroma (Allineamento di stringhe e sottosequenza palindroma più lungadue problemi di DP su stringhe con la stessa struttura della LCS. Allineamento ottimo di due stringhe di DNA con matrice di score S e penalità di gap g: C(i,j) = max{C(i-1,j-1) + s(x_i,y_j), C(i-1,j) + g, C(i,j-1) + g}, C(i,0) = i g, C(0,j) = j g; Theta(mn) tempo, spazio Theta(min{m,n}) con un solo vettore se serve solo il costo. Sottosequenza palindroma più lunga (LPS) sullo spazio delle sottostringhe X_(i..j): p(i,j) = 2 + p(i+1,j-1) se x_i = x_j, altrimenti max{p(i+1,j), p(i,j-1)}; scansione per diagonali, n(n-1)/2 confronti.Allineamento di stringhe e sottosequenza palindroma più lunga →), ma con un prodotto invece di un massimo. Lo scopo è doppio: contare esattamente le operazioni di un codice memoizzato analizzando l'albero delle chiamate, e capire come una proprietà della ricorrenza permetta di ridurre di un ordine di grandezza il numero di sottoproblemi.
La ricorrenza
Sia . Per :
Si vuole con il numero esatto di moltiplicazioni tra interi. La cella dipende da tre celle di lunghezza minore (la sottostringa ha lunghezza ; le dipendenze hanno lunghezza , , ): le stesse della LPS.
Il codice memoizzato standard
Il valore è un default ammissibile, perché per ogni (è un prodotto di numeri positivi, a partire da e ).
INIT_M(n)
if n = 1 then return 1 { M(1,1) = 1 }
if n = 2 then return 2 { M(1,2) = 2 }
for i <- 1 to n-1 do
M[i, i] <- 1
M[i, i+1] <- 2
M[n, n] <- 1
for i <- 1 to n-2 do
for j <- i+2 to n do M[i, j] <- 0 { da calcolare }
return REC_M(1, n)
REC_M(i, j)
if M[i, j] = 0 then
M[i, j] <- REC_M(i+1, j-1) * REC_M(i+1, j) * REC_M(i, j-1)
return M[i, j]Correttezza: discende dall'inizializzazione dei casi base e dalla scelta della sentinella.
Costo. INIT_M non esegue moltiplicazioni. Nell'albero delle chiamate di REC_M(1, n):
- le coppie con (cioè e ) sono nodi interni, ciascuno una volta sola: tutte vengono raggiunte dalla radice (si arriva a togliendo caratteri dagli estremi), e alle chiamate successive la cella è già piena. Ogni nodo interno esegue due moltiplicazioni (tre fattori);
- tutte le altre chiamate sono foglie a lavoro nullo (casi base e letture di tabella).
Quindi
(Nella seconda uguaglianza, per fissato il numero di valori di è ; nella terza si pone , che va da a .) Per : moltiplicazioni, nodi interni (controllato eseguendo il codice per ). È un algoritmo .
Un algoritmo migliore: le diagonali sono costanti
Basta scrivere la tabella per (riga , colonna ):
| 1 | 2 | 4 | 32 | 4096 | 536870912 | |
| 1 | 2 | 4 | 32 | 4096 | ||
| 1 | 2 | 4 | 32 | |||
| 1 | 2 | 4 | ||||
| 1 | 2 | |||||
| 1 |
Ogni diagonale ( costante) ha lo stesso valore.
Proposizione. Per ogni , tutti gli elementi con valgono uno stesso numero , dove
Dimostrazione per induzione su . Base: : per tutti gli ; : per tutti gli . Passo: sia e valga la tesi per le diagonali di indice minore di . Un elemento sulla diagonale è il prodotto di (diagonale , valore per l'ipotesi induttiva) e di e (entrambi sulla diagonale , valore ). Quindi vale , indipendentemente da .
(Nelle dispense, nel passo induttivo, il secondo fattore è scritto al posto di : è un refuso.)
La risposta cercata è : non serve più una tabella bidimensionale di elementi, ma un vettore .
INIT_V(n)
if n = 1 then return 1
if n = 2 then return 2
for l <- 3 to n do V[l] <- 0
V[1] <- 1; V[2] <- 2
return REC_V(n)
REC_V(l)
if V[l] = 0 then V[l] <- (REC_V(l-1))^2 * REC_V(l-2)
return V[l]Per l'albero di REC_V(n) ha esattamente un nodo interno per ogni con (le chiamate successive trovano il valore in tabella), ciascuno con due moltiplicazioni (il quadrato e il prodotto per ):
È un algoritmo lineare invece che quadratico: per sono moltiplicazioni invece di . I valori dell'esempio sono : , , , . Il controllo con Python coincide con la tabella completa fino a .
Osservazione sulla taglia dei numeri. Tutti i valori sono potenze di : con , e , cioè (i numeri di Pell). Crescono come : contare ogni moltiplicazione come costo unitario è lecito solo nel modello aritmetico, perché le cifre in gioco crescono esponenzialmente.
Lezione. Prima di impostare una tabella a due indici conviene guardare i valori per piccolo: regolarità come le diagonali costanti riducono lo spazio dei sottoproblemi da a e il lavoro da quadratico a lineare.
Domande d'esame
- (Risposta numerica.) Quanto vale ? E quante moltiplicazioni esegue il codice standard per , e quello con le diagonali? Traccia: ; contro .
- (Quesito aperto.) Perché per analizzare un algoritmo memoizzato si conta il numero di nodi interni dell'albero delle chiamate e non si usa la ricorrenza sul numero di operazioni? Traccia: la ricorrenza conta tutte le espansioni; nel memoizzato ogni sottoistanza non di base è espansa una volta sola e le altre chiamate sono letture di tabella a costo nullo.
- (Dimostrazione.) Provare che le diagonali sono costanti. Traccia: induzione sull'indice di diagonale ; per la cella è il prodotto di una cella sulla diagonale e di due sulla .
Altri esercizi d'esame con memoizzazione e conteggio sull'albero delle chiamate: Esercizio - ricorrenza t(i,j) con calcolo memoizzato e conteggio esatto (compito del 26 giugno 2025, es. 2) e Esercizio - ricorrenza c(i,j) con valore di default e conteggio delle operazioni (simulazione d'esame 2023-24, problema 2).
Versione ripasso
- Ricorrenza. , , per , su . Si vuole e il numero esatto di moltiplicazioni.
- Codice standard.
INIT_M(n): casi base (, ), , , il resto a (valido: tutti i valori sono ), poiREC_M(1,n).REC_M(i,j): se allora prodotto delle tre chiamate; restituisce . - Costo.
INIT_Mnon moltiplica. Nodo interno una volta per ogni coppia con , tutte raggiunte da , due moltiplicazioni ciascuno; il resto sono foglie a lavoro nullo:
Per : moltiplicazioni, nodi interni.
- Diagonali costanti. Per la riga è e ogni riga successiva è la stessa spostata a destra. Tutti gli con valgono , con , , (induzione su : un fattore sulla diagonale e due sulla ). Risposta: .
REC_V(l): if V[l] = 0 then V[l] <- (REC_V(l-1))^2 * REC_V(l-2); return V[l] INIT_V: casi , , , per . Un nodo interno per ogni , due moltiplicazioni: , lineare. Per : contro .
- Valori. , , , . Sono potenze di con esponenti di Pell (): il costo unitario per moltiplicazione vale solo nel modello aritmetico.
- Lezione. Guardare i valori per piccolo prima di costruire la tabella: le regolarità riducono lo spazio dei sottoproblemi da a . Nelle dispense, nel passo induttivo, compare al posto di .
- Domande tipiche: ; per : moltiplicazioni contro .
- Errori tipici: contare tutte le celle come nodi interni (i casi base non moltiplicano); dimenticare che i nodi interni hanno due moltiplicazioni e non una; usare come sentinella un valore che la ricorrenza può produrre; usare la ricorrenza sul numero di operazioni invece dell'albero delle chiamate.