-
.
Analisi di Complessità degli Algoritmi
Una delle cose più importanti che ho imparato all'Università è valutare la complessità di un algoritmo ed è sicuramente un aspetto che fa la differenza tra un programmatore di medio-basso livello e uno invece di medio-alto.
Finite le superiori ero consapevole che uno stesso risultato poteva essere ottenuto con algoritmi diversi, ma non avevo troppa idea che due algoritmi diversi possono risolvere lo stesso problema usando un numero di risorse radicalmente diverse.
Imparare a creare algoritmi che ottimizzano il più possibile il consumo di risorse è fondamentale per chiunque voglia fare il programmatore.
Ricordo che alla Laurea Triennale il mio professore di Algoritmi e Strutture Dati fece un esempio che mi colpì molto, immaginando due macchine: una con elevate prestazioni e l'altra invece con basse prestazioni.
Sulla macchina potente è stato inserito la risoluzione di un algoritmo fortemente inefficiente, mentre sulla macchina più debole è stato inserito la risoluzione di un algoritmo fortemente efficiente: il risultato è stato che la macchina meno prestante ha calcolato più velocemente il risultato.
Quindi non pensate di scampare alle vostre responsabilità di programmatori sperando solo in un miglioramento delle risorse hardware!
I tipi di Complessità
Per poter capire quante risorse consuma un nostro algoritmo è fondamentale distinguere due tipi di Complessità Computazionale:- Complessità Spaziale: Riferito alla memoria occupata da un algoritmo e quindi tiene conto di tutte le variabili che vengono usate dall'algoritmo, fortunatamente questo tipo di complessità è maggiormente gestibile dall'Hardware e anche dal programmatore, ad esempio ricorrendo alla deallocazione delle variabili che non servono durante l'algoritmo.
- Complessità Temporale: Riferito al numero di istruzioni eseguite dal nostro algoritmo. Non fatevi confondere dal nome, i secondi non centrano nulla! Su questa i programmatori lavorano maggiormente ed è meno gestibile dall'Hardware
Le tre tipologie di Scenari
Talvolta gli algoritmi non richiedono sempre lo stesso numero di istruzioni o di memoria e quindi sostanzialmente la complessità computazionale può variare in un range di valori:- Caso migliore: Il numero minimo di istruzioni che un algoritmo può eseguire (indicato con Ω);
- Caso medio: Il numero medio di istruzioni che un algoritmo può eseguire (indicato con Θ);
- Caso peggiore: Il numero massimo di istruzioni che un algoritmo può eseguire (indicato con Ο)
Ad esempio immaginiamo un algoritmo di ricerca lineare di un numero all'interno di un vettore di elementi.
Il vettore è composto da N numeri, ad esempio N = 5:
Vettore = [10,24,23,14,75]
Se il numero che cerco è il valore 10 avrò il caso migliore poiché mi verrà restituito immediatamente il risultato.
Se il numero che cerco è l'ultimo (75) oppure non è presente avrà eseguito 5 operazioni di confronto invece che 1 come al caso precedente.
La media tra questi valori che posso individuare rappresenta invece il caso medio.
In ambito informatico solitamente si analizzano i casi peggiori poiché sono quelli che riescono a creare una metrica di confronto tra algoritmi per valutare la loro efficienza.
Un Esempio pratico
Per aiutarvi a capire meglio vi riporto un esempio pratico che può aiutarvi a capire come funziona in modo molto elementare il calcolo della complessità temporale:
Dato il seguente codice:HTMLfor(int i = 0; i<n; i++) {
for(int j = 1; j<n; j++) {
M1[i][j] = M2[i][j-1];
}
}
Nella complessità temporale vengono conteggiate solamente le operazioni di confronto e di assegnamento, mentre le operazioni di dichiarazione di una variabile e di input/output non vengono conteggiate.
In questo caso abbiamo un primo ciclo for che ha la seguente complessità computazionale:- i = 0 : comporta un'unica istruzione 1;
- i < n : comporta n+1 confronti;
- i++ : comporta n incrementi
Quindi la complessità di questo primo ciclo for sarà: 1 + n + 1 + n = 2n + 2
Mentre nel secondo ciclo for dobbiamo tenere conto che verrà eseguito n volte e quindi moltiplichiamo n per:- j = 1 : comporta un'unica istruzione 1;
- j < n : comporta n confronti;
- j++ : comporta n-1 incrementi;
- M1[i][j] = M2[i][j-1] comporta un'unica istruzione
Quindi la complessità del secondo ciclo for sarà: n * (1 + n + n - 1 + 1) = n * (2n + 1) = 2n2 + n
Facendo la somma dei due cicli for: 2n + 2 + 2n2 + n = 2n2 + 3n + 2
L'ordine di grandezza nel caso peggiore diventa O(n2) e quindi si tratta di una complessità polinomiale di tipo quadratica.
Scala della Complessità:
Spesso per semplificare la visualizzazione della complessità per chi fa parte del settore si usa l'ordine di grandezza per specificare vari tipi di scala: in questo caso ci sono i principali:- O(n!) con k>0 complessità fattoriale;
- O(kn) con k>0 complessità esponenziale ;
- O(nk) con k>0 complessità polinomiale (con k = 2 quadratica, con k = 3 cubica);
- O(n·log n) complessità pseudolineare;
- O(n) complessità lineare;
- O(log n) complessità logaritmica;
- O(k) complessità costante;
Impostando come valore di K = 2 e come valori di N i valori che vanno da 1 a 10 otteniamo il seguente grafico con i seguenti andamenti.
Il grafico viene riportato in 3 parti evitando così di usare una scala logaritmica e semplificare anche a chi non ha conoscenze pregresse.
Nella prima parte viene mostrato che la complessità fattoriale ottiene dei valori enormi, al punto da vedere tutte le altre funzioni attaccate sullo 0 dopo appena N = 7.
Nella seconda parte viene rimossa la complessità fattoriale e potete notare comunque come una complessità esponenziale ottiene valori decisamente più alti di una complessità polinominiale all'aumentare di N.
Nella terza parte viene rimossa anche la complessità esponenziale, mostrando la differenza fra le complessità rimanenti e come potete vedere quella che ottiene valori più alti all'aumentare di N è quella polinomiale.
La differenza tra P e NP
Fino alla complessità polinomiale si dice che i problemi sono trattabili e fanno parte della classe P.
Verissimo che una complessità polinomiale può ottenere comunque valori molto alti e difficili da gestire per una macchina (ad esempio già una complessità polinomiale cubica può dare problemi) ma con un miglioramento dell'hardware è possibile aumentare molto il margine di miglioramento dell'esecuzione degli algoritmi.
La classe NP invece è una classe molto ampia che non è alternativa alla classe P, anzi la contiene.
In questa classe di problemi oltre alla classe P, rientrano tutti gli algoritmi che hanno una complessità superiore al polinomiale ma esiste un algoritmo polinomiale in grado di verificare la sua soluzione.
Mi fermo su questa piccola introduzione che mi piacerebbe usare questo post come base per fare un interessante post su queste differenze tra P e NP!. -
.
troppo tecnico per gnogno . -
.
Un luogo dove qualcuno ancora pensa a te, quello è il luogo che puoi chiamare casa...
- Group
- Member
- Posts
- 235
- Status
- Anonymous
bel post, le immagini sono andate però . -
.
Da telefono due non le visualizzavo, mentre da PC non ho problemi.