Le Notazioni Asintotiche e l’Analisi della Complessità degli Algoritmi

Concetti preliminari di analisi asintotica

Confronto tra le principali funzioni di crescita

Quando si analizza l’efficienza di un algoritmo, non ci si limita a misurare il tempo effettivo di esecuzione sul computer, poiché tale misura dipende da fattori cangianti da macchina a macchina, quello che si punta a fare è descrivere in modo astratto e generale il comportamento dell’algoritmo, indipendentemente dal contesto. In particolare si studia come il tempo di esecuzione cresce al crescere della dimensione dell’input .

  • rappresenta la funzione di costo dell’algoritmo. Un algoritmo è fatto di vari passi, un passo può essere considerato come un’operazione di base che richiede un tempo costante.

Diremo che due algoritmi sono più o meno efficienti a seconda di come cresce la loro funzione : un algoritmo la cui funzione di costo cresce più lentamente sarà, per input sufficientemente grandi, più efficiente di uno con crescita più rapida.

Dalla necessità di classificare i vari algoritmi nascono le notazioni asintotiche, che ci permettono per l’appunto di rappresentare questa idea di crescita trascurando i dettagli che non influiscono sul comportamento generale dell’algoritmo.

Confronto tra le principali funzioni di crescita

Alcune funzioni crescono lentamente, altre molto rapidamente. Immaginiamo di avere diversi algoritmi che risolvono lo stesso problema, ma con funzioni di costo differente:

  • uno è proporzionale a
  • un altro a
  • un altro ancora a Per valori piccoli di le differenze possono sembrare trascurabili, ma per input abbastanza grandi la situazione cambia, ad esempio con : In generale abbiamo diversi tipi di crescita:
  1. Funzioni costanti rappresentano algoritmi che impiegano sempre lo stesso numero di operazioni indipendentemente da
    • Accesso ad un elemento di un array
  2. Funzioni logaritmiche descrivono algoritmi che riducono il problema di un fattore costante a ogni passo
    • Ricerca binaria
  3. Funzioni lineari corrispondono ad algoritmi che analizzano tutti gli elementi dell’input una sola volta
    • Scansione di un array
  4. Funzioni linearitmiche caratterizzano algoritmi efficienti di ordinamento
    • Merge sort e Heap sort
  5. Funzioni polinomiali includono molti algoritmi praticabili ma diventano rapidamente onerose per
    • Bubble sort
  6. Funzioni esponenziali e fattoriali: descrivono problemi di natura combinatoria per i quali il numero di soluzioni cresce esplosivamente con
    • Calcolo di tutte le permutazioni Sono elencate in ordine di crescita crescente: una funzione situata più in basso cresce più rapidamente di tutte quelle sopra di essa
Termini e fattori dominanti e trascurabili

Quando si analizza il tempo di esecuzione di un algoritmo la funzione di costo può contenere più termini, si chiama termine dominante quello che determina l’andamento complessivo della funzione per valori grandi di , quando abbiamo più termini quello che dobbiamo fare è:

  • Eliminare i termini di ordine inferiore, cioè quelli che crescono più lentamente
  • Si ignorano i fattori costanti che moltiplicano i termini
  • Si conserva solo il termine che domina Di seguito degli esempi:
  1. in questa funzione domina il ed essendo un coefficiente moltiplicativo questo è trascurabile quindi possiamo dire che questo algoritmo viene descritto da
  2. qui il termine che cresce più velocemente è

Le notazioni asintotiche

Con il termine asintotico indichiamo il comportamento di una funzione quando la variabile indipendente cresce senza limiti, possiamo quindi affermare che:

Una notazione asintotica è un modo sintetico per descrivere il modo in cui una funzione cresce rispetto a un’altra. Essa non ci dice quanto vale esattamente una funzione, ma quanto rapidamente aumenta rispetto ad altre funzioni al crescere di n

Esempio: dire che è “dell’ordine ” significa che per input molto grandi il numero di operazioni richieste dall’algoritmo cresce in modo proporzionale a

In generale le notazioni asintotiche forniscono un linguaggio per esprimere relazioni del tipo:

  • cresce non più rapidamente / alla stessa velocità / più rapidamente di Esse si basano quindi sul confronto tra le due funzione nel limite per cioè osservando il loro comportamento quando la dimensione diventa molto grande

Esempio: date queste due funzioni: la differenza può sembrare significativa, ma man mano che cresce il termine diventa dominante, quindi possiamo affermare che e hanno la stessa crescita asintotica

La notazione Θ (theta)

La notazione fornisce un vincolo asintotico stretto ovvero rappresenta l’insieme di tutte le funzioni che crescono alla stessa velocità di una funzione di riferimento g(n). Formalmente scriviamo che: In altre parole, a partire da una certa soglia la funzione è sempre compresa tra due multipli costanti della funzione di riferimento . Praticamente la notazione descrive una crescita bilanciata: cresce in modo simile a differendo al più per un fattore costante. Questo significa che per input di molto grandi possiamo dire che: da questa immagine capiamo subito il significato della definizione formale infatti dopo un certo (in questo caso ) la nostra funzione resta confinata nella fascia delimitata dalla linea tratteggiata

Esempi:

  • perché la costante additiva non influisce sul comportamento

La notazione è la più informativa poiché fornisce sia un limite superiore sia uno inferiore, se allora è simultaneamente:

  • Capiremo successivamente il perché
La notazione O (Big-Oh)

La notazione rappresenta un limite superiore asintotico ovvero descrive funzioni che per n sufficientemente grandi non crescono più rapidamente di una funzione di riferimento g(n), formalmente si definisce come: direi che significa che, a partire da una cerca soglia il valore di non supera mai quello di

