Salta al contenuto
Note per Studenti Albero della ricorrenza e formula generale per le ricorrenze

Albero della ricorrenza e formula generale per le ricorrenze

In questa pagina 7

Lo srotolamento di Divide and conquer - proprietà di sottostruttura e albero delle chiamateUn algoritmo divide and conquer nasce da una proprietà di sottostruttura (funzioni Divide e Conquer, base n0) con sottoistanze di taglia strettamente minore; l'albero delle chiamate ne descrive l'esecuzione, e il D&C non ha memoria (ripete le sottoistanze uguali). Esempio completo: MAX di un array, con correttezza per induzione, ricorrenza e conteggio esatto n-1 tramite il teorema sugli alberi binari pieni.Divide and conquer - proprietà di sottostruttura e albero delle chiamate → funziona caso per caso. Qui lo si organizza in una formula generale: leggendo l'albero della ricorrenza livello per livello si ricava T(n)T(n) per un'intera famiglia di algoritmi D&C.

Albero delle chiamate e albero della ricorrenza

A ogni esecuzione di un algoritmo D&C corrisponde un albero delle chiamate. Quando si studia la complessità al caso peggiore con una ricorrenza, si associa l'albero della ricorrenza: ha la stessa forma dell'albero delle chiamate dell'istanza peggiore di taglia nn, e nei nodi si scrivono:

  • per un nodo interno: la taglia della sottoistanza (convenzionalmente dentro il nodo) e il costo complessivo di ADA_D e ACA_C su quell'istanza (di fianco);
  • per una foglia: la taglia di base e il costo della risoluzione diretta.

Il costo cumulativo di Divide e Conquer per un'istanza non di base, o il costo della risoluzione diretta per una di base, si chiama lavoro (work). La complessità T(n)T(n) si ottiene sommando tutti i lavori dei nodi: l'albero elenca i contributi dovuti alla gerarchia di istanze generate dall'istanza principale. Un esempio completo è l'albero per MAX in Divide and conquer - proprietà di sottostruttura e albero delle chiamateUn algoritmo divide and conquer nasce da una proprietà di sottostruttura (funzioni Divide e Conquer, base n0) con sottoistanze di taglia strettamente minore; l'albero delle chiamate ne descrive l'esecuzione, e il D&C non ha memoria (ripete le sottoistanze uguali). Esempio completo: MAX di un array, con correttezza per induzione, ricorrenza e conteggio esatto n-1 tramite il teorema sugli alberi binari pieni.Divide and conquer - proprietà di sottostruttura e albero delle chiamate →.

La famiglia di ricorrenze

Siano s,f:N→Ns, f : \mathbb{N} \to \mathbb{N} e w:N→R+∪{0}w : \mathbb{N} \to \mathbb{R}^+ \cup \{0\} funzioni non decrescenti:

  • s(n)s(n) è il numero di sottoistanze generate da un'istanza (non di base) di taglia nn;
  • f(n)f(n) è la taglia delle sottoistanze generate; deve essere una contrazione, f(n)<nf(n) < n per ogni taglia non di base (altrimenti la ricorsione non termina);
  • w(n)w(n) è il lavoro (divide più conquer) di un'istanza non di base di taglia nn; con costi unitari o nulli w(n)∈Nw(n) \in \mathbb{N}.

