La presentazione è in caricamento. Aspetta per favore

La presentazione è in caricamento. Aspetta per favore

Analisi di un algoritmo

Presentazioni simili


Presentazione sul tema: "Analisi di un algoritmo"— Transcript della presentazione:

1 Analisi di un algoritmo
F. Bombi 17 novembre 2003

2 Analisi delle risorse Per valutare la qualità di un algoritmo siamo interessati a valutare le risorse di cui ha bisogno un programma che lo realizzi Consideriamo i componenti principali di un calcolatore Unità centrale di elaborazione (CPU) Memoria centrale Dispositivi di memoria di massa Nella nostra trattazione elementare abbiamo studiato solo algoritmi che operano con i dati in memoria per cui analizzeremo solo l’utilizzo della CPU e della memoria centrale

3 Utilizzo della memoria
Nel nostro modello semplificato di calcolatore possiamo pensare che la memoria centrale sia divisa in tre aree distinte o segmenti Il segmento statico utilizzato per contenere il codice del programma e le costanti Il segmento dinamico (free store o heap) utilizzato per contenere gli oggetti creati dinamicamente e soggetto a garbage collection Lo stack (run time stack) utilizzato per l’allocazione delle variabili locali create all’interno di un blocco al momento della sua attivazione

4 Il segmento statico Gli algoritmi elementari, oggetto del nostro corso, portano a programmi di modeste dimensioni per cui l’utilizzo di memoria per la parte statica può essere trascurato Non dobbiamo comunque dimenticare che è possibile aumentare la velocità di un programma trasformando un ciclo in una sequenza di istruzioni senza confronti e salti. In questo modo si risparmia sulla risorsa tempo a spese dell’impiego di memoria statica

5 Memoria dinamica L’ingombro della memoria dinamica è invece un parametro che ci può aiutare a scegliere fra soluzioni diverse di un problema In certi casi infatti è possibile individuare soluzioni che richiedono un tempo minore a spese di un maggior utilizzo della memoria. Ad esempio: mergesort richiede uno spazio di memoria doppio, rispetto agli algoritmi elementari, per contenere una copia del vettore da ordinare un dizionario in una tabella, quando le chiavi sono sparse, richiede di utilizzare molto più spazio di una rappresentazione con una lista

6 Utilizzo del run time stack
Lo stack durante l’esecuzione di un programma contiene le variabili dichiarate all’interno del blocco che costituisce il corpo di ogni metodo Le variabili vengono allocate al momento dell’attivazione del blocco e rilasciate quando si esce dal blocco per cui occupano spazio solo durante le chiamate al metodo L’utilizzo dello stack può essere un fattore critico nei metodi ricorsivi in quanto un nuovo blocco viene creato ad ogni attivazione ricorsiva di un metodo senza rilasciare il blocco precedente Le dimensioni dello stack sono quindi proporzionali alla profondità di ricorsione che rappresenta di conseguenza un parametro importante per valutare la qualità della soluzione adottata

7 Utilizzo della CPU: il tempo
Nel nostro modello l’unità centrale di elaborazione esegue ciclicamente una istruzione alla volta, di conseguenza il tempo necessario alla soluzione di un dato problema è proporzionale al numero di cicli (inversamente proporzionale alla frequenza di clock del processore utilizzato e proporzionale al numero di istruzione eseguite) Il tempo necessario a risolvere un dato problema rappresenta spesso il parametro più importante nella valutazione della qualità di un algoritmo Infatti mentre possiamo immaginare di aumentare le dimensioni della memoria di una macchina a piacere, il tempo non è un bene replicabile Vedremo come anche aumentando la frequenza di clock algoritmi inefficienti non consentono di risolvere problemi di grandi dimensioni

8 Il tempo Per valutare il tempo necessario alla soluzione di un dato problema sarebbe necessario stimare il numero di istruzioni in funzione delle dimensioni o taglia del problema La stima è impossibile quando si utilizzi un linguaggio ad alto livello in quanto il numero di istruzioni macchina corrispondente ad una istruzione nel linguaggio ad alto livello dipende dal compilatore impiegato È comunque significativo stimare quale sia il comportamento asintotico del numero di istruzioni necessario alla soluzione del problema in funzione della taglia, a meno di una costante Tale stima viene comunemente indicato come complessità temporale dell’algoritmo impiegato

9 Comportamento asintotico
Le funzioni che legano il numero di istruzioni necessarie alla soluzione di un problema alla sua taglia n non sono, come vedremo con qualche esempio, in genere continue e non ammettono un limite per n tendente all’infinito Per descrivere il comportamento asintotico ci dobbiamo accontentare di una stima del limite superiore utilizzando la notazione O-grande Indichiamo con O(g(n)) un insieme di funzioni I = {f’(n), f”(n), …} per ognuna delle quali esistono due costanti positive c e n0 tali per cui 0  f(n)  c•g(n) per ogni n  n0 per indicare che una funzione appartiene all’insieme si dice f(n) è O(g(n))

10 f(n) c•n f(n)= O(n) n n0 n

