Los heaps son árboles binarios completos que mantienen la propiedad de orden parcial: cada nodo es mayor o igual que sus hijos (max-heap) o menor o igual (min-heap). Gracias a esta propiedad, permiten acceder rápidamente al elemento de mayor o menor prioridad y son la base de las colas de prioridad eficientes.
En Python se representan sobre listas y se manejan con el módulo heapq, que opera con complejidad logarítmica para insertar o extraer.
Conocer estos términos agiliza la lectura de los algoritmos de heapify e inserción.
La lista almacena el heap nivel por nivel. A partir de un índice i se calculan sus vecinos sin punteros adicionales:
(i - 1) / 2 (truncando decimales).2 * i + 1.2 * i + 2.Esta representación es compacta y permite reducir el uso de memoria respecto a las listas enlazadas.
En ambos heaps las operaciones fundamentales son heappush e heappop. heapq implementa min-heap por defecto; para simular un max-heap se suele invertir el signo de la prioridad.
import heapq
# Min-heap
h = []
heapq.heappush(h, 45)
heapq.heappush(h, 10)
heapq.heappush(h, 78)
heapq.heappush(h, 32)
print("Heap interno:", h)
while h:
print("extraido:", heapq.heappop(h))
# Max-heap usando prioridades negativas
max_heap = []
heapq.heappush(max_heap, -45)
heapq.heappush(max_heap, -10)
heapq.heappush(max_heap, -78)
heapq.heappush(max_heap, -32)
print("Max-heap interno:", max_heap)
while max_heap:
print("extraido max:", -heapq.heappop(max_heap))
Ambas operaciones mantienen la complejidad O(log n). Para un max-heap se invierte el signo de las prioridades o se envuelven los valores en una tupla con prioridad negativa.
Los heaps pueden invertirse con una sola condición de comparación o utilizando un factor signo que se multiplica por cada valor. Elegir uno u otro depende del problema: Dijkstra requiere min-heap, mientras que un planificador que atiende prioridades altas usa max-heap.
En implementaciones genéricas se recibe un puntero a función comparadora para que el usuario decida la relación de orden.
Cuando se parte de una lista arbitraria es más eficiente aplicar heapify de abajo hacia arriba que insertar elemento por elemento. El siguiente algoritmo transforma la lista en O(n):
import heapq
datos = [5, 1, 9, 4, 2, 7]
heapq.heapify(datos) # convierte la lista en min-heap in-place
print("Heapificado:", datos)
Solo los nodos internos (hasta n/2) requieren reposicionarse porque los nodos hoja ya cumplen la propiedad del heap.
Heapsort construye un heap y luego extrae repetidamente para generar una secuencia ordenada. En Python puedes apoyarte en heapq o usar una aproximación simple:
import heapq
def heapsort(iterable):
h = list(iterable)
heapq.heapify(h)
return [heapq.heappop(h) for _ in range(len(h))]
print(heapsort([9, 1, 5, 3, 8, 2])) # [1, 2, 3, 5, 8, 9]
La complejidad es O(n log n) y no necesita memoria auxiliar distinta de la lista heapificada.
El siguiente programa crea un max-heap a partir de un min-heap invirtiendo las prioridades, inserta valores, extrae todos y luego demuestra cómo heapify acelera la construcción:
import heapq
class MaxHeap:
def __init__(self):
self._heap = []
self._contador = 0
def push(self, valor):
heapq.heappush(self._heap, (-valor, self._contador, valor))
self._contador += 1
def pop(self):
if not self._heap:
return None
_, _, valor = heapq.heappop(self._heap)
return valor
def heapify(self, valores):
self._heap = [(-v, i, v) for i, v in enumerate(valores)]
heapq.heapify(self._heap)
self._contador = len(valores)
def imprimir(self):
print("[heap] n=", len(self._heap), "->", [v for _, _, v in self._heap])
if __name__ == "__main__":
heap = MaxHeap()
for v in (45, 10, 78, 32):
heap.push(v)
heap.imprimir()
while len(heap._heap):
print("extraido:", heap.pop())
heap.imprimir()
heap.heapify([5, 1, 9, 4, 2, 7])
heap.imprimir()
Integra las versiones completas de inserción, extracción y heapify para ejecutar el ejemplo. Observa cómo los valores se devuelven en orden descendente a pesar de haberse cargado en desorden.