Uno degli algoritmi di ordinamento più popolari è QuickSort. Si tratta di un approccio “divide-et-impera” che, a ogni passaggio, sceglie un elemento pivot dall’array, suddivide l’array in tre parti — gli elementi minori, gli elementi uguali e gli elementi maggiori rispetto al pivot — e ripete questo procedimento fino a quando l’intero array risulta ordinato.
In pratica, a ogni iterazione:
Scegliamo un elemento pivot dall’array.
Spostiamo gli elementi minori nella parte sinistra rispetto al pivot.
Spostiamo gli elementi maggiori nella parte destra rispetto al pivot.
Mettiamo tutti gli elementi uguali al pivot tra quelli minori e quelli maggiori.
Ripetiamo questo procedimento sia sulla parte sinistra sia su quella destra dell’array.
def quicksort(a):
if len(a) <= 1:
return a
pivot = a[len(a) // 2]
left = [x for x in a if x < pivot]
middle = [x for x in a if x == pivot]
right = [x for x in a if x > pivot]
return quicksort(left) + middle + quicksort(right)
Questa implementazione di QuickSort utilizza array aggiuntivi per le parti left, middle e right. È possibile renderla “in place” modificando il codice per usare gli indici e riordinare gli elementi direttamente all’interno dell’array.
def quicksort(a, start, end):
if start >= end: # Nessun elemento da ordinare
return
pivot = a[start] # Scegli un pivot
l, r = start + 1, end # Ordina a[start+1 ... end]
while l <= r: # Finché ci sono elementi nel range
if a[l] < pivot: # se a[l] < pivot => lascia a[i] invariato
l += 1 # Incrementa l'indice sinistro
else: # altrimenti sposta a[l] alla fine
a[l], a[r] = a[r], a[l] # Scambia a[l] con a[r]
r -= 1 # Decrementa l'indice destro
a[start], a[r] = a[r], a[start] # Scambia pivot con a[r]
quicksort(a, start, r - 1) # Ordina a[start ... r-1]
quicksort(a, r + 1, end) # Ordina a[r+1 ... end]
Avrai notato che la scelta del pivot differisce in queste due implementazioni. L’algoritmo QuickSort non impone un vincolo rigido su quale pivot scegliere. Esistono diverse strategie possibili. Eccone alcune:
Primo elemento: a[start], come mostrato nella seconda implementazione.
Ultimo elemento: a[end].
Elemento in posizione intermedia: a[(start + end + 1) // 2].
Elemento casuale: un elemento scelto a caso in a[start ... end].
Elemento mediano: la mediana del segmento corrente — garantisce suddivisioni bilanciate tra left e right, ma richiede un’implementazione più sofisticata.
Vediamo ora una simulazione dell’algoritmo su alcuni array:
Il tempo di esecuzione medio di QuickSort è . Tuttavia, a seconda della scelta del pivot, l’algoritmo può arrivare a un comportamento di tipo quadratico .
🤔 Riesci a trovare un esempio in cui, scegliendo sempre come pivot l’elemento in posizione start, l’algoritmo esegua operazioni?
Sfida
Dato un elenco di n interi e q pivot, si richiede di spostare tutti gli elementi minori del pivot a sinistra e tutti gli elementi maggiori a destra dell’elemento pivot, mantenendo i valori uguali al pivot raggruppati fra i minori e i maggiori.
Input
La prima riga dell’input contiene un singolo intero n (1 ≤ n ≤ 100 000).
La riga successiva contiene una lista di n interi separati da spazio ( ≤ ≤ ).
La terza riga contiene un singolo intero q (1 ≤ q ≤ 10).
La riga successiva contiene q interi separati da spazio (1 ≤ ≤ n) che rappresentano l’indice dell’elemento pivot.
Output
Per ognuno dei q pivot, il programma dovrebbe stampare l’array risultante dopo il riarrangiamento. Se esistono più risposte possibili, il programma dovrebbe stamparne una qualunque.