11 Attenzione Fare attenzione che dalle definizioni date una funzione che è O(n) è anche O(n2) oppure O(n3) In genere nel valutare il comportamento asintotico si cercherà di trovare la funzione che cresce più lentamente e che quindi limita in modo più stretto il comportamento asintotico

12 Esempio: ricerca in un vettore
Rivediamo, come ripasso, l’analisi dell’algoritmo di ricerca di un elemento in un vettore, il metodo restituisce la posizione dell’elemento cercato se presente, -1 se non presente int scansione (Comparable x, Comparable[] v, int n) { for (int i = 0; i < n; i++) if (x.compareTo(v[i]) == 0) return i; return -1; } Per chiarezza conviene per prima cosa riscrivere il programma trasformando il ciclo for in un ciclo while equivalente

13 Se supponiamo ora di conoscere il tempo necessario per eseguire
int scansione (Comparable x, Comparable[] v, int n) { int i = 0; while (i < n) { if (x.compareTo(v[i]) == 0) return i; i++; } return -1; Se supponiamo ora di conoscere il tempo necessario per eseguire ciascuna istruzione siamo in grado di valutare il tempo necessario a risolvere il problema determinando quante volte ciascuna istruzione verrà eseguita in funzione dei dati in ingresso. Indichiamo con ki il tempo necessario all’esecuzione di ciascuna istruzione, l’unica cosa importante è essere sicuri che le costanti non dipendano dalla taglie del problema che nel nostro caso è la lunghezza n del vettore su cui si deve effettuare la ricerca

14 k3 { if (x.compareTo(v[i]) == 0) k4 return i; k5 i++; } k6 return -1;
k1 int i = 0; k2 while (i < n) k3 { if (x.compareTo(v[i]) == 0) k return i; k i++; } k6 return -1; int i = 0; c+1-s while (i < n) c { if (x.compareTo(v[i]) == 0) s return i; c-s i++; } 1-s return -1; Il tempo complessivo necessario a ritrovare l’elemento cercato è dato da: t=f(n)=k1 +(c+1-s)k2 + ck3 + sk4 + (c-s)k5 + (1-s)k6 i = 0 while (i < n) if (x.compareTo(v[i]) == 0) return i i++ return -1 s = 1 successo c = posizione s = 0 insuccesso c = n

15 Il tempo complessivo necessario a ritrovare
l’elemento cercato è dato da: t=f(n)= k1 + (c+1-s)k2 + ck3 + sk4 + (c-s)k5 + (1-s)k6 e quindi: t=f(n)= k1+k2+k6 + s(k4-k2-k5-k6) + c(k2+k3+k5) = a1 + a2 s + a3 c Se l’elemento cercato non è presente: s = 0, c = n t = a1 + a3 n Se l’elemento cercato è presente al posto c: s = 1 Caso migliore (c = 1) t = a1 + a2 + a3 Caso peggiore (c = n) t = a1 + a2 + a3n Caso medio (tutte le posizioni sono equiprobabili) t = a1 + a2 + a3(n+1)/2

16 Conclusioni Le costanti k o a delle espressioni appena viste dipendono dal compilatore impiegato e dal processore In particolare sappiamo che sono (in prima approssimazione) inversamente proporzionali al clock della macchina impiegata Indipendentemente da questi parametri possiamo però dire che la ricerca richiede un tempo che è O(n) in caso di insuccesso e, in caso di successo, in media o nel caso peggiore. Il tempo richiesto è invece O(1) nel caso migliore

17 Il paradosso del comportamento asintotico
È ragionevole domandare perché siamo tanto preoccupati dal tempo impiegato da un algoritmo per risolvere un dato problema Sappiamo che ogni 18 mesi i microelettronici sfornano un nuovo processore che ha circa il doppio della potenza di calcolo del precedente, se un problema non è risolvibile con la tecnologia di oggi sarà risolvibile domani? Questo è vero solo se disponiamo di un algoritmo efficiente per la soluzione del problema e ce ne possiamo rendere conto osservando la seguente tabella

18 Tempo in funzione di n 1 10 100 1000 log n 2 3  n 32 n n log n 200
2 3  n 32 n n log n 200 3000 n2 10000 1e6 n3 1e9 2n 1e3 1e30 1e300

19 Dimensioni massime del problema risolvibile
Nella prossima tabella consideriamo le dimensioni di un problema risolvibile in 1 secondo, 1 minuto, 1 ora o 1 giorno supponendo di utilizzare algoritmi che richiedono un tempo pari a 0.1f(n) secondi Possiamo notare che il passaggio dalla prima alla seconda colonna corrisponde all’utilizzo di un tempo 60 volte maggiore oppure, a parità di tempo, di un processore 60 volte maggiore (circa 9 anni di progresso tecnologico)

20 Dimensioni in funzione del tempo
10 f(n) 1 sec 1 minuto 1 ora 1 giorno log n 100 6e3 1e36000 1e864000  n 3.6e5 1.3e9 7.5e11 n 10 600 3.6e4 8.6e5 n log n 250 9.1e3 1.65e5 n2 3 24 190 930 n3 2 8 33 95 2n 9 15 20


Scaricare ppt "Analisi di un algoritmo"

Presentazioni simili


Annunci Google