QuickSort es uno de los ordenamientos más usados en la práctica por su eficiencia promedio O(n log n) y su bajo uso de memoria. Funciona al elegir un pivote, particionar la lista en torno a él y ordenar recursivamente las particiones.
El pivote define un punto de corte: todos los elementos menores (o iguales) quedan a su izquierda y los mayores a su derecha. Luego QuickSort se aplica recursivamente a cada lado hasta que los subarrays sean de tamaño 0 o 1.
Los dos esquemas más usados son Lomuto y Hoare. Lomuto es más simple; Hoare reduce intercambios.
# Particionamiento de Lomuto
def particion_lomuto(arr, inicio, fin):
pivote = arr[fin]
i = inicio - 1
for j in range(inicio, fin):
if arr[j] <= pivote:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[fin] = arr[fin], arr[i + 1]
return i + 1
# Particionamiento de Hoare
def particion_hoare(arr, inicio, fin):
pivote = arr[(inicio + fin) // 2]
i = inicio - 1
j = fin + 1
while True:
i += 1
while arr[i] < pivote:
i += 1
j -= 1
while arr[j] > pivote:
j -= 1
if i >= j:
return j # indice que separa las particiones
arr[i], arr[j] = arr[j], arr[i]
Tras particionar, se invoca QuickSort sobre cada lado. El esquema de llamada cambia según la función de partición:
def quicksort_lomuto(arr, inicio, fin):
if inicio >= fin:
return
p = particion_lomuto(arr, inicio, fin)
quicksort_lomuto(arr, inicio, p - 1)
quicksort_lomuto(arr, p + 1, fin)
def quicksort_hoare(arr, inicio, fin):
if inicio >= fin:
return
p = particion_hoare(arr, inicio, fin)
quicksort_hoare(arr, inicio, p)
quicksort_hoare(arr, p + 1, fin)
Versión completa usando mediana de tres y partición de Lomuto:
def mediana_de_tres(arr, inicio, fin):
medio = (inicio + fin) // 2
if arr[medio] < arr[inicio]:
arr[medio], arr[inicio] = arr[inicio], arr[medio]
if arr[fin] < arr[inicio]:
arr[fin], arr[inicio] = arr[inicio], arr[fin]
if arr[fin] < arr[medio]:
arr[fin], arr[medio] = arr[medio], arr[fin]
return medio
def particion_lomuto_mediana(arr, inicio, fin):
m = mediana_de_tres(arr, inicio, fin)
arr[m], arr[fin] = arr[fin], arr[m] # mover pivote al final
pivote = arr[fin]
i = inicio - 1
for j in range(inicio, fin):
if arr[j] <= pivote:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[fin] = arr[fin], arr[i + 1]
return i + 1
def quicksort(arr, inicio, fin):
if inicio >= fin:
return
p = particion_lomuto_mediana(arr, inicio, fin)
quicksort(arr, inicio, p - 1)
quicksort(arr, p + 1, fin)
O(n log n) gracias a particiones balanceadas en expectativa.O(n^2) cuando el pivote genera particiones muy desbalanceadas (datos ya ordenados con pivote fijo).<= 16) mejora las constantes y evita recursiones profundas.O(1) memoria extra; fácil de adaptar a distintos tipos de datos.QuickSort sigue siendo la opción por defecto en muchas librerías gracias a su rendimiento promedio y bajo consumo de memoria.
Siguiendo el formato del tutorial, reunimos todo en ordenamientos.py para ejecutar rápido.
# archivo: ordenamientos.py
def mediana_de_tres(arr, inicio, fin):
medio = (inicio + fin) // 2
if arr[medio] < arr[inicio]:
arr[medio], arr[inicio] = arr[inicio], arr[medio]
if arr[fin] < arr[inicio]:
arr[fin], arr[inicio] = arr[inicio], arr[fin]
if arr[fin] < arr[medio]:
arr[fin], arr[medio] = arr[medio], arr[fin]
return medio
def particion_lomuto_mediana(arr, inicio, fin):
m = mediana_de_tres(arr, inicio, fin)
arr[m], arr[fin] = arr[fin], arr[m]
pivote = arr[fin]
i = inicio - 1
for j in range(inicio, fin):
if arr[j] <= pivote:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i + 1], arr[fin] = arr[fin], arr[i + 1]
return i + 1
def quicksort(arr, inicio, fin):
if inicio >= fin:
return
p = particion_lomuto_mediana(arr, inicio, fin)
quicksort(arr, inicio, p - 1)
quicksort(arr, p + 1, fin)
datos = [29, 10, 14, 37, 13, 5, 88, 1]
quicksort(datos, 0, len(datos) - 1)
print(datos)
Con esta base puedes intercambiar el esquema de pivote o la función de partición sin cambiar la interfaz pública.