arrow_backTorna alle field notes
COMPUTER SCIENCE Pubblicato 8 Aug 2026

Algoritmi Che Terminano In Tempo: Un Controllo Big O

Impara a individuare algoritmi lenti prima che vengano spediti, con esempi Big O reali, benchmark e correzioni rapide per i colli di bottiglia comuni.

Scrivi una funzione, la esegui sui dati di test e funziona bene. Poi arriva ai dati di produzione — 500.000 righe invece di 500 — e la richiesta scade. Succede costantemente, ed è quasi sempre un problema di complessità nascosto dietro codice che sembrò ragionevole a piccola scala.

Perché il tuo laptop ti ha mentito

Un algoritmo O(n²) eseguito su 100 elementi fa 10.000 operazioni. È istantaneo su qualsiasi macchina. Esegui lo stesso algoritmo su 100.000 elementi e sei a 10 miliardi di operazioni — minuti o ore invece di millisecondi. Il salto da n a n² non sembra pericoloso finché n non diventa grande, ed è esattamente per questo che passa la revisione del codice e i test locali.

Un esempio classico: controllare i duplicati con un ciclo annidato.

def has_duplicates(items):
    for i in range(len(items)):
        for j in range(i + 1, len(items)):
            if items[i] == items[j]:
                return True
    return False

Questo è O(n²). Sostituiscilo con un set:

def has_duplicates(items):
    seen = set()
    for item in items:
        if item in seen:
            return True
        seen.add(item)
    return False

Ora è O(n). Le ricerche nei set sono in media O(1), quindi l'intera funzione scala linearmente invece che quadraticamente. Su 10.000 elementi è la differenza tra circa 100 milioni di confronti e 10.000.

Misura prima di ottimizzare

Non indovinare. Usa timeit per microbenchmark:

import timeit
timeit.timeit(lambda: has_duplicates(list(range(5000))), number=10)

Per funzioni più grandi, profila con cProfile:

python -m cProfile -s cumulative myscript.py

Guarda la colonna cumtime. Se una funzione consuma il 90% del tempo di esecuzione, è il tuo obiettivo. Ottimizzare codice che non è il collo di bottiglia spreca il tuo tempo e aggiunge complessità senza vantaggi.

Modelli comuni che diventano lenti in silenzio

Controlli ripetuti di appartenenza a una lista. if x in my_list è O(n) per una lista ma O(1) in media per un set o dict. Se stai controllando l'appartenenza dentro un ciclo, quel controllo O(n) dentro un ciclo O(n) ti dà O(n²) complessivo. Sostituisci la lista con un set e sei di nuovo a O(n).

Concatenazione di stringhe in un ciclo. In Python, result += chunk dentro un ciclo su stringhe è O(n²) nel caso peggiore perché le stringhe sono immutabili e ogni concatenazione copia il tutto. Usa ''.join(chunks) invece — costruisce la stringa una volta.

Ordinamento quando non serve. sorted() è O(n log n). Se la stai chiamando dentro un ciclo solo per trovare il massimo o il minimo ad ogni iterazione, usa max()/min() (O(n)) o meglio, un heap (heapq) se hai bisogno dell'estremo corrente ripetutamente. heapq.nlargest(k, data) è O(n log k), molto più economico che ordinare l'intera lista quando k è piccolo.

Funzioni ricorsive senza memoizzazione. Fibonacci ricorsivo ingenuo è O(2ⁿ) perché ricalcola gli stessi sottoproblemi ripetutamente. Aggiungi functools.lru_cache e scende a O(n):

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

Leggere Big O dal codice reale, velocemente

Conta i cicli annidati sullo stesso input — quello è di solito il tuo esponente. Un singolo ciclo è O(n). Un ciclo dentro un ciclo è O(n²) a meno che l'intervallo del ciclo interno non si riduca (come nella forma ottimizzata di bubble sort, ancora O(n²) nel caso peggiore ma meno confronti). Chiamate ricorsive che dividono il problema a metà, come binary search o merge sort, puntano a O(log n) o O(n log n). Chiamate ricorsive che si dividono in molteplici chiamate per livello, come Fibonacci ingenuo, puntano a tempo esponenziale — un segnale forte che hai bisogno di memoizzazione o di una riscrittura iterativa.

Una buona abitudine: prima di scrivere cicli annidati o ricorsione su dati rivolti all'utente, chiediti quale potrebbe essere realisticamente n in produzione, non nel tuo file di test. Se n potrebbe raggiungere sei cifre, una soluzione O(n²) ha bisogno di una seconda occhiata prima di essere spedita.

Per ulteriori informazioni sull'analisi di complessità, algoritmi di ordinamento e strumenti di profiling, consulta i segmenti Python e Computer Science su Korra Studio.

Scritto con assistenza AI, revisionato e pubblicato da Michal Pilch (CISSP), Korra Studio.

Pronto per andare oltre?

Questa è una nota dalla knowledge base di Korra Studio — la piattaforma abbina ogni argomento a mentoring 1-to-1.

Inizia gratisarrow_forward