Complessità
Nell' Informatica è importante porsi questa domanda: Quanto costa risolvere un dato problema? Questo è l'argomento che viene trattato nella Complessità Computazionale, e si articola in due problemi: - Quanto dura l'esecuzione di un programma, fino al suo completamento? - Di quanta memoria ha bisogno un programma per poter essere eseguito? Parleremo dunque di complessità temporale e spaziale. Tipicamente, si considerano varie istanze di una classe di problemi, dove ciascuna istanza ha una dimensione. Per esempio, considera il calcolo Z = [1, 2, 3] + [4, 5, 6]; Che è una istanza specifica del problema di sommare due vettori di dimensione 3, che a sua volta è un caso speciale del problema generale di calcolare la somma di due vettori di dimensione arbitraria.
Per calcolare la durata dell'esecuzione di un programma abbiamo bisogno di Prevedere tutte le operazioni che saranno eseguite dal programma, avo una istanza di un problema, e prevedere quanto tempo durerà ciascuna di esse. In pratica, quando facciamo una stima, spesso: - Selezioniamo e contiamo solo un sottoinsieme delle operazioni (che consideriamo rilevanti) - Assumiamo che tali operazioni prano la stessa quantità di tempo Queste ipotesi sono solo una prima ma in prima analisi sono sufficienti. approssimazione (a volte grezza), Esse implicano anche che viene esaminato solo l'algoritmo (cioè la sequenza logica delle operazioni), ignorando dettagli come il processore specifico, l'interprete, il programma etc. In molti casi conteremo le operazioni aritmetiche su dati in virgola mobile.
Useremo la notazione asintotica: Dato un algoritmo per risolvere un problema, diciamo che il suo tempo di esecuzione T(n) è O(f(n)) [si legge O di f di n ] se ci sono due costanti c ed n tali che 0 T(n) c f(n), per tutti gli n n 0 Nota che il tempo effettivo di esecuzione di un algoritmo non è necessariamente lo stesso per tutte le istanze di dimensione n: abbiamo implicitamente definito prima la com mplessità nel caso peggiore, chiedo un limite superiore che fosse valido per tutte le istanze. Possiamo anche definire la complessità nel caso medio tra tutte le istanze di dimensione n. Come esempio: sommare due vettori di dimensione n richiede sempre n operazioni aritmetiche, ma ordinare un vettore di dimensione n dipe dal contenuto del vettore.
Casi speciali: O(1): un algoritmo che richiede un tempo costante, che non dipe dalla dimensione dell'input O(log(n)): un algoritmo logaritmico O(n): un algoritmo lineare k O(n ): un algoritmo polinomiale n O(a ): un algoritmo esponenziale Gli algoritmi polinomiali sono considerati trattabili. In generale, preferiamo algoritmi con limite asintotico migliore, ovvero più basso: 2 un algoritmo O(n ) da un certo valore di n in poi sarà migliore di uno 3 O(n ), anche se il coefficiente c del primo algoritmo è più grande.
Esempio: La Trasformata di Fourier Discreta (DFT) 2 Così come è definita costa O(N ); nel 1965 Cooley e Tukey scoprirono un algoritmo O(N log(n)) chiamato Trasformata di Fourier Veloce (FFT). Grazie alla FFT esistono: CD JPEG DVD Digital TV Telefoni cellulari Controlli digitali (ABS, ESP, Iniezione common rail);
Qualche osservazione: 3 Se stiamo gesto istanze piccole di un problema, probabilmente un algoritmo O(n ) è 3 2 migliore: 5n <100n per tutti gli n minori di 20. Alcuni algoritmi ottimi sono buoni sono su input astronomicamente grandi, e sono inutili quindi in pratica. Se un programma deve essere usato solo una o due volte, il tempo impiegato per scriverlo diventa importante: se è più facile scrivere l'algoritmo più lento, può essere preferibile. In alcuni casi l'algoritmo più veloce pre troppo spazio. In alcuni casi un algoritmo può essere migliore nel caso medio ma allo stesso tempo pessimo nel caso peggiore (per esempio il Quicksort). Infine: non provare mai a migliorare un programma senza conoscere esattamente (misurandola) la sua performance.
Come calcolare un conteggio delle istruzioni: I semplici calcoli scalari hanno un costo di O(1) Il costo di una sequenza di operazioni è la somma dei costi delle singole operazioni Il costo di un ciclo è la somma del costo di ciascuna iterazione, includo possibilmente il costo per testare la terminazione Operazioni sugli array hanno un costo che può essere calcolato espando l'operazione in un equivalente ciclo Una istruzione condizionale ha il costo del cas so peggiore, ovvero la parte più costosa tra quella compresa nell'if e quella compresa nell'else. Per calcolare il costo medio dovremmo moltiplicare le due ramificazioni per la probabilità che ciascuna delle due venga eseguita, più il costo dell'operazione di scelta in sé. Queste semplici regole sono in teoria tutto, ma le difficoltà sono nei dettagli.
Istruzioni che coinvolgono quantità scalari a = 2.5; b = a*a+1; c = b^3; %0 o 1: il costo di una assegnazione spesso è ignorato %qui ci sono due operazioni in virgola mobile; % b^3 è b*b*b, quindi di nuovo 2 operazioni; for k = n1:n2 % Questa è eseguita (n2-n1+1) volte c=a+b % il costo qui è sempre 1 % costo totale: 1*( (n2-n1+1) if (mod(k,2) ==0) c=a*b+c; else b=b+1; % se K è un nume ero intero random, la probabilità è 50% % è il caso è peggiore: il blocco dell'if ha costo 2 %il costo medio è 1.5 %più due per calcolare MOD() == 0
Cicli: per calcolare il costo bisogna calcolare Dove: i è una iterazione I è l'insieme di tutte le iterazioni C(i) è il costo dell' i-esima iterazione Spesso, ma non sempre, il costo per iterazione è costante; calcolare I è semplice per cicli FOR, ma non banale per cicli WHILE. Per cicli annidati corrispondono somme multiple:
Somme di vettori di dimensione n: c = a + alpha*b for i=1:n c(i) = a(i) + alpha*b(i) Costo:
Prodotto matrice-vettore (matrice m x n): y = y + A*x
Prodotto matrice-matrice (matrice m x k x n): C = C + A*B
a*d richiede n*(n+n)=2n^2 operazioni (n volte un prodotto tra vettori di dimensione n) sum() richiede n somme a+sum( ) richiede n^2 somme Il numero di operazioni richieste è eseguito n-1 volte. Quindi abbiamo in totale (n-1)(n^2+n+n+2n^2+n+n^2)=(n-1)(4n^2+3n)=4n^3-4n^2+3n^2-3n=4n^3-n^2-3n
Data una matrice A nxn, qual è il costo di questo algoritmo? if A(1,1)<=10 for i=1:size(a,1) A = A + A(1,:); else for j=1:size(a,1) B = A * A(j,:)' %costo 1, un confronto %il ciclo è ripetuto n volte %ogni riga della matrice viene aumentata %della prima riga: n^2 operazioni %n volte % n prodotti ed n somme per ciascuna riga=n*2n=2n^2 %quindi 2n^3 operazioni CASO PEGGIORE Quindi in totale 1+n(2n^3)=2n^4
Sia data una matrice A nxn for i=1:n %costo n s=sum(a(i,:)); %somma gli elementi di una riga: costa n if(mod(s,2)==0) % costo 2 for j=1:n %ripeto tutto n^2 volte for k=1:n app=a(i,j); %costo 1 A(i,j)=A(i,k); %costo 1 A(i,k)=app; %costo 1 %quindi 3n^2 else d=diag(a); %costo 1 A(i,:)=A(i,:)*d; %costo 2n m=mean(d); %costo n+1 (n prodotti ed una divisione) A(i,:)=A(i,:)*m; %costo n %quindi 4n + 2 %Il costo è n (n + 3n^2) =3n^3+n^2
ESERCIZIO: Sia data una matrice A nxn. Calcolare il costo del seguente algoritmo M=0; n=size(a,1); for i=1:n for j=1:n M=M+A(i,j) %costo 1 End %costo totale n^2 A = mod(a,m) for i=1:n %doppio ciclo costa n^2 for j=1:n if A(i,j)==0 d=diag(a) %costo 0 a=mean(d) %costo n A(i,j)=a %costo 0 else %costo totale del blocco «IF» n for k=1:n B=A*A(k,:) %costo 2n^2 %costo totale 2n^3 COSTO TOTALE = n^2 + n^2 * 2n^3 = 2 n^5+n^2 T(n) è O(2n^5+^2)