coldwa.st
All guidesProgrammingWebDataToolsDatabasesHaskellConceptsCabal & buildsToolchainCompilerPerformanceEditor & HLS

Programmazione · concetti · algoritmi

Che cosa e la notazione Big O?

Di ColdwastAggiornato il 14 lug. 20268 min di lettura#algorithms#complexity#concepts
Close-up of colorful programming code on a screen
Big O non riguarda la velocita con cui una macchina esegue il tuo codice: riguarda come cresce il tempo di esecuzione al crescere dell'input.

La notazione Big O e un modo per descrivere come cresce il tempo di esecuzione di un algoritmo (o il suo uso di memoria) al crescere dell'input. Risponde a una domanda: quando la quantita di dati raddoppia, o cresce di mille volte, il tuo codice resta veloce, rallenta un po' o si blocca del tutto? Ignora deliberatamente i secondi esatti e la velocita della macchina, e si concentra sulla forma della crescita, perche e questo che decide se un codice che funziona su 100 elementi funziona ancora su 100 milioni.

Perche non misurare semplicemente il tempo?

Perche i secondi dipendono dalla macchina, dal linguaggio, dal compilatore e da cosa altro e in esecuzione. Esegui lo stesso codice su un portatile e su un server e ottieni numeri diversi. Big O scarta tutto questo e misura qualcosa di piu duraturo: come il lavoro scala con la dimensione dell'input, di solito chiamata n. Due algoritmi possono entrambi impiegare "pochi millisecondi" su un input piccolo, eppure uno resta rapido su larga scala mentre l'altro diventa inutilizzabile. Big O e il modo in cui li distinguiamo prima di sbattere contro quel muro.

Le complessita piu comuni, dalla migliore alla peggiore

Questi sono i tassi di crescita che incontrerai piu spesso, dal piu rapido nel scalare al piu pericoloso:

NotazioneNomeIdea di massima
O(1)CostanteStesso lavoro indipendentemente dalla dimensione dell'input, ad esempio leggere array[5].
O(log n)LogaritmicaDimezza il problema a ogni passo, ad esempio la ricerca binaria.
O(n)LineareIl lavoro cresce di pari passo con l'input, ad esempio scorrere una lista una volta.
O(n log n)LinearitmicaIl meglio che puoi fare per l'ordinamento generale, ad esempio mergesort.
O(n²)QuadraticaUn ciclo dentro un ciclo: va bene per 100 elementi, doloroso per un milione.
O(2ⁿ)EsponenzialeRaddoppia a ogni elemento aggiunto: utilizzabile solo per input minuscoli.
O(n!)FattorialeOgni ordinamento dell'input: pratico solo per n molto piccoli.

Perche le costanti e i termini piccoli vengono ignorati

Big O si interessa alla crescita, non ai conteggi esatti, quindi semplifica. Se un algoritmo esegue 2n + 3 passi, si scrive O(n): il 2 e il 3 non cambiano la forma della curva quando n diventa grande. Allo stesso modo n² + n diventa O(n²), perche il termine quadratico domina quando n e grande. Ecco perche Big O riguarda il termine dominante, la parte che decide il comportamento su larga scala, e non una lettura precisa del cronometro.

A vintage mechanical stopwatch with a black dial
Big O ignora il cronometro. Misura come cresce il lavoro con l'input, non quanti secondi impiega una singola esecuzione.

Un esempio concreto

Supponiamo di voler verificare se una lista contiene un duplicato. Un approccio confronta ogni elemento con ogni altro elemento, due cicli annidati, all'incirca n × n confronti, quindi O(n²). Un altro approccio aggiunge ogni elemento a un hash set e verifica l'appartenenza man mano che procede, un solo passaggio, quindi O(n). Su 1.000 elementi la versione quadratica esegue circa un milione di confronti; su 100.000 elementi ne esegue dieci miliardi, mentre la versione lineare ne esegue 100.000. Stesso compito, destino radicalmente diverso su larga scala. Quel divario e esattamente cio che Big O e progettato per rivelare.

Caso peggiore, medio e migliore

In senso stretto, Big O descrive un limite superiore, di solito il caso peggiore, il massimo lavoro che l'algoritmo potrebbe fare. Vedrai anche due parenti: Big Omega (Ω) per il caso migliore, un limite inferiore, e Big Theta (Θ) quando il caso migliore e quello peggiore crescono allo stesso ritmo, dando un limite stretto. Nell'uso quotidiano si dice "Big O" in modo generico per intendere la crescita nel caso peggiore, perche e cio su cui si pianifica: vuoi sapere quanto brutta puo diventare la situazione, non quanto fortunato potresti essere ogni tanto.

Anche la complessita spaziale

La stessa notazione descrive la memoria, non solo il tempo. Un algoritmo che copia l'intero input in una nuova struttura usa O(n) di spazio extra; uno che riorganizza l'input sul posto usa O(1) di spazio extra. Su grandi quantita di dati, la memoria puo essere il vero limite, quindi vale la pena chiedersi sia "come cresce il tempo?" sia "come cresce la memoria?"

Come ragionare sul tuo codice

  • Conta i cicli sull'input. Un singolo passaggio e di solito O(n); un ciclo dentro un ciclo sugli stessi dati e di solito O(n²).
  • Dimezzare e un logaritmo. Se ogni passo scarta meta dei dati rimanenti (come la ricerca binaria), quello e O(log n).
  • Le ricerche in un hash map/set sono ~O(1). Sostituire una scansione annidata con una ricerca hash e il modo classico per trasformare O(n²) in O(n).
  • Ignora le costanti. Tre passaggi singoli separati sono comunque O(n), non O(3n): cio che conta e la forma.

FAQ

Una Big O piu bassa e sempre piu veloce? Su larga scala si, ma non sempre per input piccoli. Le costanti e l'overhead che Big O ignora possono far si che un metodo O(n²) batta uno O(n log n) su una manciata di elementi. Big O ti dice chi vince quando i dati crescono, che di solito e cio che conta.

Qual e la migliore complessita possibile? O(1), tempo costante: il lavoro non cresce affatto con l'input. La successiva migliore e O(log n). Per problemi che devono esaminare ogni elemento almeno una volta, O(n) e il minimo.

Perche O(n log n) e speciale per l'ordinamento? E il limite inferiore dimostrato per l'ordinamento generale basato su confronti, quindi mergesort, heapsort e le buone implementazioni di quicksort puntano tutte a esso. In generale non puoi ordinare dati arbitrari piu velocemente solo con i confronti.

Mi serve Big O per la programmazione di tutti i giorni? Non serve la matematica formale, ma l'intuizione e preziosissima: riconoscere quando un ciclo annidato esplodera su un input grande, o quando una ricerca hash ti salva, previene intere categorie di bug di prestazioni.

Guida indipendente, mantenuta dalla comunita. coldwa.st e un sito di risorse per la programmazione; questa spiegazione tratta la notazione Big O a livello introduttivo. Le complessita e gli esempi sono risultati standard dell'informatica.