Lab-in-a-Tab

Algoritmi di ordinamento

Come fa un computer a ordinare un milione di numeri in millisecondi — e perché la strategia conta?

AlgoritmiBig-OInformatica
ProvaTrascina Dimensione array a 80. Premi Bubble Sort e lascialo finire — nota Confronti. Ora premi Merge Sort sulla stessa dimensione e confronta.
Cosa stai vedendoOgni barra è un numero in attesa di essere messo in ordine — barre corte sono piccole, barre alte sono grandi. Premere un pulsante di ordinamento fa riordinare al computer, passo dopo passo. Confronti conta quante volte ha confrontato due barre, e Scambi quante volte le ha spostate.
Cosa notare
Il Bubble Sort ha bisogno di circa 3.160 confronti; il Merge Sort di circa 400 — quasi otto volte meno, per esattamente lo stesso risultato. Il Bubble Sort confronta i vicini più e più volte, quindi il lavoro cresce col quadrato della lista. Il Merge Sort divide il mucchio a metà, ordina ogni metà, poi fonde — e dividere ripetutamente è enormemente più economico. La strategia che scegli conta più della velocità del tuo computer: su un milione di elementi questo divario diventa giorni contro secondi.

Mettere le cose in ordine — nel modo furbo!

Livello Base — linguaggio semplice, senza matematica

Immagina un grosso mazzo di carte numerate, tutte mescolate, e il compito di metterle in ordine dalla più piccola alla più grande. Potresti scorrerle a mano, confrontando e infilando ciascuna al suo posto. Abbastanza facile con dieci carte. Con un milione, lo stesso paziente metodo ti terrebbe occupato per anni.

I computer incontrano questo identico problema tutto il giorno — ordinare le tue email per data, le foto per ora, i risultati di ricerca per pertinenza, i contatti dalla A alla Z. Un algoritmo di ordinamento è solo la precisa ricetta passo-passo che un computer segue per farlo. Ed ecco la cosa su cui vale la pena fermarsi: per lo stessissimo mazzo di carte, alcune ricette sono milioni di volte più veloci di altre.

Il più semplice, il Bubble Sort, percorre la lista confrontando i vicini, scambiando quelli fuori ordine, e ripete finché non resta nulla da scambiare. Funziona, ma è lento — sostanzialmente controlla ogni coppia. L'alternativa furba, il Merge Sort, gioca al divide-et-impera: taglia il mazzo a metà, ordina ciascuna metà, poi ricuce insieme le due metà ordinate. Dividi e fondi, dividi e fondi, e il milione di carte che avrebbe richiesto anni cade in ordine in un batter d'occhio. Stesso compito, strategia selvaggiamente diversa — è tutto qui il gioco.

Da sapere

  • La tua app di posta ordina migliaia di messaggi quasi all'istante usando varianti di Merge Sort e QuickSort.
  • Google Maps trova il percorso più breve fra miliardi di segmenti stradali in meno di un secondo — usando algoritmi specializzati sui grafi.
  • I videogiochi ordinano migliaia di oggetti 3D per distanza a ogni fotogramma per renderizzarli nel corretto ordine di profondità!

Complessità Big-O: misurare l'efficienza di un algoritmo

Livello Studente — le equazioni principali

Quanto è buono un algoritmo di ordinamento si riduce a una domanda: man mano che l'input cresce fino a \(n\) elementi, quanto in fretta cresce il lavoro? La notazione Big-O cattura esattamente quel tasso di crescita, nel caso peggiore. Il trucco è che la velocità grezza dipende dal tuo hardware, ma il tasso di crescita è insito nell'algoritmo stesso — ed è il tasso di crescita a decidere chi vince su un milione di elementi.

Il Bubble Sort continua a spazzare la lista, facendo salire i valori grandi verso destra uno scambio alla volta. Per \(n\) elementi sono fino a \(\tfrac{n(n-1)}{2}\) confronti — \(O(n^2)\). Raddoppia l'input e quadruplichi il lavoro. A \(n = 10^6\) sono circa 500 miliardi di confronti, all'incirca 8 minuti su una macchina che ne fa un miliardo al secondo.

