Sei sulla pagina 1di 3

Complessità Computazionale e Complessità

Spaziale

Biagio Pelaia
biagiopelaia@gmail.com

Abstract: Definizione dei criteri di valutazione delle risorse computazionali e spaziali


richieste da un algoritmo. Notazione formale O, ω, Θ © 2021 The Author(s)

1. Complessità Computazionale
Per Complessità Computazionale si intende la quantità di risorse di calcolo di cui un algoritmo necessita durante
la propria esecuzione. E’ anche detta Complessità Temporale sebbene non sia una misura del tempo fisico di
esecuzione dell’algoritmo ma una valutazione del numero di operazioni che l’algoritmo compie, da cui ovviamente
dipenderà il tempo stesso di esecuzione. Ai fini di valutare la complessità computazionale di un algoritmo non è
indicativo misurarne la durata dell’esecuzione in termini di tempo fisico poichè, come facilmente intuibile, essa
dipende da molti fattori sia hardware che software. La potenza di calcolo della macchina ovviamente influenza
la velocità di esecuzione di qualsiasi istruzione che si esegue sulla stessa, ma anche fattori relativi alla gestione
dell’avvicendamento dei vari processi da parte del sistema operativo, nei sistemi operativi non real-time, influisce
su quello che è il tempo di esecuzione di un algoritmo, che non può essere conosciuto deterministicamente e
quindi non può essere usato come valore di riferimento. E’ stato fin da subito necessario stabilire un sistema che
non dipendesse in alcun modo dalla tecnologia e dall’ambiente di esecuzione del codice.

2. Valutazione asintotica rispetto alla dimensione dell’input


La valutazione della complessità computazionale viene dunque effettuata in termini di numero di operazioni che
l’algoritmo eseguirà, rispetto alla dimensione dell’input, in particolare al crescere asintotico della dimensione
dell’input. Su input molto piccoli di solito esiste poca differenza tra il numero di operazioni ( e quindi il tempo di
esecuzione) di algoritmi con diversi valori di complessità. Le considerazioni si fanno significative dal punto di vista
teorico, ma soprattutto dal punto di vista pratico, quando la dimensione dell’input cresce molto, e la complessità
computazionale aumenta di diversi ordini di grandezza, rischiando di comportare il fatto che l’algoritmo non
riesca a dare risultato in tempi utili. Non è necessario effettuare una valutazione precisa di quello che è il numero
di operazioni che un algoritmo svolge. La cosa importante è determinare un limite superiore, un upperbound per
questo numero, che corrisponde al numero massimo di operazioni che l’algoritmo potrebbe eseguire Per alcuni
classi di algoritmi, la complessità computazione può variare in base al valore dell’input, non alla sua dimensione.
Per esempio cercare un elemento in una lista è un algoritmo il cui costo varia in funzione della dimensione della
lista. Calcolare invece il valore del fattoriale di un numero richiede un numero di operazioni che dipende dal valore
del numero stesso, sebbene esso continui a stare nello spazio di memorizzazione di un intero.

3. Cicli innestati e scorrimenti di strutture dati


Per valutare la complessità computazionale di un algoritmo bisogna ”contare le iterazioni”. Non conta il singolo
confronto tra variabili, la singola operazione algebrica, la creazione di un numero costante di variabili di ugual
dimensione. Conta quante volte vengono eseguite queste operazioni. Di fatto vanno contati i cicli, la cui lunghezza
dipende dalla dimensione o dal valore numerico dell’input, per esempio quante moltiplicazioni si fanno per calco-
lare un fattoriale ( dipende dal numero), quante volte viene eseguito lo scorrimento di una struttura dati (dipende
dalla dimensione). Eventuali cicli innestati (la cui lunghezza dipende tipicamente dalla dimensione di una struttura
dati, vanno moltiplicati tra di loro.

4. O(n)
Spesso non è possibile, ma in generale non è assolutamente necessario, contare precisamente quante operazioni
svolgerà un algoritmo. E’ necessario indicare al massimo quante potrebbe svolgerne, quello che è definito Upper-
bound. E’ esattamente ciò che sta a significare la notazione O(n). Matematicamente:

f (n) = O(g(n)) ⇔ ∃ α > 0, n0 > 0 | f (n) <= αg(n)), ∀n > n0 (1)


La 2 si legge: f(n) è un ”O grande” di g(n) se e solo se esistono un α > 0 ed un n0 > 0 tali che f (n) <= αg(n))
per ogni n > n0 Graficamente:

Fig. 1. Notazione O(g(n))

La fig. 1 rappresenta graficamente il concetto di f (n) = O(g(n)). Se noi diciamo che la complessità di un metodo
è O(n2 ), vuol dire che non conosciamo esattamente il numero di operazioni che esso esegue, ma sappiamo che tale
numero è limitato superiormente dalla funzione n2 . La complessità che stiamo cercando di definire corrisponde
alla f (n) nel grafico: non la conosciamo perfettamente, sappiamo che è limitata superiormente da g(n), a cui
corrisponde n2 .

5. ω(n)
La notazione ω(n) , molto spesso indicata anche come o(n) ( letto: O piccolo di n), rappresenta il caso spec-
ulare rispetto alla notazione O(g(n)): se questa rappresenta un upperbound, la notazione ω(n) rappresenta un
lowerbound, per la funzione che stiamo considerando. Matematicamente
f (n) = ω(g(n)) ⇔ ∃ α > 0, n0 > 0 | f (n) >= αg(n)), ∀n > n0 (2)
Graficamente:

