Salta al contenuto
Note per Studenti Algoritmi di ordinamento

Algoritmi di ordinamento

In questa pagina 9

Problema. Input: sequenza S[0..n−1]S[0..n-1] di chiavi confrontabili. Output: gli stessi elementi in ordine crescente (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 →). La taglia è nn. Qui si studiano algoritmi basati su confronti (le chiavi si usano solo con <<, ≤\le, ==, cioè l'ordine totale dei numeri reali: Campi ordinati (Q e R)Gli assiomi di somma, prodotto e ordine comuni a Q e R, da cui derivano le regole di equazioni e disequazioni.Campi ordinati (Q e R) →). Gli stessi problemi sono trattati in modo più elementare in Fondamenti di Informatica (Ricerca lineare e binariaRicerca lineare su sequenze qualsiasi in O(n); ricerca binaria su sequenze ordinate in O(log n), versione iterativa e ricorsiva, invariante e errori di indice; modulo bisect.Ricerca lineare e binaria →, Ordinare e cercare in C - qsort, bsearch e puntatori a funzionePuntatori a funzione e funzioni di confronto; qsort per ordinare array di interi, stringhe e struct; bsearch per la ricerca binaria; dizionario ordinato su array dinamico di struct con inserimento (memmove e realloc) e ricerca in O(log n).Ordinare e cercare in C - qsort, bsearch e puntatori a funzione →); qui l'attenzione è sull'analisi della complessità. Gli algoritmi ricorsivi seguono Algoritmi ricorsiviAlgoritmi ricorsivi come induzione eseguita; albero della ricorsione e record di attivazione nello stack; esempi ReverseArray, LinearSum, Power in tempo logaritmico, Fibonacci ricorsivo (esponenziale) e con memoizzazione; analisi della complessità con l'albero della ricorsione e correttezza per induzione.Algoritmi ricorsivi → (RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →) e le somme sono quelle di SommatorieIl simbolo di sommatoria, le sue proprietà (linearità, additività, cambio di indice) e le somme notevoli di Gauss e geometrica.Sommatorie →.

Per confrontare gli algoritmi si seguono tutti sullo stesso array S=[5,2,9,1,6]S=[5,2,9,1,6] (n=5n=5). L'ordine finale è [1,2,5,6,9][1,2,5,6,9].

Nota sulle fonti: insertion sort e selection sort compaiono nel materiale del corso (esercizi su complessità e invarianti); merge sort, quick sort, heap sort e il limite inferiore sono scritti dal programma ufficiale con conoscenze standard.

Selection sort

Si cerca il minimo della parte non ordinata e lo si porta in posizione ii.

Algoritmo selectionSort(S, n)
for i <- 0 to n-2 do
    minIndex <- i
    for k <- i+1 to n-1 do
        if S[k] < S[minIndex] then minIndex <- k
    swap(S[i], S[minIndex])

Proprietà (selection sort: invariante e costo). Invariante all'inizio dell'iterazione ii: S[0..i−1]S[0..i-1] contiene gli ii elementi minori, già in ordine crescente (e ciascuno ≤\le tutti gli elementi di S[i..n−1]S[i..n-1]). Alla fine i=n−1i = n-1 e tutto è ordinato. Costo: il ciclo interno fa n−1−in - 1 - i confronti, in totale ∑i=0n−2(n−1−i)=n(n−1)2∈Θ(n2)\sum_{i=0}^{n-2}(n-1-i) = \frac{n(n-1)}{2} \in \Theta(n^2) per ogni istanza, anche già ordinata. Corrisponde alla coda con priorità su lista non ordinata (vedi Code con prioritàEntry chiave-valore; ADT coda con priorità (insert, min, removeMin) con chiave minima = priorità massima; esempio di esecuzione; applicazioni; implementazioni con lista non ordinata e ordinata e relativi costi; ordinamento tramite coda con priorità.Code con priorità →).

Traccia (in grassetto la parte già ordinata; min è la posizione del minimo trovato):

ii minimo di S[i..4]S[i..4] array dopo lo scambio confronti
0 11 in posizione 3 [1,2,9,5,6][\mathbf{1},2,9,5,6] 4
1 22 in posizione 1 (resta) [1,2,9,5,6][\mathbf{1},\mathbf{2},9,5,6] 3
2 55 in posizione 3 [1,2,5,9,6][\mathbf{1},\mathbf{2},\mathbf{5},9,6] 2
3 66 in posizione 4 [1,2,5,6,9][\mathbf{1},\mathbf{2},\mathbf{5},\mathbf{6},9] 1

Totale 4+3+2+1=10=5⋅424+3+2+1=10=\frac{5\cdot4}{2}. Il numero di scambi invece è al più n−1n-1 (uno per iterazione): conta solo la parte di confronti.

Esempio. Con n=5n=5 il selection sort fa 5⋅4/2=105\cdot4/2=10 confronti su qualunque array, anche già ordinato.

Insertion sort

Proprietà (insertion sort: caso pessimo e caso migliore). Si mantiene ordinato un prefisso e si inserisce S[i]S[i] al posto giusto spostando a destra i maggiori (pseudocodice e analisi in Complessità in tempo e caso pessimoPerché lo studio sperimentale non basta; complessità al caso pessimo come massimo sul numero di operazioni tra le istanze di una data taglia; stima con limiti superiore e inferiore senza trovare l'istanza peggiore; esempi arrayMax, prefixAverages e InsertionSort; efficienza asintotica e limiti dell'analisi.Complessità in tempo e caso pessimo →). Invariante: all'inizio dell'iterazione ii, S[0..i−1]S[0..i-1] è ordinato (e contiene gli elementi originali di S[0..i−1]S[0..i-1]). Caso pessimo Θ(n2)\Theta(n^2) (array decrescente), caso migliore Θ(n)\Theta(n) (array ordinato), veloce su sequenze quasi ordinate.

Esempio. Con n=5n=5 servono 44 confronti su [1,2,3,4,5][1,2,3,4,5] e 1010 su [5,4,3,2,1][5,4,3,2,1]. Traccia su S=[5,2,9,1,6]S=[5,2,9,1,6]:

ii curr prefisso ordinato prima prefisso dopo confronti
1 2 [5][5] [2,5][2,5] 1 (5>25>2, poi si esce dall'array)
2 9 [2,5][2,5] [2,5,9][2,5,9] 1 (5>95>9 falso)
3 1 [2,5,9][2,5,9] [1,2,5,9][1,2,5,9] 3 (tutti maggiori di 11)
4 6 [1,2,5,9][1,2,5,9] [1,2,5,6,9][1,2,5,6,9] 2 (9>69>6 vero, 5>65>6 falso)

Totale 1+1+3+2=71+1+3+2=7 confronti: tra il minimo n−1=4n-1=4 e il massimo n(n−1)2=10\frac{n(n-1)}{2}=10. Ogni confronto vero sposta un elemento a destra e corrisponde a una inversione (coppia fuori ordine) risolta: qui le inversioni sono (5,2),(5,1),(2,1),(9,1),(9,6)(5,2),(5,1),(2,1),(9,1),(9,6), cioè 55. A queste si aggiunge al più un confronto falso per iterazione, quello che ferma lo scorrimento (nelle iterazioni i=2i=2 e i=4i=4; nelle altre il ciclo finisce perché si esce dall'array): 5+2=75+2=7. In generale confronti ≤\le inversioni + (n−1)+\,(n-1), per questo un array quasi ordinato, con poche inversioni, costa poco.

Merge sort

Divide et impera: si divide in due metà, si ordinano ricorsivamente, si fondono le due sequenze ordinate con un solo passaggio.

Algoritmo mergeSort(S)
Input: sequenza S di n elementi      Output: S ordinata
if n <= 1 then return S
metà <- floor(n/2)
L <- mergeSort(S[0..metà-1]);  R <- mergeSort(S[metà..n-1])
return merge(L, R)

Formula (costo del merge sort). merge(L, R) confronta le due teste e sposta la minore nell'output: Θ(∣L∣+∣R∣)\Theta(\lvert L \rvert + \lvert R \rvert). Costo: l'albero della ricorsione ha ⌈log⁡2n⌉+1\lceil \log_2 n \rceil + 1 livelli e al livello ii le 2i2^i chiamate lavorano su sequenze di taglia ≈n/2i\approx n/2^i, con costo totale per livello Θ(n)\Theta(n) (il costo di un nodo è la fusione): Θ(nlog⁡n)\Theta(n \log n) per ogni istanza. Richiede Θ(n)\Theta(n) di spazio ausiliario. È stabile se in caso di parità si prende l'elemento di sinistra (verificato: per n=1024n = 1024 già ordinato i confronti sono 51205120).

Esempio. Per n=8n=8 l'albero ha log⁡28+1=4\log_28+1=4 livelli e nel caso pessimo i confronti sono nlog⁡2n−n+1=17n\log_2n-n+1=17.

Come funziona merge. Si tengono due indici, uno per ogni sequenza ordinata; a ogni passo si confrontano le due teste e si copia la minore. Esempio con L=[2,5,8]L=[2,5,8] e R=[1,6,7]R=[1,6,7]: 22 contro 11 dà 11; 22 contro 66 dà 22; 55 contro 66 dà 55; 88 contro 66 dà 66; 88 contro 77 dà 77; resta 88, copiato senza confronto: output [1,2,5,6,7,8][1,2,5,6,7,8] con 55 confronti, al più ∣L∣+∣R∣−1|L|+|R|-1. Ogni confronto mette un elemento in output, quindi il costo è lineare.

Da dove viene nlog⁡2nn\log_2n. Sia T(n)T(n) il costo: due chiamate su metà dati più una fusione lineare, T(n)=2T(n/2)+cnT(n)=2T(n/2)+cn. Si "srotola": il livello 00 ha 11 fusione di taglia nn (costo cncn), il livello 11 ha 22 fusioni di taglia n/2n/2 (costo 2⋅cn2=cn2\cdot c\frac n2=cn), il livello ii ha 2i2^i fusioni di taglia n/2in/2^i (costo 2i⋅cn2i=cn2^i\cdot c\frac{n}{2^i}=cn). Ogni livello costa cncn e i livelli sono log⁡2n\log_2n (la taglia si dimezza fino a 11: lo stesso conto della ricerca binaria, 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 →), quindi T(n)=cnlog⁡2n+Θ(n)T(n)=cn\log_2n+\Theta(n). Traccia su S=[5,2,9,1,6]S=[5,2,9,1,6]: la ricorsione divide in [5,2][5,2] e [9,1,6][9,1,6], poi [9][9] e [1,6][1,6]; le fusioni sono [5]+[2]→[2,5][5]+[2]\to[2,5] (11 confronto), [1]+[6]→[1,6][1]+[6]\to[1,6] (11), [9]+[1,6]→[1,6,9][9]+[1,6]\to[1,6,9] (22), [2,5]+[1,6,9]→[1,2,5,6,9][2,5]+[1,6,9]\to[1,2,5,6,9] (33): totale 77 confronti.

Quick sort

Si sceglie un pivot pp (ad esempio l'ultimo elemento), si partiziona in elementi <p< p e ≥p\ge p in loco con un passaggio, si mette il pivot al suo posto definitivo e si ordinano ricorsivamente le due parti.

Algoritmo quickSort(S, lo, hi)
if lo >= hi then return
p <- S[hi]; i <- lo
for j <- lo to hi-1 do
    if S[j] < p then swap(S[i], S[j]); i <- i + 1
swap(S[i], S[hi])
quickSort(S, lo, i-1); quickSort(S, i+1, hi)

Proprietà (costo del quick sort). S[lo..i−1]<p≤S[i..j−1]S[lo..i-1] < p \le S[i..j-1]. Costo: Θ(n)\Theta(n) per partizione. Se il pivot divide in parti bilanciate l'albero ha altezza Θ(log⁡n)\Theta(\log n) e il costo è Θ(nlog⁡n)\Theta(n \log n); se è sempre il minimo o il massimo (array già ordinato con pivot ultimo) il costo è ∑(n−i)=Θ(n2)\sum (n - i) = \Theta(n^2). Con pivot scelto a caso il tempo atteso è O(nlog⁡n)O(n \log n): è l'esempio classico di caso pessimo patologico (vedi Complessità in tempo e caso pessimoPerché lo studio sperimentale non basta; complessità al caso pessimo come massimo sul numero di operazioni tra le istanze di una data taglia; stima con limiti superiore e inferiore senza trovare l'istanza peggiore; esempi arrayMax, prefixAverages e InsertionSort; efficienza asintotica e limiti dell'analisi.Complessità in tempo e caso pessimo →). Spazio ausiliario O(log⁡n)O(\log n) attesi (stack).

Esempio. Con pivot ultimo su un array già ordinato di n=6n=6 elementi i confronti sono 5+4+3+2+1=155+4+3+2+1=15: Θ(n2)\Theta(n^2). Il pivot è il massimo: tutti gli altri n−1n-1 elementi vanno a sinistra, la parte destra è vuota e il problema scende solo di 11, quindi T(n)=T(n−1)+n−1T(n)=T(n-1)+n-1, che dà ∑k=1n−1k=n(n−1)2\sum_{k=1}^{n-1}k=\frac{n(n-1)}{2}.

Traccia su S=[5,2,9,1,6]S=[5,2,9,1,6] con pivot ultimo: partizione con pivot 66: gli elementi 5,2,15,2,1 sono <6<6 e passano a sinistra, 99 resta a destra: [5,2,1,6,9][5,2,1,6,9], pivot in posizione 33 (44 confronti). Si ordina ricorsivamente [5,2,1][5,2,1]: pivot 11, nessuno è minore, il pivot va in testa: [1,2,5][1,2,5] (22 confronti); poi [2,5][2,5]: pivot 55, 2<52<5, già a posto (11 confronto). Totale 4+2+1=74+2+1=7 confronti; il lato destro [9][9] ha un solo elemento (caso base). Con pivot a caso la probabilità che il pivot cada nel mezzo (tra n/4n/4 e 3n/43n/4) è 12\frac12, e un pivot "buono" riduce il problema ad almeno 34\frac34: in media servono O(log⁡n)O(\log n) livelli di partizione da Θ(n)\Theta(n) ciascuno, il che dà il tempo atteso O(nlog⁡n)O(n\log n) (media rispetto alla scelta casuale del pivot: 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 →).

Heap sort

Definizione (heap sort). Si costruisce uno heap sull'array e si estrae ripetutamente il massimo (heap sort con max-heap) mettendolo in fondo all'array: in loco.

  1. bottom-up heap-construction: Θ(n)\Theta(n) (vedi Costruzione di uno heapCostruire uno heap da un array di n entry: approccio top-down con n-1 insert, Theta(n log n); approccio bottom-up con down-heap dalle foglie verso la radice, Theta(n), con dimostrazione della somma; esempi svolti, in loco; unione di due alberi con heap-order.Costruzione di uno heap →);
  2. per end=n−1\text{end} = n-1 fino a 11: scambia S[0]S[0] con S[end]S[\text{end}] e fai down-heap su S[0..end−1]S[0..\text{end}-1]: n−1n-1 passi da O(log⁡n)O(\log n).

Esempio. Per n=1024n=1024: costruzione bottom-up Θ(n)\Theta(n) e 10231023 down-heap da al più log⁡21024=10\log_21024=10 passi, circa 10410^4 operazioni. Perché l'ordine è crescente con un max-heap: il massimo in radice viene scambiato con l'ultima posizione utile dell'array, che è la sua posizione definitiva; poi la parte ancora da sistemare si accorcia di 11 e un down-heap ripristina l'heap (HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →). L'array è così riempito dal fondo, dal massimo al minimo, e a fine algoritmo risulta crescente senza memoria aggiuntiva.

Totale Θ(nlog⁡n)\Theta(n \log n) per ogni istanza e O(1)O(1) di spazio ausiliario. Equivale all'ordinamento con coda con priorità su heap (vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →).

Ordinare senza confrontare

Formula (ordinamento per conteggio). Se le chiavi sono interi in [0,K][0, K] si può ordinare (o contare) in Θ(n+K)\Theta(n + K) con un array di contatori B[0..K]B[0..K]: una scansione che fa B[S[i]]←B[S[i]]+1B[S[i]] \leftarrow B[S[i]] + 1 e una scansione di BB. Non usa confronti, quindi non è soggetto al limite sotto. Con K=O(n)K = O(n) è lineare (si usa nell'esercizio della mediana di nn chiavi in [0,10n][0, 10n]). L'array dei contatori è una mappa con chiavi intere (Mappe e dizionariADT mappa (chiavi distinte) con get, put, remove, keySet/values/entrySet; famiglia mappa/mappa ordinata/dizionario (multimappa); applicazioni; implementazioni semplici ma poco efficienti (lista, array indicizzato dalle chiavi); dizionario realizzato con una mappa di liste.Mappe e dizionari →, array di taglia ∣U∣|U|) e il costo Θ(n+K)\Theta(n+K) è la somma dei due passaggi: nn incrementi e una scansione di K+1K+1 contatori.

Esempio. Con S=[3,1,3,0]S=[3,1,3,0] e K=3K=3 si ottiene B=[1,1,0,2]B=[1,1,0,2] e quindi l'ordine 0,1,3,30,1,3,3: B[0]=1B[0]=1 (00 compare una volta), B[1]=1B[1]=1, B[2]=0B[2]=0, B[3]=2B[3]=2; leggendo BB da sinistra si scrive 00 una volta, 11 una volta, nessun 22, 33 due volte.

Limite inferiore

Teorema (limite inferiore per l'ordinamento per confronti). Ogni algoritmo di ordinamento basato su confronti richiede Ω(nlog⁡n)\Omega(n \log n) confronti al caso pessimo.

Esempio. Per n=3n=3 ci sono 3!=63!=6 permutazioni, quindi servono almeno ⌈log⁡26⌉=3\lceil\log_26\rceil=3 confronti; per n=1024n=1024 almeno log⁡21024!≈8769\log_21024!\approx8769.

Dimostrazione. L'esecuzione su ogni istanza è descritta da un albero di decisione binario: ogni nodo interno è un confronto S[i]<S[j]S[i] < S[j], ogni foglia una permutazione dell'input prodotta come risposta. Per essere corretto l'algoritmo deve poter produrre ognuna delle n!n! permutazioni, quindi l'albero ha almeno n!n! foglie. Un albero binario con mm foglie ha altezza ≥log⁡2m\ge \log_2 m (relazione m≤2hm \le 2^h, vedi Alberi binariAlbero binario e albero binario proprio; interfaccia; relazioni tra nodi, foglie e altezza (m = n-m+1, h+1 <= m <= 2^h, 2h+1 <= n <= 2^(h+1)-1) con dimostrazioni; visita inorder; parse tree e valutazione di espressioni; heightSum come esempio di calcolo di un'informazione più ricca.Alberi binari →), perciò l'altezza è ≥log⁡2n!\ge \log_2 n!. Poiché n!≥(n/2)n/2n! \ge (n/2)^{n/2}, log⁡2n!≥n2log⁡2n2∈Ω(nlog⁡n)\log_2 n! \ge \frac n2 \log_2 \frac n2 \in \Omega(n \log n). La disuguaglianza n!≥(n/2)n/2n!\ge(n/2)^{n/2} si vede scrivendo n!=n(n−1)⋯1n!=n(n-1)\cdots1 e tenendo solo i primi n/2n/2 fattori n,n−1,…,n2+1n,n-1,\dots,\frac n2+1, ciascuno ≥n2\ge\frac n2, scartando gli altri (tutti ≥1\ge1); si usa il fattoriale e il numero di permutazioni di nn oggetti (Fattoriale e coefficienti binomialiFattoriale, permutazioni, disposizioni, combinazioni e coefficiente binomiale n su k, con il triangolo di Tartaglia.Fattoriale e coefficienti binomiali →, Calcolo combinatorio per la probabilitàNegli spazi uniformi bisogna contare: principio di moltiplicazione, disposizioni con ripetizione n^k, disposizioni semplici n!/(n−k)!, permutazioni n!, combinazioni (n su k); estrazioni con e senza reinserimento e distribuzione ipergeometrica.Calcolo combinatorio per la probabilità →). Poi log⁡2\log_2 del prodotto è la somma dei logaritmi, log⁡2((n/2)n/2)=n2log⁡2n2\log_2\big((n/2)^{n/2}\big)=\frac n2\log_2\frac n2 (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 →) e log⁡2n2=log⁡2n−1∈Θ(log⁡n)\log_2\frac n2=\log_2n-1\in\Theta(\log n). L'albero di decisione è un albero binario (proprio) e il legame tra foglie e altezza è la relazione m≤2hm\le2^h di Alberi binariAlbero binario e albero binario proprio; interfaccia; relazioni tra nodi, foglie e altezza (m = n-m+1, h+1 <= m <= 2^h, 2h+1 <= n <= 2^(h+1)-1) con dimostrazioni; visita inorder; parse tree e valutazione di espressioni; heightSum come esempio di calcolo di un'informazione più ricca.Alberi binari →. Con n=3n=3: 3!=63!=6 foglie, h≥⌈log⁡26⌉=⌈2,58⌉=3h\ge\lceil\log_26\rceil=\lceil2{,}58\rceil=3. L'altezza dell'albero è il numero di confronti nel caso peggiore. □\square

Quindi merge sort e heap sort sono ottimi (asintoticamente) tra gli algoritmi basati su confronti. Il grafico seguente confronta il limite inferiore log⁡2n!\log_2 n! (con l'approssimazione di Stirling nlog⁡2n−nlog⁡2e+12log⁡2(2πn)n\log_2 n-n\log_2e+\frac12\log_2(2\pi n), accurata a meno di 0,020{,}02 già da n=8n=8: 15,2815{,}28 contro 15,3015{,}30) con i confronti del merge sort: per n=64n=64 il minimo teorico è 296296 e il merge sort ne fa al più 321321, cioè solo l'8%8\% in più.

Grafico interattivo: Limite inferiore log2(n!) per l'ordinamento per confronti (Stirling) e confronti del merge sort al caso pessimo n·log2 n − n + 1: a n = 64 sono 296 contro 321, quindi il merge sort è quasi ottimo

Grafico interattivo: Confronti al caso pessimo in funzione di n: selection sort e insertion sort fanno n(n−1)/2, il merge sort n·log₂n − n + 1 (esatto per n potenza di 2); a n = 64 sono 2016 contro 321, mentre l'insertion sort su un array già ordinato ne fa solo n − 1 = 63

Algoritmo Caso pessimo Caso migliore Spazio ausiliario In loco
selection sort Θ(n2)\Theta(n^2) Θ(n2)\Theta(n^2) O(1)O(1) sì
insertion sort Θ(n2)\Theta(n^2) Θ(n)\Theta(n) O(1)O(1) sì
merge sort Θ(nlog⁡n)\Theta(n \log n) Θ(nlog⁡n)\Theta(n \log n) Θ(n)\Theta(n) no
quick sort Θ(n2)\Theta(n^2) (atteso O(nlog⁡n)O(n \log n)) Θ(nlog⁡n)\Theta(n \log n) O(log⁡n)O(\log n) sì
heap sort Θ(nlog⁡n)\Theta(n \log n) Θ(nlog⁡n)\Theta(n \log n) O(1)O(1) sì

Errori comuni

  • Dire che il quick sort è Θ(nlog⁡n)\Theta(n \log n) al caso pessimo: lo è atteso (o con buon pivot); al caso pessimo è quadratico.
  • Confondere il caso pessimo con il caso migliore dell'insertion sort (lineare solo su array ordinato).
  • Dimenticare che l'invariante di selection sort riguarda anche i valori, non solo l'ordine: il prefisso contiene gli ii elementi più piccoli.
  • Applicare il limite Ω(nlog⁡n)\Omega(n \log n) ad algoritmi che non usano confronti.

Versione ripasso

Esercizi su questo argomento

Teoria collegata