Il Merge Sort prende la via del divide-et-impera: dimezza, ordina ricorsivamente ciascuna metà, poi fonde. Ogni fusione è lavoro \(O(n)\) e la ricorsione è profonda solo \(\log_2 n\) livelli, quindi il totale è \(O(n \log n)\). A \(n = 10^6\) sono circa 20 milioni di confronti — qualcosa come 25.000 volte più veloce del Bubble Sort. Il prezzo è la memoria: la fusione richiede \(O(n)\) spazio di appoggio.

Il QuickSort sceglie un pivot, divide l'array in "più piccoli" e "più grandi", e ricorre su ciascun lato. In media è \(O(n \log n)\) con un comportamento della cache delizioso; il suo caso peggiore è \(O(n^2)\) su input già ordinato, domato scegliendo il pivot a caso. I linguaggi reali coprono le scommesse: Python e Java montano il TimSort, un ibrido Merge/Insertion che individua le sequenze già in ordine e sfreccia attraverso dati quasi ordinati in quasi \(O(n)\).

Formule chiave

Bubble Sort\(T(n) = O(n^2)\)stabile; lento per n grande
Merge Sort\(T(n) = O(n \log n)\)stabile; O(n) spazio extra
QuickSort\(T(n) = O(n \log n)\text{ medio},\; O(n^2)\text{ peggiore}\)in loco; veloce in pratica
Limite inferiore dei confronti\(\Omega(n \log n)\)ogni ordinamento a confronti
Ricorrenza del merge\(T(n) = 2\,T(n/2) + O(n)\)Teorema Master → O(n log n)

Da sapere

  • Ordinare 1.000.000 di elementi: Bubble Sort ≈ 8 minuti; Merge Sort ≈ 0,02 secondi. Stesso problema, 25.000 volte più veloce — da pura strategia.
  • L'ordinamento basato su confronti ha un limite inferiore dimostrato di Ω(n log n) — nessun ordinamento a confronti può essere asintoticamente più veloce, mai.
  • Il Radix Sort aggira il limite inferiore dei confronti ordinando cifra per cifra, raggiungendo O(nk) per interi a k cifre.

Complessità computazionale, il Teorema Master e le dimostrazioni di limite inferiore

Livello Esperto — profondità matematica completa

01Risolvere le ricorrenze: il Teorema Master

Gli algoritmi divide-et-impera si descrivono con ricorrenze, e la maggior parte rientra in un unico modello: \(T(n) = a\,T(n/b) + f(n)\) — \(a\) sottoproblemi di dimensione \(n/b\), più \(f(n)\) lavoro per dividere e ricombinare. Il Teorema Master legge la risposta confrontando \(f(n)\) con \(n^{\log_b a}\): chi domina vince, e se pareggiano paghi un \(\log n\) extra. Il Merge Sort ha \(a=b=2\) e \(f(n)=\Theta(n)\), che è esattamente il caso di pareggio \(n^{\log_2 2} = n\) — così ne esce \(T(n) = \Theta(n \log n)\), senza alcun giro di parole.

02Il limite inferiore che nessuno può battere

Il Merge Sort è \(O(n\log n)\) — ma qualcosa di più furbo potrebbe essere più veloce? Per qualsiasi ordinamento che funzioni confrontando elementi, la risposta è un netto no, e la dimostrazione è splendida. Modella l'algoritmo come un albero di decisione binario: ogni nodo interno pone un confronto, ogni foglia è una permutazione finita. Ordinare \(n\) elementi significa che l'albero deve avere almeno \(n!\) foglie per distinguere ogni ordinamento, e un albero binario di altezza \(h\) ne ha al più \(2^h\). Quindi \(h \ge \log_2(n!)\), e Stirling lo trasforma in \(\Omega(n \log n)\). Il limite è teorico-informativo — non gli importa quanto sei furbo, solo quanti esiti devi distinguere.

