HeapSort combina un montículo binario con un esquema in-place para lograr O(n log n) en todos los casos, sin memoria extra significativa. Es útil cuando queremos evitar el peor caso de QuickSort y minimizar el uso de pila.
Un heap binario es un árbol completo donde cada nodo es mayor o igual (max-heap) o menor o igual (min-heap) que sus hijos. Esto permite extraer el máximo o mínimo en O(log n).
Un heap completo se mapea en una lista de forma contigua:
i: 2 * i + 1i: 2 * i + 2i: (i - 1) / 2Construimos un max-heap en tiempo lineal recorriendo desde el último nodo interno hacia la raíz y aplicando sift-down:
def sift_down(arr, n, i):
mayor = i
hijo_izq = 2 * i + 1
hijo_der = 2 * i + 2
if hijo_izq < n and arr[hijo_izq] > arr[mayor]:
mayor = hijo_izq
if hijo_der < n and arr[hijo_der] > arr[mayor]:
mayor = hijo_der
if mayor != i:
arr[i], arr[mayor] = arr[mayor], arr[i]
sift_down(arr, n, mayor)
def heapify(arr):
n = len(arr)
for i in range((n // 2) - 1, -1, -1):
sift_down(arr, n, i)
sift_down garantiza que el nodo i cumpla la propiedad de heap, desplazando el valor hacia abajo hasta que sea mayor que sus hijos. Es la operación clave en heapify y en cada extracción.
Para ordenar de forma ascendente con un max-heap:
sift_down en la raíz para restaurar el orden.def heapsort(arr):
heapify(arr)
for fin in range(len(arr) - 1, 0, -1):
arr[0], arr[fin] = arr[fin], arr[0]
sift_down(arr, fin, 0) # heap reducido
Unificación de las funciones clave:
def sift_down(arr, n, i):
mayor = i
hijo_izq = 2 * i + 1
hijo_der = 2 * i + 2
if hijo_izq < n and arr[hijo_izq] > arr[mayor]:
mayor = hijo_izq
if hijo_der < n and arr[hijo_der] > arr[mayor]:
mayor = hijo_der
if mayor != i:
arr[i], arr[mayor] = arr[mayor], arr[i]
sift_down(arr, n, mayor)
def heapify(arr):
n = len(arr)
for i in range((n // 2) - 1, -1, -1):
sift_down(arr, n, i)
def heapsort(arr):
heapify(arr)
for fin in range(len(arr) - 1, 0, -1):
arr[0], arr[fin] = arr[fin], arr[0]
sift_down(arr, fin, 0)
O(n log n) en todos los casos.O(1) adicional; trabaja in-place.O(n log n) sin riesgo de peor caso cuadrático; in-place; no depende de recursión profunda.Siguiendo el formato del tutorial, reunimos todo en ordenamientos.py para ejecutar rápido.
# archivo: ordenamientos.py
def sift_down(arr, n, i):
mayor = i
hijo_izq = 2 * i + 1
hijo_der = 2 * i + 2
if hijo_izq < n and arr[hijo_izq] > arr[mayor]:
mayor = hijo_izq
if hijo_der < n and arr[hijo_der] > arr[mayor]:
mayor = hijo_der
if mayor != i:
arr[i], arr[mayor] = arr[mayor], arr[i]
sift_down(arr, n, mayor)
def heapify(arr):
n = len(arr)
for i in range((n // 2) - 1, -1, -1):
sift_down(arr, n, i)
def heapsort(arr):
heapify(arr)
for fin in range(len(arr) - 1, 0, -1):
arr[0], arr[fin] = arr[fin], arr[0]
sift_down(arr, fin, 0)
datos = [16, 7, 3, 20, 17, 8, 10, 1]
heapsort(datos)
print(datos)
Con esta base puedes ajustar el esquema de sift_down o instrumentar conteos de comparaciones sin cambiar la interfaz.