3.7 KiB
Quick sort
Il quick sort è un altro approccio ricorsivo all'ordinamento per confronto.
Funzionamento
Anche qui, applichiamo il divide et impera.
- Divide: Scelgo un pivot qualsiasi all'interno della lista. Metto alla sua sinistra tutti i numeri minori e alla sua destra tutti i numeri maggiori.
- Impera: Eseguo un quick sort su entrambe le sottoliste.
Esempi
Iterazione con partizioni bilanciate
Osserviamo come si formi una partizione con tre elementi e una con quattro.
|¦ [2] 8 7 1 3 5 6 {4}
2 |¦ [8] 7 1 3 5 6 {4}
2 |8 ¦ [7] 1 3 5 6 {4}
2 |8 ¦ [7] 1 3 5 6 {4}
2 |8 7 ¦ [1] 3 5 6 {4}
2 1 |7 8 ¦ [3] 5 6 {4}
2 1 3 |8 7 ¦ [5] 6 {4}
2 1 3 |8 7 5 ¦ [6] {4}
2 1 3 |8 7 5 6 ¦ [{4}]
[2 1 3] {4} [8 5 6 7]
Iterazione con partizioni sbilanciate
Osserviamo come si formi una partizione con zero elementi e una con tre.
|¦ [4] 7 3 {1}
|4 ¦ [7] 3 {1}
|4 7 |¦ [3] {1}
|4 7 3 |¦ [{1}]
[] {1} [4 7 3]
Costo computazionale
Categoria | Upper bound | Lower bound | Tight bound |
---|---|---|---|
Tempo | O(n²) |
Ω(n log n) |
- |
Il costo della funzione è dato dalla somma del costo per dividere in due partizioni con il costo per realizzare il Quick sort delle due sottopartizioni
Possiamo applicare allora il Master Theorem generale:
T(n)\\
=\\
Θ(1) \qquad per\ n = 1\\
T(q) + T(dim_lista - pivot - 1) + Θ(n) \qquad per\ n > 1
Il caso migliore
Se il pivot q
è la mediana della partizione che stiamo ordinando, si vengono a creare due sottopartizioni bilanciate, e sostituendo otteniamo:
T(n)\\
=\\
Θ(1) \qquad per\ n = 1\\
2 T(\frac{n}{2}) + Θ(n) \qquad per\ n > 1
Possiamo allora applicare il Master Theorem particolare:
T(n)\\
=\\
Θ(1) \qquad per\ n = 1\\
Θ(n log n) \qquad per\ n > 1
Il caso peggiore
Se il pivot è uno degli estremi dell'array, si creano due partizioni sbilanciate: una delle due sottoliste è sempre vuota!
Allora:
T(n) = T(n-1) + Θ(n)\\
= T(n-2) + Θ(n-1) + Θ(n)\\
= T(n-3) + Θ(n-2) + Θ(n-1) + Θ(n)\\
= …
∈ Θ(n^2)
"Non date da mangiare sequenze ordinate al Quicksort, gli sono indigeste."
Pseudocodice
def partition(partizione, inizio, fine):
"""Dividi una partizione in due, usando l'ultimo elemento come pivot.
Note utili:
partizione[fine] è il pivot
partizione[maggiori] è il primo numero dei maggiori
partizione[non_iterati] è l'elemento su cui si sta iterando al momento"""
# Crea il primo separatore (la | pipe nell'esempio)
maggiori = inizio
# Crea il secondo separatore (la ¦ broken pipe nell'esempio)
non_iterati = inizio
# Itera su ogni numero tra inizio e fine (escluso!)
while non_iterati < fine:
# Se l'elemento su cui stiamo iterando è minore del pivot
if partizione[non_iterati] <= partizione[fine]:
# Mettilo nell'insieme dei minori, scambiandolo con il primo numero dei maggiori e incrementando il primo separatore
partizione[maggiori], partizione[non_iterati] = partizione[non_iterati], partizione[maggiori]
maggiori += 1
# Incrementa sempre il secondo separatore
non_iterati += 1
# Inserisci il pivot tra le due sottopartizioni create,
partizione[fine], partizione[non_iterati] = partizione[non_iterati], partizione[fine]
return maggiori
Visualizzazione
visualgo.net (Nota: invece che prendere l'ultimo numero come pivot prende il primo, cambiando leggermente l'algoritmo.)
Note per l'esame
La domanda che fa sempre è "Qual è la sequenza di pivot utilizzata?"
Elementi da soli non vengono presi come pivot!