03Battere il limite barando (legalmente)

Quel muro vale solo per gli ordinamenti che confrontano. Esci da lì e puoi andare più veloce sfruttando la struttura nei dati. Il Counting Sort conta gli interi in un intervallo noto \([0,k]\) in \(O(n+k)\); il Radix Sort lo concatena cifra per cifra per \(O(d(n+k))\); il Bucket Sort raggiunge un atteso \(O(n)\) su dati distribuiti uniformemente. Nessuno di questi contraddice il limite inferiore — fanno semplicemente aritmetica sulle chiavi invece di chiedere "quale è più grande?", e il teorema non ha mai promesso nulla al riguardo.

04Il caso medio del QuickSort

Il caso peggiore del QuickSort è un brutto \(O(n^2)\), eppure è l'ordinamento a cui la maggior parte dei sistemi reali ricorre, perché in media non è solo \(O(n\log n)\) ma \(O(n\log n)\) con costanti minuscole e una superba località di cache. La media segue dal sommare le probabilità che due elementi qualsiasi vengano mai confrontati, che porta a \(E[T(n)] = 2n H_n - 2n\) con \(H_n\) il numero armonico — decisamente \(O(n\log n)\). Randomizzare il pivot rende il caso cattivo astronomicamente improbabile anziché semplicemente raro.

05La macchina sotto il modello

Il Big-O conta le operazioni, ma una CPU reale tiene enormemente alla località di memoria — un cache miss può costare centinaia di confronti "gratuiti". È per questo che un algoritmo \(O(n\log n)\) che scorre la memoria in ordine spesso stracci uno teoricamente uguale che salta di qua e di là. Gli algoritmi cache-oblivious (Frigo et al., 1999) sfruttano la ricorsione per raggiungere il comportamento di cache ottimale a ogni dimensione di cache contemporaneamente, senza mai essere informati della dimensione — un promemoria che il modello asintotico è una mappa, non il territorio.

06Il posto dell'ordinamento nello zoo della complessità

L'ordinamento sta comodamente in P, la classe dei problemi risolvibili in tempo polinomiale. Il suo vicino famoso è la domanda se \(P = NP\) — se ogni problema la cui soluzione è rapida da verificare sia anche rapida da risolvere. L'ordinamento è facile; il problema del commesso viaggiatore, che si limita a riordinare città, è NP-difficile e si crede richieda sforzo esponenziale. Un algoritmo ottimale veloce per esso farebbe collassare \(P\) in \(NP\) e porterebbe giù con sé la crittografia moderna. Che un compito tranquillo come mettere le cose in ordine confini con un problema che potrebbe riscrivere la matematica è il dramma silenzioso della teoria della complessità.

Formule chiave

Teorema Master\(T(n) = a\,T(n/b) + f(n)\)
Caso del Merge Sort\(f(n)=\Theta(n^{\log_b a}) \Rightarrow T=\Theta(n^{\log_b a}\log n)\)
Limite inferiore dei confronti\(h \ge \log_2(n!) = \Omega(n \log n)\)
Stirling\(\log(n!) = n \log n - n + O(\log n)\)
QuickSort medio\(E[T(n)] = 2n H_n - 2n = O(n \log n)\)H_n = n-esimo numero armonico
Radix Sort\(T(n) = O(d(n+k))\)d cifre, base k

Da sapere

  • La crittografia RSA si basa sulla difficoltà della fattorizzazione di interi (creduta NP-intermedia) — il miglior algoritmo noto gira in exp(O(n^{1/3})), non in tempo polinomiale.
  • Gli algoritmi di ordinamento cache-oblivious (Frigo et al., 1999) raggiungono prestazioni di cache ottimali a tutte le dimensioni di cache simultaneamente usando una struttura ricorsiva.
  • La costruzione di suffix array in O(n log n) (o O(n) con SA-IS) è il cavallo da tiro dell'assemblaggio del genoma — ordinare i ~3 miliardi di suffissi del genoma umano.

Fonti

Articolo completo su Wikipedia ↗