Salta al contenuto
Note per Studenti Esercizio - algoritmo ibrido e valore ottimo della soglia (compito del 27 gennaio 2025, es. 1)

Esercizio - algoritmo ibrido e valore ottimo della soglia (compito del 27 gennaio 2025, es. 1)

Esame
In questa pagina 4

Testo (compito d'esame del 27 gennaio 2025 di Algoritmi per l'Ingegneria, Ing. Informatica UniPD, seconda parte, esercizio 1, 17 punti). Sia nn una potenza di due. Dato un problema Π⊆I×S\Pi \subseteq I \times S, sia AΠ1A^1_\Pi un algoritmo ricorsivo per Π\Pi la cui complessità in tempo T(n)T(n) obbedisce alla ricorrenza T(n)={64n=16 T(n/2)+16 n3n>1.T(n) = \begin{cases} 64 & n = 1 \\ 6\,T(n/2) + 16\,n^3 & n > 1. \end{cases} Sia AΠ2A^2_\Pi un ulteriore algoritmo per Π\Pi di complessità T′(n)=n4T'(n) = n^4.

  1. (a) [4 punti] Per i∈Ii \in I e n0n_0 parametro costante potenza di due, si descriva la struttura dello pseudocodice dell'algoritmo ibrido HYBRID(i,n0)\text{HYBRID}(i, n_0) ottenuto utilizzando AΠ2A^2_\Pi per size(i)≤n0\text{size}(i) \le n_0 e la strategia ricorsiva altrimenti.
  2. (b) [3 punti] Si dimostri induttivamente che T(n)=64 n3T(n) = 64\,n^3.
  3. (c) [6 punti] Si determini la complessità Tn0(n)T_{n_0}(n) dell'algoritmo ibrido.
  4. (d) [4 punti] Si determini il valore del parametro n0n_0 che garantisce la migliore complessità alle costanti del risultante algoritmo ibrido.

Teoria usata: Albero della ricorrenza e formula generale per le ricorrenzeL'albero della ricorrenza ha la forma dell'albero delle chiamate dell'istanza peggiore e T(n) è la somma dei lavori dei nodi. Per la famiglia T(n) = s(n)T(f(n)) + w(n) con base T0 per n <= n0, contando i nodi per livello con le iterate di f e il livello f*(n,n0) si ottiene la formula chiusa T(n) = somma_l [prodotto_j s(f^(j)(n))] w(f^(l)(n)) + [prodotto s] T0, verificata per induzione e applicata a esempi (merge sort, Hanoi, ricorrenze con radice).Albero della ricorrenza e formula generale per le ricorrenze → (in particolare f∗(n,n0)f^\ast(n, n_0) e la taglia delle foglie), Master Theorem - enunciato, casi e applicazioniMaster Theorem per T(n) = aT(n/b) + w(n): si confronta w(n) con la funzione soglia n^(log_b a). Caso 1 (w minore di una potenza polinomiale): Theta(n^(log_b a)); caso 2 (w della stessa taglia): Theta(n^(log_b a) log n); caso 3 (w maggiore e regolare): Theta(w(n)). Prova completa dai livelli dell'albero, versione estesa (parti intere, n0 qualsiasi), esempi, e l'uso scorretto quando nessun caso si applica (w = n log n).Master Theorem - enunciato, casi e applicazioni →, Principio di induzionePer dimostrare una proprietà per ogni n basta il passo base e il passo induttivo da n a n+1.Principio di induzione →.

L'idea dell'ibridazione: un algoritmo ricorsivo asintoticamente migliore (64n364n^3) può essere battuto, sulle istanze piccole, da uno asintoticamente peggiore ma con costanti piccole (n4n^4 batte 64n364n^3 per n<64n < 64). Si ferma allora la ricorsione a una taglia n0n_0 e si passa a A2A^2.

(a) Pseudocodice dell'algoritmo ibrido

Si cambia solo il caso base del codice di A1A^1: invece di fermarsi a n=1n = 1 ci si ferma a n≤n0n \le n_0 e si chiama A2A^2. La ricorrenza di A1A^1 ha s(n)=6s(n) = 6 sottoistanze di taglia n/2n/2 e lavoro w(n)=16n3w(n) = 16n^3 (Divide più Conquer); il resto del codice resta quello di A1A^1, con le chiamate ricorsive a HYBRID.

HYBRID(i, n0)                                 {n0 potenza di due, costante}
n <- size(i)
[B]: if n <= n0 then return A2(i)             // costo n^4
[D]: <i_1, ..., i_6> <- A_D(i)                // Divide di A1: 6 sottoistanze di taglia n/2
[R]: for j <- 1 to 6 do s_j <- HYBRID(i_j, n0)    // il parametro n0 resta fisso
[C]: return A_C(s_1, ..., s_6)                // Conquer di A1

La correttezza segue da quella dei due algoritmi: sulle istanze di base si usa A2A^2 (corretto per ipotesi) e sulle altre la stessa proprietà di sottostruttura di A1A^1.

(b) T(n)=64 n3T(n) = 64\,n^3

Si pone n=2kn = 2^k e si procede per induzione su kk.

  • [B] k=0k = 0: n=1n = 1, T(1)=64=64⋅13T(1) = 64 = 64\cdot 1^3.
  • [HP] T(n/2)=64 (n/2)3T(n/2) = 64\,(n/2)^3 (la tesi per n/2=2k−1n/2 = 2^{k-1}).
  • [TH] T(n)=6 T(n/2)+16n3=6⋅64 n38+16n3=48n3+16n3=64 n3T(n) = 6\,T(n/2) + 16n^3 = 6\cdot 64\,\frac{n^3}{8} + 16n^3 = 48n^3 + 16n^3 = 64\,n^3. □\square

Coerente con il Master Theorem: a=6a = 6, b=2b = 2, soglia nlog⁡26≈n2,58n^{\log_2 6} \approx n^{2{,}58}, w=16n3=Ω(nlog⁡26+ε)w = 16n^3 = \Omega(n^{\log_2 6 + \varepsilon}) con regolarità 6⋅16(n/2)3=34⋅16n36\cdot16(n/2)^3 = \frac34\cdot16n^3, quindi Caso 3: Θ(n3)\Theta(n^3).

(c) La complessità Tn0(n)T_{n_0}(n) dell'algoritmo ibrido

La ricorrenza dipende da due parametri, nn e n0n_0 (con n0≤nn_0 \le n potenze di due): Tn0(n)={n4n≤n06 Tn0(n/2)+16 n3n>n0.T_{n_0}(n) = \begin{cases} n^4 & n \le n_0 \\ 6\,T_{n_0}(n/2) + 16\,n^3 & n > n_0. \end{cases} Formula generale con s=6s = 6, f(n)=n/2f(n) = n/2, w(n)=16n3w(n) = 16n^3, taglia di base n0n_0 e costo di base T0=n04T_0 = n_0^4 (tutte le foglie hanno taglia esattamente n0n_0). Per f(n)=n/2f(n) = n/2 si ha f∗(n,n0)=log⁡2(n/n0)−1f^\ast(n, n_0) = \log_2(n/n_0) - 1: i livelli non di base sono ℓ=0,…,m−1\ell = 0, \dots, m - 1 con m=log⁡2(n/n0)m = \log_2(n/n_0), e le foglie, al livello mm, sono 6m6^m. Dunque Tn0(n)=∑ℓ=0m−16ℓ⋅16(n2ℓ)3+6m n04.T_{n_0}(n) = \sum_{\ell=0}^{m-1} 6^\ell\cdot 16\left(\frac{n}{2^\ell}\right)^3 + 6^{m}\, n_0^4.

Somma dei livelli. 6ℓ(n2ℓ)3=n3(68)ℓ=n3(34)ℓ6^\ell\left(\frac{n}{2^\ell}\right)^3 = n^3\left(\frac{6}{8}\right)^\ell = n^3\left(\frac34\right)^\ell, quindi, con la somma geometrica di ragione 34\frac34, 16 n3∑ℓ=0m−1(34)ℓ=16 n3 1−(3/4)m1−3/4=64 n3(1−(34)m).16\,n^3\sum_{\ell=0}^{m-1}\left(\frac34\right)^\ell = 16\,n^3\,\frac{1 - (3/4)^m}{1 - 3/4} = 64\,n^3\left(1 - \left(\tfrac34\right)^m\right).

Riscrittura in potenze di nn e n0n_0. Con L=log⁡23L = \log_2 3 si ha log⁡234=L−2\log_2\frac34 = L - 2, e m=log⁡2nn0m = \log_2\frac n{n_0}, quindi (34)m=(nn0)L−2,6m=(nn0)log⁡26=(nn0)1+L.\left(\tfrac34\right)^m = \left(\tfrac{n}{n_0}\right)^{L - 2}, \qquad 6^m = \left(\tfrac{n}{n_0}\right)^{\log_2 6} = \left(\tfrac{n}{n_0}\right)^{1 + L}. Allora:

  • 64 n3(nn0)L−2=64 n1+L n02−L64\,n^3\left(\frac{n}{n_0}\right)^{L-2} = 64\,n^{1+L}\,n_0^{2-L} (perché n3⋅nL−2=n1+Ln^3\cdot n^{L-2} = n^{1+L});
  • 6m n04=n1+L n0−1−L n04=n1+L n03−L6^m\,n_0^4 = n^{1+L}\,n_0^{-1-L}\,n_0^4 = n^{1+L}\,n_0^{3-L}.

Sommando, con n1+L=nlog⁡26n^{1+L} = n^{\log_2 6}: Tn0(n)=64 n3−64 nlog⁡26 n02−log⁡23+nlog⁡26 n03−log⁡23=64 n3+c(n0) nlog⁡26,T_{n_0}(n) = 64\,n^3 - 64\,n^{\log_2 6}\,n_0^{2-\log_2 3} + n^{\log_2 6}\,n_0^{3-\log_2 3} = 64\,n^3 + c(n_0)\,n^{\log_2 6}, c(n0)=−64 n0 2−log⁡23+n0 3−log⁡23.c(n_0) = -64\,n_0^{\,2-\log_2 3} + n_0^{\,3-\log_2 3}.

Controlli. Per n0=1n_0 = 1 (A2A^2 a taglia 11 costa 11 invece di 6464): c(1)=−64+1=−63c(1) = -64 + 1 = -63, quindi T1(n)=64n3−63 nlog⁡26T_1(n) = 64n^3 - 63\,n^{\log_2 6}; per n=1n = 1 dà 11. La formula coincide con la ricorrenza per ogni n=2k≤215n = 2^k \le 2^{15} e ogni n0=2j≤nn_0 = 2^j \le n (verificato con Python). Per n0=64n_0 = 64 si ha c(64)=642−L(−64+64)=0c(64) = 64^{2-L}(-64 + 64) = 0 e T64(n)=64n3T_{64}(n) = 64n^3: coincide con A1A^1, perché a taglia 6464 i due algoritmi costano uguale (644=64⋅64364^4 = 64\cdot 64^3).

(d) Il valore ottimo di n0n_0

Il coefficiente del termine dominante, 64 n364\,n^3, non dipende da n0n_0: l'ibridazione può agire solo sulla costante c(n0)c(n_0) del termine di ordine inferiore nlog⁡26n^{\log_2 6} (esponente ≈2,58<3\approx 2{,}58 < 3). Il costo è minimo dove c(n0)c(n_0) è minimo (più negativo). Si studia il segno della derivata: d cd n0=−64(2−L) n01−L+(3−L) n02−L=n0 1−L[−64(2−L)+(3−L) n0].\frac{d\,c}{d\,n_0} = -64(2 - L)\,n_0^{1-L} + (3 - L)\,n_0^{2-L} = n_0^{\,1-L}\Bigl[-64(2-L) + (3-L)\,n_0\Bigr]. Poiché n01−L>0n_0^{1-L} > 0, il segno è quello della parentesi, che è negativa per n0n_0 piccolo, si annulla in n0∗=64 (2−log⁡23)3−log⁡23≈18,77n_0^\ast = \frac{64\,(2 - \log_2 3)}{3 - \log_2 3} \approx 18{,}77 e poi è positiva: cc decresce fino a n0∗n_0^\ast e cresce dopo. Il minimo tra le potenze di due sta in una delle due più vicine a 18,7718{,}77, cioè 1616 o 3232. Per confrontarle si usa 162−L=24(2−L)=2834=2568116^{2-L} = 2^{4(2-L)} = \frac{2^8}{3^4} = \frac{256}{81} e 322−L=25(2−L)=21035=102424332^{2-L} = 2^{5(2-L)} = \frac{2^{10}}{3^5} = \frac{1024}{243}; il secondo termine di cc è n0⋅n02−Ln_0\cdot n_0^{2-L}: c(16)=162−L(−64+16)=−48⋅25681≈−151,70,c(32)=322−L(−64+32)=−32⋅1024243≈−134,85.c(16) = 16^{2-L}(-64 + 16) = -48\cdot\frac{256}{81} \approx -151{,}70, \qquad c(32) = 32^{2-L}(-64 + 32) = -32\cdot\frac{1024}{243} \approx -134{,}85. Poiché c(16)<c(32)c(16) < c(32), la soglia migliore è n0=16,T16(n)≃64 n3−151,70 nlog⁡26(n≥16).\boxed{n_0 = 16, \qquad T_{16}(n) \simeq 64\,n^3 - 151{,}70\,n^{\log_2 6} \quad (n \ge 16).}

Errore nella soluzione del corso. La soluzione ufficiale riporta c(32)≃−202,27c(32) \simeq -202{,}27 e conclude n0=32n_0 = 32 con T32(n)≃64n3−202,27 nlog⁡26T_{32}(n) \simeq 64n^3 - 202{,}27\,n^{\log_2 6}. Ma il secondo termine di c(32)c(32) è 323−L=32⋅322−L32^{3-L} = 32\cdot 32^{2-L}, non 16⋅322−L16\cdot 32^{2-L}: la soluzione ha riusato il fattore 1616 di c(16)c(16), e così −64⋅1024243+16⋅1024243=−48⋅1024243≃−202,27-64\cdot\frac{1024}{243} + 16\cdot\frac{1024}{243} = -48\cdot\frac{1024}{243} \simeq -202{,}27 invece del corretto −32⋅1024243≃−134,85-32\cdot\frac{1024}{243} \simeq -134{,}85. Con il valore giusto vince 1616. Lo conferma la ricorrenza valutata direttamente per n=214n = 2^{14}:

n0n_0 4 8 16 32 64 128
c(n0)c(n_0) −106,67-106{,}67 −132,74-132{,}74 −151,70\mathbf{-151{,}70} −134,85-134{,}85 00 479,46479{,}46
Tn0(214)⋅10−14T_{n_0}(2^{14})\cdot 10^{-14} 2,7312{,}731 2,7112{,}711 2,696\mathbf{2{,}696} 2,7092{,}709 2,8152{,}815 3,1903{,}190

Il guadagno è modesto: a n=214n = 2^{14} il costo scende da 64n3=2,815⋅101464n^3 = 2{,}815\cdot10^{14} (puro A1A^1, o n0=64n_0 = 64) a 2,696⋅10142{,}696\cdot10^{14}, circa il 4,2%4{,}2\%, perché agisce solo su un termine di ordine inferiore. Per n0>64n_0 > 64 l'ibrido è peggiore di A1A^1 (c>0c > 0).

python
import math
from functools import lru_cache

@lru_cache(None)
def Th(n, n0):
    return n**4 if n <= n0 else 6 * Th(n // 2, n0) + 16 * n**3

L = math.log2(3)
c = lambda n0: -64 * n0**(2 - L) + n0**(3 - L)

n = 2**14
for j in range(2, 8):
    n0 = 2**j
    pred = 64 * n**3 + c(n0) * n**math.log2(6)
    assert abs(Th(n, n0) - pred) < 1e-9 * pred
print(min(range(15), key=lambda j: Th(n, 2**j)))   # 4, cioe' n0 = 16
print(round(c(16), 2), round(c(32), 2))            # -151.7 -134.85

Versione ripasso

Testo. T(1)=64T(1)=64, T(n)=6T(n/2)+16n3T(n)=6T(n/2)+16n^3; A2A^2 costa n4n^4. (a) pseudocodice di HYBRID(i,n0)(i,n_0), A2A^2 per size≤n0\text{size}\le n_0 [4 punti]; (b) T(n)=64n3T(n)=64n^3 per induzione [3]; (c) Tn0(n)T_{n_0}(n) [6]; (d) n0n_0 ottimo [4].

Teoria: Albero della ricorrenza e formula generale per le ricorrenzeL'albero della ricorrenza ha la forma dell'albero delle chiamate dell'istanza peggiore e T(n) è la somma dei lavori dei nodi. Per la famiglia T(n) = s(n)T(f(n)) + w(n) con base T0 per n <= n0, contando i nodi per livello con le iterate di f e il livello f*(n,n0) si ottiene la formula chiusa T(n) = somma_l [prodotto_j s(f^(j)(n))] w(f^(l)(n)) + [prodotto s] T0, verificata per induzione e applicata a esempi (merge sort, Hanoi, ricorrenze con radice).Albero della ricorrenza e formula generale per le ricorrenze →, Master Theorem - enunciato, casi e applicazioniMaster Theorem per T(n) = aT(n/b) + w(n): si confronta w(n) con la funzione soglia n^(log_b a). Caso 1 (w minore di una potenza polinomiale): Theta(n^(log_b a)); caso 2 (w della stessa taglia): Theta(n^(log_b a) log n); caso 3 (w maggiore e regolare): Theta(w(n)). Prova completa dai livelli dell'albero, versione estesa (parti intere, n0 qualsiasi), esempi, e l'uso scorretto quando nessun caso si applica (w = n log n).Master Theorem - enunciato, casi e applicazioni →.

  • (a) Si cambia solo il caso base: if n <= n0 then return A2(i); poi Divide di A1A^1 (6 sottoistanze di taglia n/2n/2), 6 chiamate HYBRID(ij,n0)(i_j,n_0), Conquer di A1A^1.
  • (b) Base T(1)=64T(1)=64; passo 6⋅64n38+16n3=48n3+16n3=64n36\cdot64\frac{n^3}8+16n^3=48n^3+16n^3=64n^3.
  • (c) Tn0(n)=n4T_{n_0}(n)=n^4 (n≤n0n\le n_0), 6Tn0(n/2)+16n36T_{n_0}(n/2)+16n^3; f∗=log⁡2nn0−1f^\ast=\log_2\frac n{n_0}-1, m=log⁡2nn0m=\log_2\frac n{n_0}, foglie 6m6^m di costo n04n_0^4: Tn0(n)=16n3∑ℓ<m(34)ℓ+6mn04=64n3(1−(34)m)+6mn04.T_{n_0}(n)=16n^3\sum_{\ell<m}\left(\tfrac34\right)^\ell+6^mn_0^4=64n^3\left(1-(\tfrac34)^m\right)+6^mn_0^4. Con L=log⁡23L=\log_2 3: Tn0(n)=64n3+c(n0) nlog⁡26T_{n_0}(n)=64n^3+c(n_0)\,n^{\log_2 6}, c(n0)=−64n02−L+n03−Lc(n_0)=-64n_0^{2-L}+n_0^{3-L}. Controlli: c(1)=−63c(1)=-63; c(64)=0c(64)=0 (=A1=A^1).
  • (d) Il termine 64n364n^3 non dipende da n0n_0: si minimizza cc. c′=n01−L[−64(2−L)+(3−L)n0]c'=n_0^{1-L}[-64(2-L)+(3-L)n_0], nullo in n0∗=64(2−L)3−L≈18,77n_0^\ast=\frac{64(2-L)}{3-L}\approx18{,}77. Potenze di due vicine: c(16)=−48⋅25681≈−151,70c(16)=-48\cdot\frac{256}{81}\approx-151{,}70, c(32)=−32⋅1024243≈−134,85c(32)=-32\cdot\frac{1024}{243}\approx-134{,}85: n0=16n_0=16, T16≃64n3−151,70 nlog⁡26T_{16}\simeq64n^3-151{,}70\,n^{\log_2 6}.
  • Errore del corso: la soluzione riporta c(32)≃−202,27c(32)\simeq-202{,}27 (−48⋅1024243-48\cdot\frac{1024}{243}, con 1616 al posto di 3232 nel secondo termine) e sceglie n0=32n_0=32. Con la ricorrenza diretta per n=214n=2^{14}: T16<T32T_{16}<T_{32}.
  • Errori tipici: cercare n0n_0 ottimo con un solo confronto invece dei due vicini di n0∗n_0^\ast; sbagliare n03−L=n0⋅n02−Ln_0^{3-L}=n_0\cdot n_0^{2-L}; porre T0=T(1)T_0=T(1) invece di n04n_0^4.
  • Tabella di controllo (n=214n=2^{14}): c(n0)=−106,67,−132,74,−151,70,−134,85,0,479,46c(n_0)=-106{,}67,-132{,}74,-151{,}70,-134{,}85,0,479{,}46 per n0=4,8,16,32,64,128n_0=4,8,16,32,64,128; Tn0(214)⋅10−14=2,731; 2,711; 2,696; 2,709; 2,815; 3,190T_{n_0}(2^{14})\cdot10^{-14}=2{,}731;\,2{,}711;\,\mathbf{2{,}696};\,2{,}709;\,2{,}815;\,3{,}190. Guadagno su A1A^1 (n0=64n_0=64): circa 4,2%4{,}2\%, perché agisce solo su un termine di ordine inferiore; per n0>64n_0>64 l'ibrido peggiora (c>0c>0).

Esercizi su questo argomento

Teoria collegata