Fig. 2. Notazione ω(g(n))

La fig. 2 rappresenta graficamente il concetto di f (n) = ω(g(n)). Se noi diciamo che la complessità di un metodo
è ω(n), vuol dire che non conosciamo esattamente il numero di operazioni che esso esegue, ma sappiamo che tale
numero è limitato inferiormente dalla funzione n. La complessità che stiamo cercando di definire corrisponde
alla f (n) nel grafico: non la conosciamo perfettamente, sappiamo che è limitata inferiormente da g(n), a cui
corrisponde n.
6. Θ
La notazione f (n) = Θ(g(n)) indica una caratterizzazione più precisa della funzione che descrive la complessità
computazionale di un metodo. in particolare:
(
f (n) = O(g(n))
⇔ f (n) = Θ(g(n)) (3)
f (n) = ω(g(n))
La 3 ci dice che la funzione f può essere definita Θ(g(n)) se è contemporaneamente O(g(n)) e ω(g(n)) . Cioè se
la funzione f è limitata sia superiormente che inferiormente da una funzione g... di fatto le 2 funzioni coincidono.
Attenzione al fatto che la funzione f deve essere contemporaneamente O(g(n)) e ω(g(n)) per essere Θ(g(n)).
Questo vuol dire che se f (n) = Θ(g(n)) possiamo dire senza errore che f (n) = O(g(n)), o che f (n) = ω(g(n)),
ma non viceversa ( considerando le singole condizioni o ed ω).

7. Caso migliore e caso peggiore


Per alcuni algoritmi è possibile individuare una particolare congigurazione dell’input in corrispondenza della
quale si ottiene un costo computazionale diverso dal ”solito”. Per esempio se in un algoritmo di ricerca troviamo
come primo elemento della collezione proprio l’elemento cercato, il costo di quella particolare chiamata non
sarà lineare rispetto all’input, ma sarà costante. Attenzione: la complessità computazionale si valuta rispetto alla
dimensione dell’input, il caso migliore ed il caso peggiore rispetto alla configurazione dell’input, non alla sua
dimensione. Il caso migliore della ricerca non è quando la lista è lunga 0 elementi, ma quando può essere anche
molto lunga, ma il primo elemento è quello che cerco. Tutti gli algoritmi avrebbero costo costante su strutture
vuote. Analogamente al caso migliore, si può individuare il caso peggiore, cioè una particolare configurazione
dell’input in cui il costo dell’algoritmo è esattamente il massimo che si possa pagare. In un algoritmo di ricesca il
caso peggiore corrisponde a quando non si trova proprio l’elemento che si sta cercando, e non si può asserire di
non averlo trovato se non prima si è completato lo scorrimento della struttura, indipendentemente da quanto essa
sia lunga. Ovviamente non tutti gli algoritmi presentano un caso migliore ed un caso peggiore. Il caso medio viene
calcolato con strumenti di natura probabilistica, e non deterministica, qualora sia possibile muovere delle ipotesi
sul contenuto dell’input.

8. Complessità Spaziale
Per complessità spaziale di un algoritmo si intende la quantità di risorse che esso utilizza in termini di spazio di
memoria, escluso lo spazio per l’allocazione dell’input e dell’output dell’algoritmo stesso, che ovviamente vanno
necessariamente allocati, indipendentemente dalla strategia di soluzione implementata. Dunque la complessità
spaziale di un algoritmo è data dalla dimensione delle strutture dati che esso alloca come supporto all’esecuzione
dei calcoli, espressa in funzione della dimensione dell’input, usando le sopracitate notazioni 0, ω e Θ , con la
medesima semantica adottata per esprimere la complessità computazionale. Un algoritmo che prende in ingresso
una lista lunga n elementi, e restituisca un intero, se non costruisce alcuna struttura dati di supporto ha complessità
spaziale costante, indicata con T heta(1) ( si può dire senza errore O(1)). Se invece, per qualche motivo, alloca
una lista di supporto di dimensione pari alla dimensione della struttura in ingresso, e l’output è sempre un intero,
ha occupato uno spazio di memoria lineare rispetto alla dimensione dell’input. La sua complessità spaziale sarà
O(n).Quanto appena detto vale solo per gli algoritmi iterativi.

9. La ricorsione
La valutazione della compelssità computazionale per la ricorsione non differisce molto da quella della valutazione
della complessità computazionale di algoritmi iterativi. Si ottiene ”contando” quante chiamate vengono eseguite,
che di solito non contengono altri elementi di grande onere computazionale al loro interno. La complessità com-
putazionale di un algoritmo ricorsivo è dunque data dal numero totale delle chiamate che esso effettuerà, poichè
ovviamente il metodo finisce quando tutte esse saranno finite. La complessità spaziale degli algoritmi ricorsivi
è data dal numero di chiamate a metodo contemporaneamente aperti in memoria. Ogni chiamata corrisponde
all’apertura di un record di attivazione nello stack, e quindi all’occupazione di uno spazio, non molto grande,
ma che va moltiplicato per il numero di chiamate. Le chiamate totali che esegue un metodo ricorsivo spesso non
sono tutte contemporaneamente aperte in memoria ma alcune si chiudono per lasciare posto ad altre. Ecco perchè
non conta la totalità delle chiamate come complessità spaziale, ma il numero massimo di chiamate contempo-
raneamente aperte in memoria. Tipicamente la ricorsione peggiora la complessità spaziale rispetto alla tecnica
iterativa.

Potrebbero piacerti anche