Esercizio - ricorrenza t(i,j) con calcolo memoizzato e conteggio esatto (compito del 26 giugno 2025, es. 2)
Fonte: compito di Algoritmi per l'Ingegneria, Ing. Informatica UniPD, 26 giugno 2025, seconda parte, esercizio 2 (16 punti).
Testo
Dato un vettore di interi positivi, si consideri la seguente ricorrenza, definita per :
- (a) [10 punti] Si fornisca lo pseudocodice della coppia di procedure
INIT_T(x)eREC_T(x, i, j)che, dato in ingresso il vettore , eseguano il calcolo memoizzato di . - (b) [6 punti] Si determini il numero esatto di confronti e somme tra interi effettuato dall'algoritmo memoizzato.
Soluzione
Lettura della ricorrenza
Si pensi a una tabella con per riga e per colonna.
- Prima riga (): .
- Ultima colonna (): , cioè scende dal basso verso l'alto leggendo da sinistra a destra: , , ..., . Nell'angolo i due casi base danno lo stesso valore , quindi la definizione è coerente.
- Celle interne ( e ): dipendono da tre vicine, quella in alto a destra , quella sopra e quella a destra . Il valore richiesto è l'angolo in basso a sinistra, .
Nelle chiamate ricorsive diminuisce o aumenta (o entrambi): la quantità scende di almeno a ogni livello e i casi base (prima riga, ultima colonna) sono raggiunti, quindi la ricorsione termina.
(a) Valore di default
Tutti i valori della tabella sono strettamente positivi: i casi base sono componenti di (positive), e un valore interno è il massimo tra un numero positivo e una somma di numeri positivi (induzione sull'ordine di valutazione). Quindi è un valore di default ammissibile per le celle ancora da calcolare, perché la ricorrenza non lo può mai produrre.
INIT_T(x)
n <- x.length
if n = 1 then return x_1 { caso base: t(1,1) = x_1 }
for i <- 1 to n do
T[1, i] <- x_i { prima riga }
T[n+1-i, n] <- x_i { ultima colonna: T[i', n] = x_{n+1-i'} }
for i <- 2 to n do
for j <- 1 to n-1 do T[i, j] <- 0 { da calcolare }
return REC_T(x, n, 1)
REC_T(x, i, j)
if T[i, j] = 0 then
a <- REC_T(x, i-1, j)
b <- REC_T(x, i, j+1)
c <- REC_T(x, i-1, j+1)
if c >= a + b then T[i, j] <- c
else T[i, j] <- a + b
return T[i, j]La procedura INIT_T risolve direttamente il caso , riempie la prima riga e l'ultima colonna con i casi base, mette in tutte le altre celle e chiama REC_T sull'istanza principale. La procedura REC_T calcola solo le celle non di base, e memorizza ciascun valore appena calcolato in per non ricalcolarlo. Ogni cella è letta (cioè restituita dalla chiamata) soltanto dopo che la chiamata che la calcola è terminata, quindi contiene sempre il valore definitivo. La correttezza discende dalla ricorrenza, dai casi base e dal valore di default.
Esempio (, )
Le celle di base: riga uguale a , colonna uguale a dall'alto (, , , ). Le altre si calcolano con la ricorrenza, ad esempio:
- ;
- ;
- .
Tabella completa :
| 1 | 2 | 3 | 4 | |
|---|---|---|---|---|
| 1 | 2 | 1 | 3 | 2 |
| 2 | 9 | 7 | 6 | 3 |
| 3 | 23 | 14 | 7 | 1 |
| 4 | 46 | 23 | 9 | 2 |
Il risultato è . L'ordine in cui le celle interne vengono completate da REC_T(x, 4, 1) è : le righe vengono completate una per volta, ciascuna da destra a sinistra. In tutto le chiamate sono (ogni nodo interno ne fa tre). Il codice è stato controllato in Python contro la ricorrenza diretta su vettori casuali, con .
(b) Numero esatto di operazioni
INIT_T non esegue operazioni aritmetiche né confronti tra interi (solo assegnamenti). Si analizza l'albero delle chiamate di REC_T(x, n, 1), non la ricorrenza (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 →):
- Tutte le celle non di base con e vengono raggiunte dalla radice: per arrivare a si fanno passi verso l'alto e verso destra, e tutte le celle intermedie hanno e , quindi sono celle non di base e vengono espanse.
- Ognuna compare una sola volta come nodo interno: dopo il primo calcolo la tabella contiene un valore positivo, e tutte le altre chiamate sulla stessa cella restituiscono subito il valore (foglie a lavoro nullo).
- Il lavoro di un nodo interno è : una somma () e un confronto ().
Le celle non di base sono righe per colonne, quindi
Per : operazioni (nove somme e nove confronti); per : . Controllato eseguendo il codice con i contatori per .
Osservazione: simmetria rispetto all'antidiagonale
La tabella è simmetrica rispetto all'antidiagonale: (nell'esempio , , ). Prova. Si pone e si mostra che soddisfa la stessa ricorrenza di con gli stessi casi base:
- ;
- ;
- per e si applica la ricorrenza di in (che è una cella non di base, perché e ): , cioè , la ricorrenza di .
Per induzione sull'ordine di valutazione, . Sfruttandola, ogni volta che si calcola una cella fuori dall'antidiagonale si può scrivere anche la sua simmetrica, dimezzando la costante del termine (miglioramento non richiesto).
Versione ripasso
Testo. vettore di interi positivi; se ; se ; se e . (a) [10 punti] INIT_T(x) e REC_T(x,i,j) per il calcolo memoizzato di . (b) [6 punti] Numero esatto di confronti e somme.
- Struttura: prima riga ; ultima colonna dall'alto (cioè ); celle interne dipendono da , , . Si vuole l'angolo in basso a sinistra . Terminazione: scende a ogni chiamata.
- Default : tutti i valori sono positivi (casi base positivi, massimo di positivi e somme di positivi).
INIT_T(x)
n <- x.length; if n = 1 then return x_1
for i <- 1 to n do T[1,i] <- x_i; T[n+1-i,n] <- x_i
for i <- 2 to n do for j <- 1 to n-1 do T[i,j] <- 0
return REC_T(x, n, 1)
REC_T(x, i, j)
if T[i,j] = 0 then
a <- REC_T(x,i-1,j); b <- REC_T(x,i,j+1); c <- REC_T(x,i-1,j+1)
if c >= a + b then T[i,j] <- c else T[i,j] <- a + b
return T[i,j]- Esempio : , , ; . Tabella (righe ): , , , . Ordine di completamento delle celle interne: ; chiamate.
- Costo (albero delle chiamate, non la ricorrenza):
INIT_Tnon fa operazioni; ogni cella con , è raggiunta da (si va su e a destra tra celle non di base) ed è nodo interno una volta, con lavoro (una somma e un confronto):
- Simmetria: . Prova: ha gli stessi casi base e soddisfa la stessa ricorrenza, quindi per induzione. Si potrebbe calcolare metà tabella.
- Errori tipici: default senza giustificare la positività; leggere l'ultima colonna come invece di ; dimenticare che ogni nodo interno ha una somma e un confronto; contare nodi interni (le celle di base non lavorano); analizzare con una ricorrenza invece dell'albero.