Praticamente la notazione serve per stabilire una stima superiore del comportamento asintotico infatti garantisce che non crescerà mai più velocemente di per grandi

Attraverso questa immagine riusciamo subito a capire la definizione formale, infatti dopo una la funzione non cresce più velocemente di

Esempi:

la notazione indica solo un limite superiore se diciamo che stiamo affermando che l’algoritmo non richiede mai più di un tempo proporzionale a ma potrebbe essere anche più efficiente. Di solito questa notazione viene usata per descrivere il caso peggiore

La notazione Ω (Big-Omega)

La notazione rappresenta un limite inferiore asintotico ovvero descrive funzioni che per n sufficientemente grande crescono almeno tanto rapidamente quanto una funzione di riferimento g(n) formalmente si definisce come: Ciò significa che a partire da un certo punto il valore di è sempre maggiore o uguale a una constante moltiplicativa di

Praticamente la notazione descrive una crescita minima garantita, infatti oltre una scelta soglia la funzione rimane sempre al di sopra della funzione di riferimento

Attraverso questa immagine è abbastanza intuitiva la definizione formale, oltre una certa la nostra funzione rimane sempre al di sopra della curva

La notazione indica un limite inferiore e appunto per questo viene utilizzata per descrivere il caso migliore

Esempi:

Le notazioni o e ω (piccole)

Accanto a e che rappresentano limiti asintotici ampi, esistono le loro controparti minuscole e che esprimo limiti stretti

  • La prima esprime che cresce più lentamente di :Significa che il rapporto tende a zero quindi cresce in modo più lento di che per grandi il suo contributo diventa trascurabile

    Praticamente possiamo dire che un tempo di esecuzione significa che esso cresce più lentamente di qualunque funzione proporzionale a

  • La seconda ha un significato opposto descrive infatti funzioni che crescono più rapidamente di :Significa che il rapporto tende a infinito Praticamente possiamo dire che un tempo di esecuzione significa che la nostra funzione cresce più velocemente di qualunque funzione proporzionale a

Possiamo dunque pensare alle notazioni e come versioni esclusive di e che indicano una crescita più lenta o più rapida che non raggiunge mai quella della funzione di riferimento

Conclusioni sulle notazioni asintotiche

La scala delle notazioni è la seguente

Oltre i casi limite

Cosa accade davvero in pratica

Le notazioni asintotiche sono strumenti potenti per classificare gli algoritmi, ma è importante ricordare che esse rappresentano un modello astratto del comportamento di un programma. Dire che un algoritmo ha complessità non significa che esegua esattamente operazioni ma solo che per grandi il suo tempo di esecuzione non cresce più rapidamente di una funzione , di solito in questo tipo di analisi si distinguono tre casi:

  • che descrive il caso peggiore ovvero il limite superiore del tempo di esecuzione
  • che descrive il caso migliore ovvero il limite inferiore del tempo di esecuzione
  • indica che il tempo di esecuzione è compreso tra i due limiti precedenti, fornisce quindi una descrizione asintoticamente esatta di quello che è il tempo di esecuzione

Esempio 1: La ricerca lineare Nel caso peggiore, una ricerca lineare in un array di elementi richiede di esaminare tutti gli elementi: Tuttavia, se l’elemento cercato si trova spesso nelle prime posizioni, il numero medio di confronti può essere molto più basso, ad esempio circa . In tal caso, il comportamento medio si avvicina a: ma il tempo effettivo percepito sarà spesso inferiore rispetto al limite teorico.

Esempio 2: L’ordinamento tramite QuickSort Il QuickSort ha una complessità nel caso peggiore di , che si verifica quando il pivot scelto in ogni passo è sempre il minimo o il massimo elemento (ad esempio, se l’array è già ordinato). Tuttavia, nella pratica, grazie alla scelta casuale del pivot o a tecniche di bilanciamento, l’algoritmo si comporta quasi sempre come un algoritmo . Il suo caso medio è quindi molto più rappresentativo delle prestazioni reali: Esempio 3: La ricerca binaria La ricerca binaria ha caso migliore (quando l’elemento cercato è esattamente quello centrale) e caso peggiore . Nella pratica, tuttavia, ogni ricerca richiede un numero di passi che dipende in modo logaritmico da , per cui il comportamento effettivo si avvicina costantemente al caso peggiore.

Questi esempi ci mostrano che le notazioni non devono essere interpretate come previsioni puntuali, ma come descrizioni qualitative del comportamento, le notazioni vanno interpretate in questo modo:

  • Caso peggio ci garantisce la soglia di sicurezza, utile in contesti critici
  • Caso migliore: indica il limite di ottimalità teorica, ma non sempre raggiungibile
  • Caso medio: è spesso quello più significativo nella valutazione pratica
Quando le costanti contano

Nell’analisi asintotica degli algoritmi, è prassi trascurare i fattori costanti e i termini di ordine inferiore, questo approccio consente di concentrarsi sulla crescita dominante della funzione per molto grandi, tuttavia questa cosa nella pratica quotidiana per input di piccole o medie dimensioni dovrebbe essere attenzionata

Esempio: dal punto di vista asintotico è più efficiente, tuttavia il fattore costante nel primo algoritmo può renderlo più lento del secondo per una vasta gamma di valori realistici di , vediamolo con In questo caso, l’algoritmo con complessità peggiore risulta più veloce. Questo esempio mette in evidenza un punto importante: le notazioni asintotiche descrivono il comportamento per grandi, ma non sempre riflettono le prestazioni reali su input di dimensioni moderate