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 →). In Fondamenti di Informatica lo stesso tema è trattato in modo più elementare in Complessità computazionaleCosto di un algoritmo in funzione della dimensione dell'input; caso peggiore, migliore e medio; notazione O-grande e classi di crescita; come contare i passi di cicli e ricorsioni; costo delle operazioni Python.Complessità computazionale →; qui si formalizza.
Complessità al caso pessimo
Definizione (complessità in tempo al caso pessimo). Sia il numero di operazioni eseguite da sull'istanza . La complessità in tempo al caso pessimo è
Esempio. InsertionSort su elementi fa confronti sull'array ordinato e sull'array invertito : il caso pessimo è il massimo, .
Altre analisi (caso medio, probabilistica) esistono, ma qui si usa il caso pessimo. Il caso medio richiede di assegnare una probabilità a ciascuna istanza e di calcolare un valore attesoIl valore atteso E[X] = Σ x p_X(x) è la media dei valori di X pesata con le loro probabilità (esiste se la serie converge assolutamente); per una funzione g vale E[g(X)] = Σ g(x) p_X(x) senza trovare la legge di g(X), ed E è lineare: E[aX + bY + c] = aE[X] + bE[Y] + c.Valore atteso → del costo; il caso pessimo no, ed è una garanzia valida per qualunque input.
Proprietà (stima con limiti superiore e inferiore). 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; le definizioni di , e con le costanti ed sono in 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 →). La funzione deve essere la più vicina possibile al vero andamento (tight) e la più semplice (niente costanti, niente termini di ordine inferiore).
Esempio. Se ogni istanza di taglia costa al più operazioni e una di esse ne costa almeno , allora .
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, per ogni valore di il ciclo interno ( da a ) fa iterazioni. Il costo totale è quindi proporzionale a
dove si è posto (la somma passa da a ) e si è usata la somma dei primi interi (vedi SommatorieIl simbolo di sommatoria, le sue proprietà (linearità, additività, cambio di indice) e le somme notevoli di Gauss e geometrica.Sommatorie →; la formula si dimostra per induzione: Principio di induzionePer dimostrare una proprietà per ogni n basta il passo base e il passo induttivo da n a n+1.Principio di induzione →). Ad esempio per : . Il termine è un polinomio di grado , quindi . Nella seconda si riusa la somma precedente: s contiene già , basta aggiungere (una sola addizione) invece di rifare tutte le ; il ciclo fa iterazioni di costo costante, . Stesso problema, algoritmi di efficienza molto diversa.
Grafico interattivo: Iterazioni del ciclo interno di prefixAverages1, n(n+1)/2, e del ciclo unico di prefixAverages2, n, in funzione di n: a n = 50 sono 1275 contro 50, e il rapporto (n+1)/2 cresce con n
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 : .
Perché sull'array decrescente. All'iterazione (con ) l'elemento curr = S[i] è minore di tutti i precedenti, quindi S[j] > curr è vero per : si contano confronti (più uno finale quando esce dall'array, che non confronta nulla). In totale (stessa somma, vedi SommatorieIl simbolo di sommatoria, le sue proprietà (linearità, additività, cambio di indice) e le somme notevoli di Gauss e geometrica.Sommatorie →). Traccia su , array :
curr |
array prima | confronti con S[j] > curr veri |
array dopo | |
|---|---|---|---|---|
| 1 | 3 | 1 | ||
| 2 | 2 | 2 | ||
| 3 | 1 | 3 |
Totale , come nell'esempio sopra. Sull'array già ordinato ogni curr è maggiore del precedente, il test fallisce subito e si fa un solo confronto per iterazione: in tutto. Sull'array già ordinato servono solo confronti: per questo si parla di caso pessimo.
Grafico interattivo: Confronti di InsertionSort in funzione di n: n(n−1)/2 sull'array decrescente (caso pessimo, 45 per n = 10) e n−1 sull'array già ordinato (caso migliore): la complessità al caso pessimo è quadratica, ma su input ordinato l'algoritmo è lineare
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 | |
Come si leggono i numeri. Un secondo sono ns, un minuto ns, un'ora ns. Per si risolve e basta. Per si risolve , cioè : , , . Per si risolve , cioè (vedi Esponenziale e logaritmoLa funzione esponenziale a^x (base positiva diversa da 1) e la sua inversa, il logaritmo in base a, con grafici e proprietà.Esponenziale e logaritmo →): , , . Per la condizione dà , un numero con miliardi di cifre. Il messaggio: moltiplicare per 3600 il tempo a disposizione (da 1 s a 1 ora) fa passare l'algoritmo esponenziale da a , mentre quello quadratico guadagna un fattore sulla taglia.
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 : infatti (dividendo per ). Il confronto tra le funzioni per è lo stesso della gerarchia degli infinitiI limiti di base per sciogliere le forme indeterminate delle successioni: potenze, polinomi, il numero e, esponenziali, la gerarchia logaritmi < potenze < esponenziali < fattoriale < n^n e la razionalizzazione.Limiti notevoli e gerarchia degli infiniti → vista in Analisi 1.
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
- Perché un'analisi teorica: il tempo dipende dall'istanza (non solo dalla taglia), dall'hardware e dal software; misurare non copre tutti gli input e non confronta gli algoritmi indipendentemente dalla macchina. Si lavora sullo pseudocodice, 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 →).
- Caso pessimo: , dove è il numero di operazioni di sull'istanza . È una garanzia per ogni input; il caso medio usa invece il valore attesoIl valore atteso E[X] = Σ x p_X(x) è la media dei valori di X pesata con le loro probabilità (esiste se la serie converge assolutamente); per una funzione g vale E[g(X)] = Σ g(x) p_X(x) senza trovare la legge di g(X), ed E è lineare: E[aX + bY + c] = aE[X] + bE[Y] + c.Valore atteso →.
- Esempio: InsertionSort su fa confronti su e su ; il caso pessimo è .
- Due limiti: superiore = ogni istanza di taglia costa al più ; inferiore = esiste un'istanza che costa almeno . Se coincidono, ; dev'essere la più semplice e la più vicina al vero andamento (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: ciclo di iterazioni di costo costante; , quindi . Le costanti non servono.
- prefixAverages1 (, doppio ciclo): ; per sono . prefixAverages2 riusa la somma corrente con
s <- s + X[i]: iterazioni, . - InsertionSort: ogni istanza fa al più operazioni (ciclo esterno volte, interno al più ) e almeno (il ciclo esterno gira sempre). L'array decrescente costa confronti, quindi . Traccia su : confronti. Su array ordinato sono .
- Efficienza: significa che è asintoticamente più efficiente. Polinomiale = efficiente, esponenziale = inefficiente.
- Tempi massimi in 1 s ( in ns): per , ; per , circa; per , . In un'ora arriva a circa e solo a circa .
- Limiti: il caso pessimo può essere patologico (QuickSort); le costanti contano per piccoli: batte solo per , e batte solo per .
- Taglia risolvibile: con in un minuto si arriva a circa ; con in un'ora a circa . Moltiplicare il tempo per dà un fattore sulla taglia quadratica, ma solo su quella esponenziale.
- Errori tipici: ", quindi sempre più veloce"; provare un solo limite e scrivere ; costanti o termini minori nel risultato, come .
Teoria collegata
- Problemi computazionali - istanze, soluzioni, correttezza e complessità
- Algoritmi di ordinamento
- Dimostrazioni, induzione e invarianti
- Liste concatenate in C
- Notazione asintotica
- Problemi computazionali e algoritmi
- Anomaly detection
- Classificazione e k-nearest neighbors
- Clustering e k-means
- Algoritmi randomizzati - Monte Carlo, Las Vegas e amplificazione
- Analisi probabilistica di quicksort e quickselect