Complessità in tempo e caso pessimo
In questa pagina 8
Perché serve un'analisi teorica
Il tempo di esecuzione di un programma dipende dall'istanza (a parità di taglia, input diversi possono costare molto diversamente: pensa a InsertionSort su un array già ordinato o invertito), dall'hardware e dal software. Misurarlo (in Java con System.currentTimeMillis() prima e dopo l'esecuzione) ha limiti: non copre tutti gli input, richiede di implementare l'algoritmo, e il confronto tra algoritmi dipende da macchina e implementazione.
Servono quindi un'analisi che consideri tutti gli input, permetta di confrontare algoritmi senza il tempo esatto, e parta dallo pseudocodice. L'approccio: caso pessimo in funzione della taglia, conteggio di passi nel modello RAM (vedi Problemi computazionali e algoritmiProblema computazionale come insieme di coppie (istanza, soluzione); algoritmo e modello di calcolo RAM; pseudocodice; taglia di un'istanza; ADT e struttura dati concreta; esempio svolto con ricerca lineare e binaria in un array ordinato.Problemi computazionali e algoritmi →), analisi asintotica (vedi Notazione asintoticaDefinizioni di O, Omega, Theta e o piccolo con le costanti c ed n0; esempi con costanti esplicite; proprietà (polinomi, esponenziali, logaritmi, somme, implicazioni tra notazioni); sommatorie notevoli; terminologia (logaritmica, lineare, polinomiale, esponenziale).Notazione asintotica →).
Complessità al caso pessimo
Sia il numero di operazioni eseguite da sull'istanza . La complessità in tempo al caso pessimo è
Altre analisi (caso medio, probabilistica) esistono, ma qui si usa il caso pessimo.
Il metodo pratico. Trovare l'istanza peggiore e contare i passi esatti è difficile e inutile. Si cercano due funzioni semplici:
- limite superiore : si argomenta che per ogni abbastanza grande, ogni istanza di taglia costa al più operazioni;
- limite inferiore : si argomenta che per ogni abbastanza grande esiste un'istanza di taglia che costa almeno (a volte è comodo mostrare che tutte le istanze costano tanto).
Se coincidono si ha (stima stretta). La funzione deve essere la più vicina possibile al vero andamento (tight) e la più semplice (niente costanti, niente termini di ordine inferiore).
Esempio: arrayMax
Algoritmo arrayMax(A, n)
Input: array A di n >= 1 interi
Output: massimo di A
currMax <- A[0]
for i <- 1 to n-1 do
if currMax < A[i] then currMax <- A[i]
return currMaxFuori dal ciclo ci sono operazioni in numero costante; il ciclo fa iterazioni, ciascuna di costo costante. Esistono quindi con : . Non servono i valori delle costanti.
Esempio: prefix averages
Dato , calcolare .
prefixAverages1(X, n) prefixAverages2(X, n)
for i <- 0 to n-1 do s <- 0
a <- 0 for i <- 0 to n-1 do
for j <- 0 to i do s <- s + X[i]
a <- a + X[j] A[i] <- s / (i+1)
A[i] <- a / (i+1) return A
return ANella prima versione il ciclo interno fa iterazioni, quindi il costo è proporzionale a . Nella seconda si riusa la somma precedente: . Stesso problema, algoritmi di efficienza molto diversa.
Esempio: InsertionSort
Algoritmo InsertionSort(S)
for i <- 1 to n-1 do
curr <- S[i]; j <- i-1
while j >= 0 AND S[j] > curr do
S[j+1] <- S[j]; j <- j-1
S[j+1] <- curr- Ogni istanza di taglia esegue al più operazioni: ciclo esterno volte, interno al più volte, . Quindi .
- Ogni istanza esegue almeno operazioni (il ciclo esterno gira sempre volte): .
- Esiste un'istanza con operazioni: l'array in ordine decrescente, dove ogni elemento percorre tutto il prefisso ( confronti; verificato: per sono ). .
Da e : . Sull'array già ordinato servono solo confronti: per questo si parla di caso pessimo.
Efficienza asintotica e tempi
Se allora è asintoticamente più efficiente di . Regola pratica: complessità polinomiale (o migliore) algoritmo efficiente; esponenziale inefficiente. Taglia massima risolvibile in tempo (con in nanosecondi):
| 1 secondo | 1 minuto | 1 ora | |
|---|---|---|---|
| (illimitata) | illimitata | illimitata | |
Limiti dell'analisi
- Caso pessimo: può riguardare istanze patologiche mentre quelle di interesse costano meno (esempio: QuickSort); si può restringere il dominio delle istanze o analizzare il caso medio.
- Asintotica: le costanti ignorate possono contare. è e è peggiore asintoticamente, ma solo per , cioè mai in pratica. Così batte solo per .
Errori comuni
- Dire " è sempre più veloce di " perché : l'asintotica vale per grandi, le costanti contano per piccoli.
- Calcolare il caso pessimo scegliendo un'istanza a caso, o provare solo il limite superiore e dichiarare .
- Mettere costanti o termini minori nel risultato ( invece di ).
Versione ripasso
- Caso pessimo: , con numero di operazioni sull'istanza (modello RAM, vedi Problemi computazionali e algoritmiProblema computazionale come insieme di coppie (istanza, soluzione); algoritmo e modello di calcolo RAM; pseudocodice; taglia di un'istanza; ADT e struttura dati concreta; esempio svolto con ricerca lineare e binaria in un array ordinato.Problemi computazionali e algoritmi →).
- Metodo: limite superiore = ogni istanza di taglia costa ; limite inferiore = esiste un'istanza che costa ; tight e semplice (vedi Notazione asintoticaDefinizioni di O, Omega, Theta e o piccolo con le costanti c ed n0; esempi con costanti esplicite; proprietà (polinomi, esponenziali, logaritmi, somme, implicazioni tra notazioni); sommatorie notevoli; terminologia (logaritmica, lineare, polinomiale, esponenziale).Notazione asintotica →).
- arrayMax: . prefixAverages: con ciclo doppio ; riusando la somma corrente .
- InsertionSort: (ogni istanza), (ogni istanza), (array decrescente, confronti) ; array ordinato: confronti.
- Efficienza: asintoticamente più efficiente; polinomiale = efficiente, esponenziale (: in 1 s) = inefficiente.
- Limiti: caso pessimo può essere patologico; costanti ignorate possono dominare ( contro ).
- Algoritmi di esempio:
arrayMax(un ciclo di iterazioni);prefixAverages1(doppio ciclo, somme) controprefixAverages2(somma correntes <- s + X[i]). - Tempi massimi in 1 s ( in ns): ; : ; : ; : illimitata.
- Errori: " quindi sempre più veloce"; provare un solo limite e scrivere ; costanti nel risultato.