Complessità asintotica
La complessità asintotica di un algoritmo descrive l'andamento temporale per input di grandezza .
Sequenza di Fibonacci
Per esempio, data la sequenza di Fibonacci definita come esistono diversi algoritmi per trovare l'-esimo numero:
Correttezza | Complessità temporale | Complessità spaziale | |
---|---|---|---|
Binet | Incorretto | ||
Ricorsivo | Corretto | ||
Iterativo con array | Corretto | ||
Iterativo | Corretto |
Binet
La formula di Binet lega la sezione aurea all'-esimo numero di Fibonacci: dove e sono le soluzioni a .
Di conseguenza, l'algoritmo risulta semplicemente essere:
Fib1(int n) -> int
return 1/sqrt(5) * (pow(phi, n) - pow(1 - phi, n))
Complessità temporale e spaziale
In questo caso, dato che l'algoritmo è composto da un return
e l'unico dato salvato è .
Correttezza
Per verificane la correttezza, bisogna dimostrare che:
Proseguendo per induzione, i casi base sono:
- Per ,
- Per ,
Per cui va verificato il passo induttivo: di conseguenza, dati i casi base, si suppone che la formula sia valida per e , per cui: che va trasformata in , cioè: e siccome e sono le uniche soluzioni del sistema, la formula è verificata.
Seppur dimostrato, Fib1
rimane incorretto perchè l'imprecisione aumenta con il crescere di .
Ricorsivo
Nel caso ricorsivo, l'algoritmo risulta essere:
Fib2(int n) -> int
if n <= 2
return 1
return Fib2(n - 1) + Fib2(n - 2)
Complessità temporale e spaziale
I passaggi che fa questo algoritmo sono:
dove è per l'if
/return
, mentre è per l'if
più l'else
/return
.
Per trovare il valore di si può usare un albero ricorsivo . Per , l'albero sarà:
Contando i pesi sui nodi dell'albero, si può ricavare il costo: dove è il numero di nodi interni e è il numero di foglie.
Si può quindi dimostrare che:
-
, per induzione:
Tra i casi base, se:
Per cui, supponendo che sia vero per ipotesi induttiva, allora: dato che non possiede altri sottoalberi oltre che e . Di conseguenza:
-
Tra i casi base, se:
Supponendo vero per ipotesi induttiva, allora: visto che per la precedente dimostrazione .
Perciò, la complessità temporale di Fib2
sarà:
e la complessità spaziale .
È anche possibile che la complessità cresce più velocemente che esponenzialmente: infatti per induzione, il caso base per e il passo induttivo per :
Iterativo con array
Sfruttando un array, si possono salvare i valori di senza doverli ricalcolare su :
Fib3(int n) -> int
int F[n]
F[1] = F[2] = 1
for i = 3 to n
F[i] = F[i-1] + F[i-2]
return F[n]
Complessità temporale e spaziale
Il numero di istruzioni eseguite sono
dove sono per l'inizializzazione di F
e il return
, mentre:
- sul contenuto del ciclo: eseguito per e per il resto delle volte
- sulla condizione: per , per il resto delle volte e una alla fine per
Mentre lo spazio occupato sarà , dato che viene salvato per ogni .
Iterativo
La precedente versione si può ottimizzare ulteriormente, salvando solamente e :
Fib4(int n) -> int
int f1 = 1, f2 = 1, f
for i = 3 to n
f = f1 + f2
f1 = f2
f2 = f
return f
per cui rimane che , mentre la complessità spaziale diventa .