Salta al contenuto
Note per Studenti Esercizio - ricorrenza t(i,j) con calcolo memoizzato e conteggio esatto (compito del 26 giugno 2025, es. 2)

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 xx di n≥1n \ge 1 interi positivi, si consideri la seguente ricorrenza, definita per 1≤i,j≤n1 \le i, j \le n:

t(i,j)={xji=1xn+1−ij=nmax⁡{ t(i−1,j+1),  t(i−1,j)+t(i,j+1) }(1<i≤n) ∧ (1≤j<n)t(i, j) = \begin{cases} x_j & i = 1 \\ x_{n+1-i} & j = n \\ \max\{\, t(i-1, j+1),\ \ t(i-1, j) + t(i, j+1) \,\} & (1 < i \le n) \ \land \ (1 \le j < n) \end{cases}

  • (a) [10 punti] Si fornisca lo pseudocodice della coppia di procedure INIT_T(x) e REC_T(x, i, j) che, dato in ingresso il vettore xx, eseguano il calcolo memoizzato di t(n,1)t(n, 1).
  • (b) [6 punti] Si determini il numero esatto T(n)T(n) di confronti e somme tra interi effettuato dall'algoritmo memoizzato.

Soluzione

Lettura della ricorrenza

Si pensi a una tabella n×nn \times n con ii per riga e jj per colonna.

  • Prima riga (i=1i = 1): t(1,j)=xjt(1, j) = x_j.
  • Ultima colonna (j=nj = n): t(i,n)=xn+1−it(i, n) = x_{n+1-i}, cioè scende dal basso verso l'alto leggendo xx da sinistra a destra: t(n,n)=x1t(n, n) = x_1, t(n−1,n)=x2t(n-1, n) = x_2, ..., t(1,n)=xnt(1, n) = x_n. Nell'angolo (1,n)(1, n) i due casi base danno lo stesso valore xnx_n, quindi la definizione è coerente.
  • Celle interne (2≤i≤n2 \le i \le n e 1≤j≤n−11 \le j \le n - 1): dipendono da tre vicine, quella in alto a destra (i−1,j+1)(i-1, j+1), quella sopra (i−1,j)(i-1, j) e quella a destra (i,j+1)(i, j+1). Il valore richiesto è l'angolo in basso a sinistra, t(n,1)t(n, 1).

Nelle chiamate ricorsive ii diminuisce o jj aumenta (o entrambi): la quantità i+(n−j)i + (n - j) scende di almeno 11 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 xx (positive), e un valore interno è il massimo tra un numero positivo e una somma di numeri positivi (induzione sull'ordine di valutazione). Quindi 00 è 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 n=1n = 1, riempie la prima riga e l'ultima colonna con i casi base, mette 00 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 T[i,j]T[i, j] 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 (n=4n = 4, x=(2,1,3,2)x = (2, 1, 3, 2))

Le celle di base: riga 11 uguale a xx, colonna 44 uguale a (2,3,1,2)(2, 3, 1, 2) dall'alto (t(1,4)=x4t(1,4) = x_4, t(2,4)=x3t(2,4) = x_3, t(3,4)=x2t(3,4) = x_2, t(4,4)=x1t(4,4) = x_1). Le altre si calcolano con la ricorrenza, ad esempio:

  • t(2,3)=max⁡{t(1,4), t(1,3)+t(2,4)}=max⁡{2, 3+3}=6t(2, 3) = \max\{t(1, 4),\ t(1, 3) + t(2, 4)\} = \max\{2,\ 3 + 3\} = 6;
  • t(2,2)=max⁡{t(1,3), t(1,2)+t(2,3)}=max⁡{3, 1+6}=7t(2, 2) = \max\{t(1, 3),\ t(1, 2) + t(2, 3)\} = \max\{3,\ 1 + 6\} = 7;
  • t(2,1)=max⁡{t(1,2), t(1,1)+t(2,2)}=max⁡{1, 2+7}=9t(2, 1) = \max\{t(1, 2),\ t(1, 1) + t(2, 2)\} = \max\{1,\ 2 + 7\} = 9.

Tabella completa t(i,j)t(i, j):

i\ji \backslash j 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 è t(4,1)=46t(4, 1) = 46. L'ordine in cui le 9=(n−1)29 = (n-1)^2 celle interne vengono completate da REC_T(x, 4, 1) è (2,3),(2,2),(2,1),(3,3),(3,2),(3,1),(4,3),(4,2),(4,1)(2,3), (2,2), (2,1), (3,3), (3,2), (3,1), (4,3), (4,2), (4,1): le righe vengono completate una per volta, ciascuna da destra a sinistra. In tutto le chiamate sono 28=1+3⋅928 = 1 + 3 \cdot 9 (ogni nodo interno ne fa tre). Il codice è stato controllato in Python contro la ricorrenza diretta su 300300 vettori casuali, con n≤9n \le 9.

(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 (i,j)(i, j) con 2≤i≤n2 \le i \le n e 1≤j≤n−11 \le j \le n - 1 vengono raggiunte dalla radice: per arrivare a (i′,j′)(i', j') si fanno n−i′n - i' passi verso l'alto e j′−1j' - 1 verso destra, e tutte le celle intermedie hanno i≥i′≥2i \ge i' \ge 2 e j≤j′≤n−1j \le j' \le n - 1, 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 è 22: una somma (a+ba + b) e un confronto (c≥a+bc \ge a + b).

Le celle non di base sono (n−1)(n - 1) righe per (n−1)(n - 1) colonne, quindi

T(n)=∑i=2n∑j=1n−12=2(n−1)2.T(n) = \sum_{i=2}^{n} \sum_{j=1}^{n-1} 2 = 2(n-1)^2.

Per n=4n = 4: 1818 operazioni (nove somme e nove confronti); per n=1n = 1: 00. Controllato eseguendo il codice con i contatori per n≤9n \le 9.

Osservazione: simmetria rispetto all'antidiagonale

La tabella è simmetrica rispetto all'antidiagonale: t(i,j)=t(n+1−j, n+1−i)t(i, j) = t(n+1-j,\, n+1-i) (nell'esempio t(2,1)=t(4,3)=9t(2, 1) = t(4, 3) = 9, t(3,1)=t(4,2)=23t(3, 1) = t(4, 2) = 23, t(2,2)=t(3,3)=7t(2, 2) = t(3, 3) = 7). Prova. Si pone u(i,j)=t(n+1−j,n+1−i)u(i, j) = t(n+1-j, n+1-i) e si mostra che uu soddisfa la stessa ricorrenza di tt con gli stessi casi base:

  • u(1,j)=t(n+1−j,n)=xn+1−(n+1−j)=xju(1, j) = t(n+1-j, n) = x_{n+1-(n+1-j)} = x_j;
  • u(i,n)=t(1,n+1−i)=xn+1−iu(i, n) = t(1, n+1-i) = x_{n+1-i};
  • per 1<i≤n1 < i \le n e 1≤j<n1 \le j < n si applica la ricorrenza di tt in (n+1−j, n+1−i)(n+1-j,\, n+1-i) (che è una cella non di base, perché 1<n+1−j≤n1 < n+1-j \le n e 1≤n+1−i<n1 \le n+1-i < n): u(i,j)=max⁡{t(n−j,n+2−i), t(n−j,n+1−i)+t(n+1−j,n+2−i)}u(i, j) = \max\{t(n-j, n+2-i),\ t(n-j, n+1-i) + t(n+1-j, n+2-i)\}, cioè max⁡{u(i−1,j+1), u(i,j+1)+u(i−1,j)}\max\{u(i-1, j+1),\ u(i, j+1) + u(i-1, j)\}, la ricorrenza di tt.

Per induzione sull'ordine di valutazione, u=tu = t. Sfruttandola, ogni volta che si calcola una cella fuori dall'antidiagonale si può scrivere anche la sua simmetrica, dimezzando la costante del termine n2n^2 (miglioramento non richiesto).

Versione ripasso

Testo. xx vettore di n≥1n \ge 1 interi positivi; t(i,j)=xjt(i,j) = x_j se i=1i = 1; xn+1−ix_{n+1-i} se j=nj = n; max⁡{t(i−1,j+1), t(i−1,j)+t(i,j+1)}\max\{t(i-1,j+1),\ t(i-1,j) + t(i,j+1)\} se 1<i≤n1 < i \le n e 1≤j<n1 \le j < n. (a) [10 punti] INIT_T(x) e REC_T(x,i,j) per il calcolo memoizzato di t(n,1)t(n,1). (b) [6 punti] Numero esatto T(n)T(n) di confronti e somme.

  • Struttura: prima riga =x= x; ultima colonna dall'alto xn,xn−1,…,x1x_n, x_{n-1}, \dots, x_1 (cioè T[i,n]=xn+1−iT[i,n] = x_{n+1-i}); celle interne dipendono da (i−1,j+1)(i-1,j+1), (i−1,j)(i-1,j), (i,j+1)(i,j+1). Si vuole l'angolo in basso a sinistra (n,1)(n,1). Terminazione: i+(n−j)i + (n - j) scende a ogni chiamata.
  • Default 00: 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 x=(2,1,3,2)x = (2,1,3,2): t(2,3)=max⁡{2, 3+3}=6t(2,3) = \max\{2,\ 3 + 3\} = 6, t(2,2)=max⁡{3, 1+6}=7t(2,2) = \max\{3,\ 1 + 6\} = 7, t(2,1)=max⁡{1, 2+7}=9t(2,1) = \max\{1,\ 2 + 7\} = 9; t(4,1)=46t(4,1) = 46. Tabella (righe i=1..4i = 1..4): (2,1,3,2)(2,1,3,2), (9,7,6,3)(9,7,6,3), (23,14,7,1)(23,14,7,1), (46,23,9,2)(46,23,9,2). Ordine di completamento delle celle interne: (2,3),(2,2),(2,1),(3,3),(3,2),(3,1),(4,3),(4,2),(4,1)(2,3), (2,2), (2,1), (3,3), (3,2), (3,1), (4,3), (4,2), (4,1); 28=1+3⋅928 = 1 + 3 \cdot 9 chiamate.
  • Costo (albero delle chiamate, non la ricorrenza): INIT_T non fa operazioni; ogni cella con 2≤i≤n2 \le i \le n, 1≤j≤n−11 \le j \le n - 1 è raggiunta da (n,1)(n,1) (si va su e a destra tra celle non di base) ed è nodo interno una volta, con lavoro 22 (una somma e un confronto):

T(n)=∑i=2n∑j=1n−12=2(n−1)2(18 per n=4, 0 per n=1).T(n) = \sum_{i=2}^{n}\sum_{j=1}^{n-1} 2 = 2(n-1)^2 \quad (18 \text{ per } n = 4,\ 0 \text{ per } n = 1).

  • Simmetria: t(i,j)=t(n+1−j,n+1−i)t(i,j) = t(n+1-j, n+1-i). Prova: u(i,j)=t(n+1−j,n+1−i)u(i,j) = t(n+1-j, n+1-i) ha gli stessi casi base e soddisfa la stessa ricorrenza, quindi u=tu = t per induzione. Si potrebbe calcolare metà tabella.
  • Errori tipici: default 00 senza giustificare la positività; leggere l'ultima colonna come xix_i invece di xn+1−ix_{n+1-i}; dimenticare che ogni nodo interno ha una somma e un confronto; contare n2n^2 nodi interni (le celle di base non lavorano); analizzare con una ricorrenza invece dell'albero.

Teoria collegata