







Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
I metodi per risolvere le equazioni di ricorrenza, strumenti fondamentali per analizzare la complessità degli algoritmi ricorsivi. Vengono presentati quattro metodi: sostituzione, iterativo, dell'albero e del teorema principale, illustrati con esempi pratici. Una solida base per comprendere come determinare la complessità computazionale degli algoritmi ricorsivi.
Tipologia: Dispense
1 / 13
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!








Valutare la complessità di un algoritmo ricorsivo è, in genere, più laborioso che nel caso degli algoritmi iterativi.
Infatti, la natura ricorsiva della soluzione algoritmica dà luogo a una funzione di costo che, essendo strettamente legata alla struttura dell’algoritmo, è anch’essa ricorsiva. Trovare la funzione di costo ricorsiva è piuttosto immediato. Essa però deve essere riformulata in modo che non sia più ricorsiva, altrimenti il costo asintotico non può essere quantificato, e questa è la parte meno semplice. La riformulazione della funzione di costo ricorsiva in una equivalente ma non ricorsiva si affronta impostando una equazione di ricorrenza , costituita dalla formulazione ricorsiva e dal caso base.
Iniziamo da un esempio, quello della ricerca sequenziale ricorsiva (par. 4.2.4). Il corpo della funzione, chiamato su un vettore di dimensione n :
Dunque, nel corpo della funzione abbiamo un numero costante di operazioni elementari e una chiamata ricorsiva.
Indicando con T(n) la complessità dell’algoritmo ricorsivo, possiamo esprimere la complessità dell’algoritmo mediante questa equazione di ricorrenza:
In generale, la parte generale dell’equazione di ricorrenza che definisce T(n) deve essere sempre costituita dalla somma di almeno due addendi, di cui almeno uno contiene la parte ricorsiva (nell’esempio sopra T(n-1) ) mentre uno rappresenta la complessità di tutto ciò che viene eseguito al di fuori della chiamata ricorsiva. Si tenga presente che, anche se questa parte dovesse essere un solo confronto, la sua complessità non può essere ignorata, altrimenti si otterrebbe che la funzione ha una complessità indipendente dalla dimensione del suo input, e questa è data da quanto illustrato nel caso base. Non possiamo dire che ciò sia impossibile, ma è molto improbabile.
Esistono alcuni metodi utili per risolvere le equazioni di ricorrenza, che saranno illustrati nei prossimi paragrafi:
L’idea alla base di questo metodo è la seguente:
La difficoltà di questo metodo risiede nel fatto che si deve trovare la funzione più vicina alla vera soluzione, perché tutte le funzioni più grandi (se stiamo cercando O ) o più piccole (se stiamo cercando Ω ) funzionano. In effetti questo metodo serve soprattutto nelle dimostrazioni mentre si sconsiglia di utilizzarlo nella pratica.
Applichiamo questo metodo all’equazione di ricorrenza dell’algoritmo ricorsivo di ricerca sequenziale:
Poiché dobbiamo ipotizzare una soluzione, è innanzi tutto necessario eliminare la notazione asintotica dall'equazione di ricorrenza, così che le costanti non rimangano "nascoste" nella notazione, conducendo a risultati errati: infatti la verifica della soluzione va fatta in termini esatti e non asintotici.
Quindi, l'equazione di ricorrenza può essere trasformata, senza cambiarne il significato, nella seguente:
per due costanti c e d fissate.
Ipotizziamo ora la soluzione T(n) = O(n), ossia T(n) ≤ kn dove k è una costante che va ancora determinata.
Sostituiamo dapprima la soluzione ipotizzata nel caso base. Si ottiene: T(1) ≤ k ; poiché sapevamo che T(1) = d , la disuguaglianza è soddisfatta se e solo se k ≥ d.
Se sostituiamo la soluzione nella formulazione ricorsiva dell'equazione di ricorrenza otteniamo invece:
T(n) ≤ k(n-1) + c = kn – k + c ≤ kn
L’ultima disuguaglianza è vera se e solo se k ≥ c.
T(n) = T(n – 2) + Ө(1) + Ө(1), ma T(n-2)=T(n-3) + Ө(1) quindi T(n) = T(n – 3) + Ө(1) + Ө(1) + Ө(1), ma T(n-3)=T(n-4) + Ө(1) quindi T(n) = T(n – 4) + Ө(1) + Ө(1) + Ө(1) + Ө(1), ecc.
fino a ottenere:
T(n) = n Ө(1) = Ө(n).
Come si vede, otteniamo una soluzione identica a quella trovata col metodo di sostituzione.
Applichiamo ora il metodo iterativo all’equazione di ricorrenza che descrive la complessità dell’algoritmo di ricerca binaria ricorsiva (par. 4.2.5).
L’equazione di ricorrenza è la seguente:
in quanto, nel corpo della funzione, troviamo un numero costante di operazioni elementari ed una chiamata ricorsiva che opera su un problema di dimensione dimezzata.
Sviluppiamo l’equazione:
T(n) = T( ) + Ө(1) = T( ) + Ө(1) + Ө(1) = T( ) + Ө(1) + Ө(1) + Ө(1)=… = T( ) + k Ө(1)
Ora, quando k = log n si ha = 1 , quindi per tale valore di k ci fermiamo ed otteniamo:
T(n) = Ө(1) + log n Ө(1) = Ө(log n).
Vediamo infine un altro esempio, l’algoritmo ricorsivo per il calcolo dei numeri di Fibonacci (par. 4.2.2). Nel corpo della funzione abbiamo un numero costante di operazioni e due chiamate ricorsive.
L’equazione di ricorrenza è perciò:
E’ facile vedere che non si riesce a risolvere questa equazione di ricorrenza tramite il metodo iterativo, in quanto il numero di addendi cresce esponenzialmente giungendo presto ad una quantità non gestibile. Possiamo però fare le seguenti considerazioni:
Limite superiore
Applichiamo il metodo di iterazione alla prima disuguaglianza. Otteniamo:
T n T n – 1) + Ө 22 T(n – 2) + 2^1 Ө (1) + Ө 23 T(n – 3) + 2^2 Ө (1) + 2^1 Ө (1) + Ө … 2 kT(n – k) + Ө (1)
Il procedimento si ferma quando n – k = 1 , ossia k = n – 1 per cui otteniamo:
T n n-1^ Ө (1) + Ө (1) = 2n-1^ Ө (1) + (2n-1^ – 1) Ө (1) = (2n^ – 1) Ө (1)
E dunque troviamo che:
T(n) = O(2n)
Limite inferiore
Applichiamo il metodo di sostituzione alla seconda disuguaglianza. Otteniamo:
T n T n – 2) + Ө (1) 22 T(n – 4) + 2^1 Ө (1) + Ө (1) 23 T(n – 6) + 2^2 Ө (1) + 2^1 Ө (1) + Ө (1) … 2 kT(n – 2k) + Ө (1)
Il procedimento si ferma quando n – 2k = 0 (se n è pari; per n dispari il procedimento termina quando n – 2k = 1 e differisce per alcuni dettagli, ma il comportamento asintotico non cambia), ossia k = n/2 per cui otteniamo:
T(n) 2 n/2^ Ө (1) + Ө (1) = 2n/2^ Ө (1) + (2n/2^ – 1) Ө (1) = (2*2n/2^ – 1) Ө (1)
E dunque troviamo che:
T(n) = Ω(2n/2).
Osserviamo che, anche se non siamo in grado di trovare una funzione asintotica precisa ( Ө ), possiamo comunque concludere che la complessità del calcolo dei numeri di Fibonacci con una tecnica ricorsiva richiede una complessità esponenziale in n , visto che k 12 n/2^ T(n) k 22 n^ per opportune costanti k 1 e k 2.
fino ad arrivare alle foglie, che corrispondono ai casi base e quindi non hanno figli. Una volta completato l’albero, la complessità è data dalla somma delle complessità di tutti i livelli (cioè le “righe” in cui sono disposti i nodi) di cui è costituito l’albero. Sebbene l’albero consenta di visualizzare più chiaramente la decomposizione in sottoproblemi, le problematiche dei calcoli algebrici da effettuare sono essenzialmente analoghe a quelle del metodo iterativo.
Ad esempio, per l’equazione di cui sopra:
livello 1 (radice): Ө(n^2 )
livello 2 (figli della radice): 2 Ө( ) =Ө( + ) = Ө( )
livello 3: 4 Ө( ) = Ө( + + + ) = Ө( )
…
livello i: 2 i-1Ө( ) = Ө( )
dove i ha un valore massimo k tale che = 1 , ossia k – 1 = log n e quindi k = log n + 1.
Il contributo totale è quindi:
=
nella seconda espressione abbiamo posto, per convenienza, j = i – 1.
Si osservi che, quando calcoliamo il contributo di ciascun livello, non possiamo nascondere le costanti nella notazione Ө , in quanto esse non sono in effetti costanti, come si può evincere dal generico livello i , in cui il fattore è 2 i-1, che cresce fino a divenire n , quando i cresce fino a divenire log n + 1.
Questo metodo è molto utile nella pratica e fornisce una soluzione meccanica per le equazioni di ricorrenza della forma:
Dove a ≥ 1, b > 1 sono delle costanti ed f(n) è una funzione asintoticamente positiva.
Si deve subito osservare che l’equazione di ricorrenza precedente non è ben definita, dato che n/b potrebbe non essere un valore intero. E’ però facile convincersi che, sostituendo agli a termini T(n/b) i termini T( ) oppure T( ) il risultato asintotico non cambia. Per questa
ragione, commettendo un leggero abuso di notazione, ignoriamo il problema assumendo che n sia sempre una potenza di b > 1.
5.4.1 Enunciato del teorema principale
Per inciso, mentre già sappiamo che f(n) rappresenta il costo di ricombinazione delle soluzioni ai sottoproblemi, osserviamo ora che il valore logba è legato alla relazione che c’è fra il numero dei sottoproblemi in cui si suddivide un problema e la dimensione dei sottoproblemi in rapporto alla dimensione del problema.
Il teorema principale ci dice che “vince” il maggiore fra f(n) e , ossia la complessità è governata dal maggiore dei due:
Si noti che “più grande” e “più piccolo” in questo contesto significa polinomialmente più grande (o più piccolo), data la posizione all’esponente di. In altre parole, f(n) deve essere
asintoticamente più grande (o più piccola) rispetto a di un fattore n per qualche > 0.
In effetti fra i casi 1 e 2 vi è un intervallo in cui f(n) è più piccola di , ma non polinomialmente. Analogamente, fra i casi 2 e 3 vi è un intervallo in cui f(n) è più grande di , ma non polinomialmente.
In tali intervalli (casi 1 e 3) il metodo del teorema principale non può essere usato, come non può essere usato se (caso 3) non vale la condizione a f(n/b) ≤ c f(n).
Esempio 5. T(n) = 9T(n/3) + Ө(n)
= Ө(n) + Ө(n log^2 n - n ) =
= Ө(n) + Ө(n log^2 n – n ) =
= Ө(n) + Ө(n log^2 n – log^2 n + log n) = = Ө(n log^2 n)
5.4.2 Dimostrazione del teorema principale Dimostreremo il teorema principale solo per valori di n che siano potenze esatte, al fine di semplificare i calcoli.
Per una dimostrazione completa si dovrebbe gestire anche il caso di tutti gli altri valori interi, ma ciò richiede di applicare nei conteggi gli operatori di parte intera superiore e inferiore causando notevoli complicazioni senza nulla aggiungere alla sostanza del risultato. Questa parte viene quindi omessa ma può comunque essere consultata sul libro di testo.
Si noti che dimostrare il teorema solo per potenze esatte di per sè non è sufficiente a dimostrare il teorema in generale. Infatti, nel caso di una ipotetica relazione di ricorrenza definita in questo modo:
La soluzione T(n) = Ө(n) , valida per le potenze di due, non lo sarebbe ovviamente affatto per gli altri valori di n.
D’altro canto, nel nostro caso le equazioni di ricorrenza esprimono sempre delle complessità computazionali, e quindi sono sempre delle funzioni monotone crescenti in n : il caso contrario implicherebbe che la soluzione di un problema di una certa dimensione possa costare meno della soluzione di un problema di dimensione inferiore, il che non è certamente una situazione che si incontra usualmente nella realtà.
Occupiamoci dunque di dimostrare il teorema per l’equazione di ricorrenza:
Iteriamo la ricorrenza:
T(n) = aT( ) + f(n) =
= a + f(n) = a^2 T + a + f(n) =
= a^2 + a + f(n) = a^3 T + a^2 + a + f(n) = …
=akT +
dove, analogamente a quanto già visto in casi precedenti, l’iterazione termina per il valore k tale
che = 1 , ossia k = logbn.
Dunque:
T(n) = Ө (1)+
Ora,
= = =
e quindi l’equazione diviene:
T(n) = Ө ( )+
Valutiamo la sommatoria nei tre casi del teorema.
Caso 1
f(n) = O( ) per qualche costante > 0; allora = O( )
Quindi:
= O =
= O = O =
Ricordando che quando |c| < 1 la somma della serie geometrica è:
abbiamo:
f(n) < f(n) = f(n)
Da ciò segue che = O(f(n)). E’ facile vedere inoltre che il primo addendo della
sommatoria è proprio f(n) , e quindi = Ω(f(n)). Quindi = Ө(f(n)).
Da ciò segue che l’equazione di ricorrenza iniziale diviene:
T(n) = Ө ( )+ Ө (f(n))
Ma, per l’ipotesi, f n Ω ) e di conseguenza nella relazione precedente il termine dominante è f(n). Dunque,
T(n) = (f(n)) CVD(3)