Si restringe il paradigma al caso in cui ogni istanza di taglia nn genera sempre s(n)s(n) sottoistanze, tutte di taglia f(n)f(n): il numero può dipendere da nn ma non dall'istanza. Le istanze di base hanno taglia ≤n0\le n_0 e T0T_0 è un limite superiore al tempo della risoluzione diretta. Il metacodice è lo stesso di prima con k←s(n)k \leftarrow s(n), e la ricorrenza diventa T(n)={T0n≤n0s(n)⋅T(f(n))+w(n)n>n0,(∗)T(n) = \begin{cases} T_0 & n \le n_0 \\ s(n)\cdot T\bigl(f(n)\bigr) + w(n) & n > n_0, \end{cases} \tag{$\ast$} con w(n)=TAD(n)+TAC(n)w(n) = T_{A_D}(n) + T_{A_C}(n) il lavoro dell'istanza (peggiore, nell'analisi del caso peggiore) di taglia nn.

Osservazioni. Poiché s,f,ws, f, w sono non decrescenti, lo è anche TT. Se le quantità di (∗)(\ast) sono solo limitazioni superiori di numero di sottoistanze, taglie o lavoro, la soluzione T(n)T(n) è una limitazione superiore alla complessità.

Esempio: merge sort. Per n=2kn = 2^k e con costo unitario solo per i confronti tra chiavi:

La ricorrenza è T(n)=0T(n) = 0 per n=1n = 1 e T(n)=2T(n/2)+n−1T(n) = 2T(n/2) + n - 1 per n>1n > 1.

Le iterate di ff

Le iterate danno le taglie delle sottoistanze ai vari livelli di ricorsione: f(0)(n)=n,f(ℓ)(n)=f(f(ℓ−1)(n))(ℓ>0),f^{(0)}(n) = n, \qquad f^{(\ell)}(n) = f\bigl(f^{(\ell-1)}(n)\bigr) \quad (\ell > 0), cioè ff applicata ℓ\ell volte. Casi notevoli, per ricavare l'espressione esplicita:

f(n)f(n) forma di nn f(ℓ)(n)f^{(\ell)}(n)
n/2n/2 n=2kn = 2^k n/2ℓn/2^\ell
n/bn/b n=bkn = b^k, b>1b > 1 n/bℓn/b^\ell
n\sqrt{n} n=22kn = 2^{2^k} n1/2ℓn^{1/2^\ell}
n1/bn^{1/b} n=abkn = a^{b^k} n1/bℓn^{1/b^\ell}
n−1n - 1 n>0n > 0 n−ℓn - \ell

(Per n\sqrt n: f(2)(n)=(n1/2)1/2=n1/22f^{(2)}(n) = (n^{1/2})^{1/2} = n^{1/2^2}, e così via.) In presenza di parti intere l'espressione analitica di f(ℓ)f^{(\ell)} diventa molto più difficile.

Data una contrazione ff e un valore n0n_0 si definisce f∗(n,n0)=max⁡{ k:f(k)(n)>n0 }.f^{\ast}(n, n_0) = \max\{\,k : f^{(k)}(n) > n_0\,\}. È il massimo indice di iterata la cui taglia è strettamente maggiore di n0n_0, cioè l'ultimo livello di istanze non di base: alla radice (livello 00) c'è taglia n=f(0)(n)n = f^{(0)}(n), al livello 11 taglia f(n)f(n), ... al livello f∗(n,n0)f^\ast(n,n_0) l'ultima taglia >n0> n_0, e al livello f∗(n,n0)+1f^\ast(n, n_0) + 1 ci sono le foglie (istanze di base). La definizione ha senso solo per n>n0n > n_0 (altrimenti si prenderebbe il massimo dell'insieme vuoto): per n≤n0n \le n_0 si pone per convenzione f∗(n,n0)=−1f^\ast(n,n_0) = -1, in modo che l'unica foglia dell'albero (l'istanza principale, già di base) sia al livello f∗+1=0f^\ast + 1 = 0.

Calcolo di f∗f^\ast nei casi notevoli:

  • n=bkn = b^k, f(n)=n/bf(n) = n/b: f(k)(n)=n/bk>n0  ⟺  bk<n/n0  ⟺  k<log⁡b(n/n0)f^{(k)}(n) = n/b^k > n_0 \iff b^k < n/n_0 \iff k < \log_b(n/n_0), quindi f∗(n,n0)=log⁡bnn0−1,f∗(n,1)=log⁡bn−1f^\ast(n, n_0) = \log_b \frac{n}{n_0} - 1, \qquad f^\ast(n, 1) = \log_b n - 1 (con n/n0n/n_0 potenza di bb, perché kk intero massimo strettamente minore di un intero è l'intero precedente).
  • n=22kn = 2^{2^k}, f(n)=nf(n) = \sqrt n: n1/2k>n0  ⟺  log⁡2n2k>log⁡2n0  ⟺  2k<log⁡2nlog⁡2n0  ⟺  k<log⁡2log⁡2n−log⁡2log⁡2n0n^{1/2^k} > n_0 \iff \frac{\log_2 n}{2^k} > \log_2 n_0 \iff 2^k < \frac{\log_2 n}{\log_2 n_0} \iff k < \log_2\log_2 n - \log_2 \log_2 n_0, quindi f∗(n,n0)=log⁡2log⁡2n−log⁡2log⁡2n0−1,f∗(n,2)=log⁡2log⁡2n−1.f^\ast(n,n_0) = \log_2\log_2 n - \log_2\log_2 n_0 - 1, \qquad f^\ast(n, 2) = \log_2\log_2 n - 1. Più in generale, per n=abkn = a^{b^k} e f(n)=n1/bf(n) = n^{1/b}: f∗(n,a)=log⁡blog⁡an−1f^\ast(n, a) = \log_b\log_a n - 1.
  • f(n)=n−1f(n) = n - 1: n−k>n0  ⟺  k<n−n0n - k > n_0 \iff k < n - n_0, quindi f∗(n,n0)=n−n0−1f^\ast(n, n_0) = n - n_0 - 1 e f∗(n,1)=n−2f^\ast(n, 1) = n - 2.

Analisi dell'albero della ricorrenza

Studio di (∗)(\ast). Per quanto visto, a livello ℓ\ell le istanze hanno taglia f(ℓ)(n)f^{(\ell)}(n) e l'ultimo livello non di base è f∗(n,n0)f^\ast(n,n_0). Per 0≤ℓ≤f∗(n,n0)0 \le \ell \le f^\ast(n, n_0) il lavoro di ogni nodo del livello ℓ\ell è w(f(ℓ)(n))w\bigl(f^{(\ell)}(n)\bigr); al livello f∗(n,n0)+1f^\ast(n,n_0) + 1 il lavoro di ogni foglia è T0T_0.

Numero di nodi per livello. Al livello 00 c'è un nodo, padre di s(n)s(n) nodi del livello 11; ognuno di questi genera s(f(n))s(f(n)) nodi del livello 22, e così via: un nodo del livello ℓ\ell genera s(f(ℓ)(n))s\bigl(f^{(\ell)}(n)\bigr) figli. Quindi i nodi del livello ℓ\ell sono s(n)⋅s(f(n))⋯s(f(ℓ−1)(n))=∏j=0ℓ−1s(f(j)(n)),s(n)\cdot s\bigl(f(n)\bigr)\cdots s\bigl(f^{(\ell-1)}(n)\bigr) = \prod_{j=0}^{\ell-1} s\bigl(f^{(j)}(n)\bigr), per 0≤ℓ≤f∗(n,n0)+10 \le \ell \le f^\ast(n,n_0) + 1. Si usano le convenzioni dell'elemento neutro: somma vuota ∑i=ab⋯=0\sum_{i=a}^{b} \dots = 0 e prodotto vuoto ∏i=ab⋯=1\prod_{i=a}^{b} \dots = 1 per a>ba > b; così il livello 00 ha 11 nodo, e n≤n0n \le n_0 (con f∗=−1f^\ast = -1) dà T(n)=T0T(n) = T_0.

LIVELLO            TAGLIA       NODI/LIVELLO                    LAVORO/NODO
0                  f^(0)(n)     1                               w(f^(0)(n))
1                  f^(1)(n)     s(f^(0)(n))                     w(f^(1)(n))
...
l  (0<=l<=f*)      f^(l)(n)     prod_{j=0}^{l-1} s(f^(j)(n))    w(f^(l)(n))
...
f*+1  (foglie)     <= n0        prod_{j=0}^{f*} s(f^(j)(n))     T0

Per ottenere T(n)T(n) si sommano, riga per riga, i prodotti delle ultime due colonne (il lavoro cumulativo del livello):

Formula generale. T(n)=∑ℓ=0f∗(n,n0)[∏j=0ℓ−1s(f(j)(n))]w(f(ℓ)(n))  +  [∏j=0f∗(n,n0)s(f(j)(n))]T0.T(n) = \sum_{\ell=0}^{f^\ast(n,n_0)} \left[\prod_{j=0}^{\ell-1} s\bigl(f^{(j)}(n)\bigr)\right] w\bigl(f^{(\ell)}(n)\bigr) \;+\; \left[\prod_{j=0}^{f^\ast(n,n_0)} s\bigl(f^{(j)}(n)\bigr)\right] T_0.

La correttezza dipende anche dalle convenzioni sopra. Dimostrazione per induzione, che nelle dispense è lasciata come esercizio:

Dimostrazione (induzione su nn).

  • [B] Se n≤n0n \le n_0, f∗=−1f^\ast = -1: la somma è vuota (00) e il prodotto vuoto (11), quindi T(n)=T0T(n) = T_0.
  • [HP] La formula vale per ogni taglia m<nm < n, con n>n0n > n_0.
  • [TH] T(n)=s(n) T(f(n))+w(n)T(n) = s(n)\,T(f(n)) + w(n) e f(n)<nf(n) < n, quindi per [HP] si applica a f(n)f(n). Valgono f(j)(f(n))=f(j+1)(n)f^{(j)}(f(n)) = f^{(j+1)}(n) e f∗(f(n),n0)=f∗(n,n0)−1f^\ast(f(n), n_0) = f^\ast(n,n_0) - 1 (se f(n)≤n0f(n) \le n_0 allora f∗(n,n0)=0f^\ast(n,n_0) = 0 e f∗(f(n),n0)=−1f^\ast(f(n),n_0) = -1 per convenzione). Posto F=f∗(n,n0)F = f^\ast(n,n_0): T(f(n))=∑ℓ=0F−1[∏j=0ℓ−1s(f(j+1)(n))]w(f(ℓ+1)(n))+[∏j=0F−1s(f(j+1)(n))]T0.T(f(n)) = \sum_{\ell=0}^{F-1} \left[\prod_{j=0}^{\ell-1} s\bigl(f^{(j+1)}(n)\bigr)\right] w\bigl(f^{(\ell+1)}(n)\bigr) + \left[\prod_{j=0}^{F-1} s\bigl(f^{(j+1)}(n)\bigr)\right] T_0. Moltiplicando per s(n)s(n), che è il fattore j=0j = 0 di ogni prodotto, e cambiando indici ℓ′=ℓ+1\ell' = \ell + 1, j′=j+1j' = j + 1: s(n) T(f(n))=∑ℓ′=1F[∏j′=0ℓ′−1s(f(j′)(n))]w(f(ℓ′)(n))+[∏j′=0Fs(f(j′)(n))]T0.s(n)\,T(f(n)) = \sum_{\ell'=1}^{F} \left[\prod_{j'=0}^{\ell'-1} s\bigl(f^{(j')}(n)\bigr)\right] w\bigl(f^{(\ell')}(n)\bigr) + \left[\prod_{j'=0}^{F} s\bigl(f^{(j')}(n)\bigr)\right] T_0. Aggiungendo w(n)=[∏j=0−1]w(f(0)(n))w(n) = \bigl[\prod_{j=0}^{-1}\bigr] w(f^{(0)}(n)), cioè il termine ℓ=0\ell = 0, si ottiene la formula per T(n)T(n). □\square

Esempi di applicazione

Esempio 1: s=6s = 6, f=n/3f = n/3

T(n)={4n=16 T(n/3)+n(n−1)n>1,n=3k.T(n) = \begin{cases} 4 & n = 1 \\ 6\,T(n/3) + n(n-1) & n > 1, \end{cases} \qquad n = 3^k. Parametri: s(n)=6s(n) = 6, f(n)=n/3f(n) = n/3, w(n)=n2−nw(n) = n^2 - n, T0=4T_0 = 4, n0=1n_0 = 1; quindi f(ℓ)(n)=n/3ℓf^{(\ell)}(n) = n/3^\ell, f∗(n,1)=log⁡3n−1f^\ast(n,1) = \log_3 n - 1, ∏j<ℓ6=6ℓ\prod_{j<\ell} 6 = 6^\ell e w(f(ℓ)(n))=n29ℓ−n3ℓw(f^{(\ell)}(n)) = \frac{n^2}{9^\ell} - \frac{n}{3^\ell}. La formula dà T(n)=∑ℓ=0log⁡3n−16ℓ(n29ℓ−n3ℓ)⏟A+6log⁡3n⋅4⏟B.T(n) = \underbrace{\sum_{\ell=0}^{\log_3 n - 1} 6^\ell \left(\frac{n^2}{9^\ell} - \frac{n}{3^\ell}\right)}_{A} + \underbrace{6^{\log_3 n}\cdot 4}_{B}. Parte BB. Con l'identità alog⁡cb=blog⁡caa^{\log_c b} = b^{\log_c a} si ha 6log⁡3n=nlog⁡36=nlog⁡32+log⁡33=n1+log⁡326^{\log_3 n} = n^{\log_3 6} = n^{\log_3 2 + \log_3 3} = n^{1 + \log_3 2}, quindi B=4 n1+log⁡32B = 4\,n^{1+\log_3 2}. Queste riscritture servono a portare i termini in forma standard per poi semplificare.

Parte AA. Spezzando la somma e usando 6ℓ/9ℓ=(2/3)ℓ6^\ell/9^\ell = (2/3)^\ell e 6ℓ/3ℓ=2ℓ6^\ell/3^\ell = 2^\ell: A=n2∑ℓ=0log⁡3n−1(23)ℓ−n∑ℓ=0log⁡3n−12ℓ=n2⋅1−(2/3)log⁡3n1−2/3−n⋅1−2log⁡3n1−2.\begin{aligned} A &= n^2 \sum_{\ell=0}^{\log_3 n - 1} \left(\tfrac23\right)^\ell - n \sum_{\ell=0}^{\log_3 n - 1} 2^\ell = n^2 \cdot \frac{1 - (2/3)^{\log_3 n}}{1 - 2/3} - n\cdot\frac{1 - 2^{\log_3 n}}{1 - 2}. \end{aligned} Con (2/3)log⁡3n=nlog⁡3(2/3)=nlog⁡32−1(2/3)^{\log_3 n} = n^{\log_3(2/3)} = n^{\log_3 2 - 1} e 2log⁡3n=nlog⁡322^{\log_3 n} = n^{\log_3 2}: A=3n2(1−nlog⁡32−1)+n(1−nlog⁡32)=3n2−3n1+log⁡32+n−n1+log⁡32=3n2−4n1+log⁡32+n.A = 3n^2\bigl(1 - n^{\log_3 2 - 1}\bigr) + n\bigl(1 - n^{\log_3 2}\bigr) = 3n^2 - 3n^{1 + \log_3 2} + n - n^{1+\log_3 2} = 3n^2 - 4n^{1+\log_3 2} + n. Somma. T(n)=A+B=3n2−4n1+log⁡32+n+4n1+log⁡32=3n2+nT(n) = A + B = 3n^2 - 4n^{1+\log_3 2} + n + 4n^{1+\log_3 2} = \mathbf{3n^2 + n}.

Verifica per induzione (logicamente non necessaria, perché la formula è dimostrata, ma utile contro gli errori di calcolo): base T(1)=4=3+1T(1) = 4 = 3 + 1; passo, per n>1n > 1: T(n)=6 T(n/3)+n(n−1)=6(3n29+n3)+n2−n=2n2+2n+n2−n=3n2+nT(n) = 6\,T(n/3) + n(n-1) = 6\left(3\frac{n^2}{9} + \frac n3\right) + n^2 - n = 2n^2 + 2n + n^2 - n = 3n^2 + n.

Esempio 2: s=2s = 2, f=nf = \sqrt n

T(n)={1n=22 T(n)+1n>2,n=22k.T(n) = \begin{cases} 1 & n = 2 \\ 2\,T(\sqrt n) + 1 & n > 2, \end{cases} \qquad n = 2^{2^k}. Parametri: s=2s = 2, f(n)=nf(n) = \sqrt n, w=1w = 1, n0=2n_0 = 2, T0=1T_0 = 1; f(ℓ)(n)=n1/2ℓf^{(\ell)}(n) = n^{1/2^\ell}, f∗(n,2)=log⁡2log⁡2n−1f^\ast(n,2) = \log_2\log_2 n - 1, ∏j<ℓ2=2ℓ\prod_{j<\ell} 2 = 2^\ell: T(n)=∑ℓ=0log⁡2log⁡2n−12ℓ⋅1+2log⁡2log⁡2n⋅1=(2log⁡2log⁡2n−1)+2log⁡2log⁡2n=2log⁡2n−1,T(n) = \sum_{\ell=0}^{\log_2\log_2 n - 1} 2^\ell \cdot 1 + 2^{\log_2\log_2 n}\cdot 1 = \bigl(2^{\log_2\log_2 n} - 1\bigr) + 2^{\log_2\log_2 n} = 2\log_2 n - 1, perché 2log⁡2log⁡2n=log⁡2n2^{\log_2\log_2 n} = \log_2 n. Controllo: T(2)=1T(2) = 1, T(4)=2T(2)+1=3=2⋅2−1T(4) = 2T(2) + 1 = 3 = 2\cdot 2 - 1, T(16)=7T(16) = 7, T(256)=15T(256) = 15.

Esempio 3: merge sort, conteggio esatto

s=2s = 2, f=n/2f = n/2, w=n−1w = n - 1, T0=0T_0 = 0, n0=1n_0 = 1, n=2kn = 2^k: f∗=log⁡2n−1f^\ast = \log_2 n - 1, nodi al livello ℓ\ell: 2ℓ2^\ell, lavoro per nodo n2ℓ−1\frac{n}{2^\ell} - 1. Ogni livello costa 2ℓ(n2ℓ−1)=n−2ℓ2^\ell\left(\frac{n}{2^\ell} - 1\right) = n - 2^\ell: T(n)=∑ℓ=0log⁡2n−1(n−2ℓ)+0=nlog⁡2n−(n−1)=nlog⁡2n−n+1.T(n) = \sum_{\ell=0}^{\log_2 n - 1}\left(n - 2^\ell\right) + 0 = n\log_2 n - (n - 1) = n\log_2 n - n + 1. Per n=8n = 8: 24−8+1=1724 - 8 + 1 = 17 (verificato con il conteggio della ricorrenza).

Esempio 4: la contrazione f(n)=n−1f(n) = n - 1

T(n)=2 T(n−1)+1T(n) = 2\,T(n-1) + 1 con T(1)=1T(1) = 1 (le torri di Hanoi): s=2s = 2, f=n−1f = n - 1, w=1w = 1, n0=1n_0 = 1, T0=1T_0 = 1, f∗(n,1)=n−2f^\ast(n,1) = n - 2, nodi al livello ℓ\ell: 2ℓ2^\ell: T(n)=∑ℓ=0n−22ℓ+2n−1⋅1=(2n−1−1)+2n−1=2n−1.T(n) = \sum_{\ell=0}^{n-2} 2^\ell + 2^{n-1}\cdot 1 = (2^{n-1} - 1) + 2^{n-1} = 2^n - 1. Con f(n)=n−1f(n) = n - 1 l'albero è molto profondo (n−1n - 1 livelli) e con s=2s = 2 le foglie sono 2n−12^{n-1}: esponenziale. Con s=1s = 1, per esempio T(n)=T(n−1)+nT(n) = T(n-1) + n con T(1)=1T(1) = 1, i livelli costano n−ℓn - \ell e la formula dà ∑ℓ=0n−2(n−ℓ)+1=n(n+1)2\sum_{\ell=0}^{n-2}(n - \ell) + 1 = \frac{n(n+1)}{2}.

Taglia delle foglie diversa da 11 (algoritmi ibridi)

Se la ricorsione si ferma a n0>1n_0 > 1 (per esempio si passa a un algoritmo diverso sotto una soglia), con f(n)=n/bf(n) = n/b e n/n0n/n_0 potenza di bb, si ha f∗=log⁡b(n/n0)−1f^\ast = \log_b(n/n_0) - 1 e le foglie sono alog⁡b(n/n0)=(n/n0)log⁡baa^{\log_b(n/n_0)} = (n/n_0)^{\log_b a} con lavoro T0T_0, che può dipendere da n0n_0. Esempio con a=2a = 2, b=2b = 2, w(n)=nw(n) = n, n0=4n_0 = 4, T0=5T_0 = 5: T(n)=∑ℓ=0log⁡2n−32ℓn2ℓ+n4⋅5=n(log⁡2n−2)+5n4,T(n) = \sum_{\ell=0}^{\log_2 n - 3} 2^\ell\frac{n}{2^\ell} + \frac n4\cdot 5 = n(\log_2 n - 2) + \frac{5n}{4}, per esempio T(8)=8+10=18T(8) = 8 + 10 = 18. È la situazione degli esercizi con algoritmo ibrido: Esercizio - algoritmo ibrido e valore ottimo della soglia (compito del 27 gennaio 2025, es. 1) e Esercizio - algoritmo ibrido con soglia ottima e confronto con la versione ricorsiva (compito del 4 settembre 2024, es. 1).

Verifica automatica

python
import math
from functools import lru_cache

def T_rec(n, n0, T0, s, f, w):           # ricorrenza (*) applicata alla lettera
    return T0 if n <= n0 else s(n) * T_rec(f(n), n0, T0, s, f, w) + w(n)

def T_formula(n, n0, T0, s, f, w):       # formula generale per livelli
    if n <= n0:
        return T0
    taglie = [n]
    while f(taglie[-1]) > n0:
        taglie.append(f(taglie[-1]))
    F = len(taglie) - 1                  # = f*(n, n0)
    tot, prod = 0, 1
    for l in range(F + 1):
        tot += prod * w(taglie[l])
        prod *= s(taglie[l])
    return tot + prod * T0

# esempio 1: 6T(n/3) + n(n-1), T(1) = 4
for k in range(12):
    n = 3**k
    assert T_rec(n, 1, 4, lambda m: 6, lambda m: m // 3, lambda m: m*m - m) \
        == T_formula(n, 1, 4, lambda m: 6, lambda m: m // 3, lambda m: m*m - m) == 3*n*n + n

La formula coincide con la ricorrenza anche per funzioni ss, ff, ww qualsiasi non decrescenti (controllato su 500 casi con f(n)=⌊n/b⌋f(n) = \lfloor n/b\rfloor, ss costante o dipendente da nn, f(n)=⌊n⌋f(n) = \lfloor\sqrt n\rfloor).

Errori comuni

  • Dimenticare di portare i termini in forma standard (potenze di nn con esponente log⁡\log) prima di sommare: i termini che si cancellano (−4n1+log⁡32-4n^{1+\log_3 2} e +4n1+log⁡32+4n^{1+\log_3 2}) non si riconoscono.
  • Contare i livelli sbagliati: i livelli non di base sono 0..f∗0..f^\ast (cioè f∗+1f^\ast + 1 livelli) e le foglie stanno al livello f∗+1f^\ast + 1, con costo T0T_0 per foglia e non ww.
  • Usare s(n)s(n) invece di s(f(j)(n))s(f^{(j)}(n)) nel prodotto quando ss dipende dalla taglia.
  • Dimenticare la somma dei costi delle foglie [∏s]T0\bigl[\prod s\bigr] T_0 (se T0>0T_0 > 0 domina nei casi con molte foglie).
  • Usare la formula con nn non della forma richiesta (n=bkn = b^k, o n=abkn = a^{b^k}) senza dirlo: i risultati valgono per quei nn.

Domande d'esame

  1. Risolvere con la formula generale T(n)=4T(n/2)+nT(n) = 4T(n/2) + n, T(1)=1T(1) = 1, per n=2kn = 2^k. Traccia: s=4s = 4, f=n/2f = n/2, w=nw = n, T0=1T_0 = 1, f∗=log⁡2n−1f^\ast = \log_2 n - 1; il livello ℓ\ell costa 4ℓ⋅n2ℓ=n 2ℓ4^\ell \cdot \frac{n}{2^\ell} = n\,2^\ell, quindi T(n)=n(n−1)+4log⁡2n=n2−n+n2=2n2−nT(n) = n(n-1) + 4^{\log_2 n} = n^2 - n + n^2 = 2n^2 - n.
  2. Che cosa rappresenta f∗(n,n0)f^\ast(n, n_0) e quanto vale per f(n)=n/2f(n) = n/2, n0=1n_0 = 1? Risposta: l'ultimo livello dell'albero con istanze non di base; log⁡2n−1\log_2 n - 1; le foglie sono al livello log⁡2n\log_2 n.
  3. Qual è la complessità esatta (in confronti) del merge sort nel caso peggiore per n=2kn = 2^k? nlog⁡2n−n+1n\log_2 n - n + 1.

Versione ripasso

  • Albero della ricorrenza: forma dell'albero delle chiamate dell'istanza peggiore; nodo interno = taglia + lavoro (Divide+Conquer), foglia = costo diretto. T(n)T(n) = somma dei lavori di tutti i nodi.
  • Famiglia: T(n)=T0T(n)=T_0 per n≤n0n\le n_0, T(n)=s(n) T(f(n))+w(n)T(n)=s(n)\,T(f(n))+w(n) per n>n0n>n_0, con s,f,ws,f,w non decrescenti e f(n)<nf(n)<n (contrazione). Se s,f,ws,f,w sono limiti superiori, TT è un limite superiore. Merge sort (Algoritmi di ordinamentoSelection sort e insertion sort (quadratici, con invarianti), merge sort (divide et impera, Theta(n log n)), quick sort (caso pessimo quadratico, medio n log n), heap sort (in loco, Theta(n log n)), ordinamento senza confronti per chiavi intere in un intervallo piccolo, limite inferiore Omega(n log n) per gli algoritmi basati su confronti.Algoritmi di ordinamento →): s=2,f=n/2,w=n−1,T0=0s=2,f=n/2,w=n-1,T_0=0.
  • Iterate: f(0)=nf^{(0)}=n, f(ℓ)=f(f(ℓ−1))f^{(\ell)}=f(f^{(\ell-1)}). n/b→n/bℓn/b\to n/b^\ell; n→n1/2ℓ\sqrt n\to n^{1/2^\ell}; n−1→n−ℓn-1\to n-\ell. f∗(n,n0)=max⁡{k:f(k)(n)>n0}f^\ast(n,n_0)=\max\{k:f^{(k)}(n)>n_0\} (ultimo livello non di base; −1-1 se n≤n0n\le n_0): log⁡bnn0−1\log_b\frac n{n_0}-1 per n/bn/b; log⁡2log⁡2n−1\log_2\log_2 n-1 per n\sqrt n con n0=2n_0=2; n−n0−1n-n_0-1 per n−1n-1.
  • Livelli: livello ℓ\ell: taglia f(ℓ)(n)f^{(\ell)}(n), ∏j<ℓs(f(j)(n))\prod_{j<\ell}s(f^{(j)}(n)) nodi, lavoro w(f(ℓ)(n))w(f^{(\ell)}(n)) ciascuno; le foglie al livello f∗+1f^\ast+1 con lavoro T0T_0. Convenzioni: somma vuota 00, prodotto vuoto 11.
  • Formula generale: T(n)=∑ℓ=0f∗[∏j=0ℓ−1s(f(j)(n))]w(f(ℓ)(n))+[∏j=0f∗s(f(j)(n))]T0.T(n)=\sum_{\ell=0}^{f^\ast}\Bigl[\prod_{j=0}^{\ell-1}s(f^{(j)}(n))\Bigr]w(f^{(\ell)}(n))+\Bigl[\prod_{j=0}^{f^\ast}s(f^{(j)}(n))\Bigr]T_0. Dimostrazione per induzione su nn usando f∗(f(n),n0)=f∗(n,n0)−1f^\ast(f(n),n_0)=f^\ast(n,n_0)-1 e riindicizzando.
  • Esempi: 6T(n/3)+n(n−1)6T(n/3)+n(n-1), T(1)=4T(1)=4: 3n2+n3n^2+n (parte A=3n2−4n1+log⁡32+nA=3n^2-4n^{1+\log_3 2}+n, parte B=4n1+log⁡32B=4n^{1+\log_3 2} con alog⁡cb=blog⁡caa^{\log_c b}=b^{\log_c a}); 2T(n)+12T(\sqrt n)+1, T(2)=1T(2)=1: 2log⁡2n−12\log_2 n-1; merge sort: nlog⁡2n−n+1n\log_2 n-n+1; 2T(n−1)+12T(n-1)+1, T(1)=1T(1)=1: 2n−12^n-1; T(n−1)+nT(n-1)+n: n(n+1)2\frac{n(n+1)}2; con n0>1n_0>1 le foglie sono (n/n0)log⁡ba(n/n_0)^{\log_b a} con lavoro T0T_0 (ibridi).
  • Errori tipici: non portare i termini in forma standard; sbagliare il numero di livelli (foglie a f∗+1f^\ast+1 con costo T0T_0); usare s(n)s(n) al posto di s(f(j)(n))s(f^{(j)}(n)); dimenticare il termine delle foglie.
  • Passi dell'Esempio 1 (6T(n/3)+n(n−1)6T(n/3)+n(n-1), T(1)=4T(1)=4): s=6s=6, f=n/3f=n/3, w=n2−nw=n^2-n, f∗=log⁡3n−1f^\ast=\log_3n-1. Livello ℓ\ell: 6ℓ6^\ell nodi di lavoro n29ℓ−n3ℓ\frac{n^2}{9^\ell}-\frac n{3^\ell}, quindi A=n2∑(23)ℓ−n∑2ℓ=3n2(1−nlog⁡32−1)+n(1−nlog⁡32)A=n^2\sum(\frac23)^\ell-n\sum2^\ell=3n^2(1-n^{\log_32-1})+n(1-n^{\log_32}); B=6log⁡3n⋅4=4n1+log⁡32B=6^{\log_3n}\cdot4=4n^{1+\log_32} (da alog⁡cb=blog⁡caa^{\log_cb}=b^{\log_ca} e log⁡36=1+log⁡32\log_36=1+\log_32). Somma: i termini ∓4n1+log⁡32\mp4n^{1+\log_32} si cancellano. Verifica: T(n)=6(3n29+n3)+n2−n=3n2+nT(n)=6(3\frac{n^2}9+\frac n3)+n^2-n=3n^2+n.
  • Passi dell'Esempio 2: ∏j<ℓ2=2ℓ\prod_{j<\ell}2=2^\ell, somma ∑ℓ=0log⁡2log⁡2n−12ℓ=2log⁡2log⁡2n−1=log⁡2n−1\sum_{\ell=0}^{\log_2\log_2n-1}2^\ell=2^{\log_2\log_2n}-1=\log_2n-1, foglie 2log⁡2log⁡2n=log⁡2n2^{\log_2\log_2n}=\log_2n: T=2log⁡2n−1T=2\log_2n-1. Controllo: T(4)=3T(4)=3, T(16)=7T(16)=7, T(256)=15T(256)=15.
  • Merge sort esatto: livello ℓ\ell costa 2ℓ(n2ℓ−1)=n−2ℓ2^\ell(\frac n{2^\ell}-1)=n-2^\ell; ∑ℓ<log⁡2n(n−2ℓ)=nlog⁡2n−n+1\sum_{\ell<\log_2n}(n-2^\ell)=n\log_2n-n+1 (=17=17 per n=8n=8). Hanoi (2T(n−1)+12T(n-1)+1, f∗=n−2f^\ast=n-2): T=2n−1T=2^n-1.
  • Con n0>1n_0>1 (ibridi): f∗=log⁡bnn0−1f^\ast=\log_b\frac n{n_0}-1, foglie (n/n0)log⁡ba(n/n_0)^{\log_ba} di costo T0T_0 (anche dipendente da n0n_0): 2T(n/2)+n2T(n/2)+n, n0=4n_0=4, T0=5T_0=5 dà n(log⁡2n−2)+5n4n(\log_2n-2)+\frac{5n}4. Esercizi: Esercizio - algoritmo ibrido e valore ottimo della soglia (compito del 27 gennaio 2025, es. 1), Esercizio - algoritmo ibrido con soglia ottima e confronto con la versione ricorsiva (compito del 4 settembre 2024, es. 1).
  • Calcolo di f∗f^\ast: f(k)(n)>n0f^{(k)}(n)>n_0 con f=n/bf=n/b: bk<n/n0⇒k<log⁡bnn0b^k<n/n_0\Rightarrow k<\log_b\frac n{n_0}, massimo k=log⁡bnn0−1k=\log_b\frac n{n_0}-1. Con f=nf=\sqrt n: log⁡2n2k>log⁡2n0⇒k<log⁡2log⁡2n−log⁡2log⁡2n0\frac{\log_2n}{2^k}>\log_2n_0\Rightarrow k<\log_2\log_2n-\log_2\log_2n_0. Con f=n−1f=n-1: n−k>n0⇒k<n−n0n-k>n_0\Rightarrow k<n-n_0, quindi f∗=n−n0−1f^\ast=n-n_0-1.
  • Dimostrazione della formula (idea): induzione su nn; T(n)=s(n)T(f(n))+w(n)T(n)=s(n)T(f(n))+w(n), f(j)(f(n))=f(j+1)(n)f^{(j)}(f(n))=f^{(j+1)}(n) e f∗(f(n),n0)=f∗(n,n0)−1f^\ast(f(n),n_0)=f^\ast(n,n_0)-1; moltiplicare la formula di T(f(n))T(f(n)) per s(n)s(n) aggiunge il fattore j=0j=0 a ogni prodotto e sposta gli indici di uno; il termine w(n)w(n) è il livello 00.
  • Domande tipiche: risolvere 4T(n/2)+n4T(n/2)+n, T(1)=1T(1)=1: livello n2ℓn2^\ell, T=n(n−1)+n2=2n2−nT=n(n-1)+n^2=2n^2-n; significato di f∗(n,n0)f^\ast(n,n_0); conteggio esatto del merge sort.

Esercizi su questo argomento

Teoria collegata