Un montículo es una estructura de datos que permite implementar una cola de prioridad, la cual resuelve eficientemente operaciones como obtener el máximo de un conjunto de elementos.
Un mónticulo es básicamente un árbol binario 'casi' completo, es decir todos los niveles están llenos, excepto posiblemente el último. Un mónticulo se representa por un arreglo A[1..n]. El elemento A[1] corresponde a la raíz del árbol, los elementos A[2] y A[3] al primer nivel y así sucesivamente. La siguiente función imprime un arreglo conteniendo un móntículo de manera que se pueda observar la estructura del ábol.
from math import log
def pretty_print_heap(A):
n = len(A) - 1 # the heap starts at position 1, so A[0] is ignored
for h in range(int(log(n,2))):
nspc = (2**(int(log(n,2)) - h + 1) - 2)
line = nspc * ' '
for i in range(2**h, 2**(h+1)):
line += repr(A[i]).ljust(2) + (2*nspc + 2)*' '
print line
line = ''
for i in range(2**(int(log(n,2))), n+1):
line += repr(A[i]).ljust(2) + ' '
print line
pretty_print_heap(range(0,20))
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
El padre de un node en la posición i es calculado por:
def parent(i):
return int(i / 2)
el hijo izquierdo es calculado por:
def left(i):
return 2 * i
y el hijo derecho por:
def right(i):
return 2 * i + 1
La propiedad importante que todo montículo debe cumplir es que el padre debe ser mayor o igual a sus hijos (si es un max-heap): $$ \forall 1 \le i \le n, A[parent(i)] \ge A[i]$$
Por ejemplo, el siguiente arreglo no cumple con la propiedad:
A = [0, 16, 7, 10, 8, 14, 9, 3, 2, 4, 1]
pretty_print_heap(A)
16 7 10 8 14 9 3 2 4 1
pues la posición A[2] es menor que su hijo derecho A[5] y que su hijo izquierdo A[4]. Mientras que este arreglo si tiene la propiedad:
A = [0, 16, 14, 10, 8, 7, 9, 3, 2, 4, 1]
pretty_print_heap(A)
16 14 10 8 7 9 3 2 4 1
Suponga que en el anterior arreglo, se modifica la primera posición por un número menor de 10, por ejemplo 6, en ese caso se pierde la propiedad de montículo, aunque el sub-árbol derecho e izquierdo todavía la conservan. La siguiente función restablece la propiedad mediante un procedimiento recursivo:
def max_heapify(A, i, trace=True):
if trace: # este código solo se ejecuta si trace==True
print "-----------------------"
print "i =", i
print A
pretty_print_heap(A)
l = left(i)
r = right(i)
if l <= (len(A)- 1) and A[l] > A[i]:
largest = l
else:
largest = i
if r <= len(A)- 1 and A[r] > A[largest]:
largest = r
if largest <> i:
A[i], A[largest] = A[largest], A[i]
max_heapify(A, largest, trace)
Probemos la función max_heapify en el siguiente arreglo:
max_heapify(A, 1)
----------------------- i = 1 [0, 16, 14, 10, 8, 7, 9, 3, 2, 4, 1] 16 14 10 8 7 9 3 2 4 1
El problema ahora es como partiendo de un arreglo en un orden arbitrario, modificarlo de manera que la propiedad de montículo se cumpla. La idea es usar la función max-heapify de la base del árbol hacia arriba, restableciendo la propiedad de montículo, nivel por nivel. Esta estrategia se implementa en la siguiente función:
def build_max_heap(A, trace=False):
for i in range(int(len(A) / 2), 0, -1):
if trace:
# The next 4 lines of code just print the heap
print "-----------------------"
print "i =", i
print A
pretty_print_heap(A)
max_heapify(A, i, trace=False)
Ahora aplicamos la función a un arreglo con los números del 1 al 10:
A = range(11)
build_max_heap(A, trace= True)
----------------------- i = 5 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 1 2 3 4 5 6 7 8 9 10 ----------------------- i = 4 [0, 1, 2, 3, 4, 10, 6, 7, 8, 9, 5] 1 2 3 4 10 6 7 8 9 5 ----------------------- i = 3 [0, 1, 2, 3, 9, 10, 6, 7, 8, 4, 5] 1 2 3 9 10 6 7 8 4 5 ----------------------- i = 2 [0, 1, 2, 7, 9, 10, 6, 3, 8, 4, 5] 1 2 7 9 10 6 3 8 4 5 ----------------------- i = 1 [0, 1, 10, 7, 9, 5, 6, 3, 8, 4, 2] 1 10 7 9 5 6 3 8 4 2
La función puede ser usada para ordenar un arreglo. La idea es inicialmente construir un montículo y después iterativamente extraemos el elemento máximo y lo insertamos en un nuevo arreglo que al final del procedimiento estará ordenado.
def heap_sort(A):
B = []
build_max_heap(A)
while len(A)>1:
maxim = A[1]
A[1] = A[-1]
del A[-1]
B.insert(0,maxim)
print B
max_heapify(A,1,trace=False)
return B
print heap_sort([0,4,6,7,2,5,9])
[9] [7, 9] [6, 7, 9] [5, 6, 7, 9] [4, 5, 6, 7, 9] [2, 4, 5, 6, 7, 9] [2, 4, 5, 6, 7, 9]
La estructura montículo puede ser usada para implementar una cola de prioridades. En esta se pretende mantener organizados un conjunto de ítems de acuerdo con un criterio (la prioridad), de manera que sea pueda obtener el elemento con prioridad máxima de manera eficiente. La cola de prioridad debe soportar las siguientes operaciones:
def extract_max_heap(A):
maxim = A[1]
A[1] = A[-1]
del A[-1]
max_heapify(A,1,trace=False)
return maxim
A = [0, 16, 7, 10, 8, 14, 9, 3, 2, 4, 1]
build_max_heap(A)
pretty_print_heap(A)
while len(A)>2:
print "-----------------------"
print "Extracted element:", extract_max_heap(A)
pretty_print_heap(A)
16 14 10 8 7 9 3 2 4 1 ----------------------- Extracted element: 16 14 8 10 4 7 9 3 2 1 ----------------------- Extracted element: 14 10 8 9 4 7 1 3 2 ----------------------- Extracted element: 10 9 8 3 4 7 1 2 ----------------------- Extracted element: 9 8 7 3 4 2 1 ----------------------- Extracted element: 8 7 4 3 1 2 ----------------------- Extracted element: 7 4 2 3 1 ----------------------- Extracted element: 4 3 2 1 ----------------------- Extracted element: 3 2 1 ----------------------- Extracted element: 2 1
def increase_key_heap(A,i,v):
if A[i]>v:
print "Error"
A[i] = v
print "-----------------------"
print "Changed heap:"
pretty_print_heap(A)
while i>1 and A[i>>1] < A[i]:
A[i>>1], A[i] = A[i], A[i>>1]
# The next 4 lines of code just print the heap
print "-----------------------"
print A
pretty_print_heap(A)
print "i =", i
##############################################
i = i>>1
A = [0, 16, 7, 10, 8, 14, 9, 3, 2, 4, 1]
build_max_heap(A)
pretty_print_heap(A)
increase_key_heap(A,7,17)
16 14 10 8 7 9 3 2 4 1 ----------------------- Changed heap: 16 14 10 8 7 9 17 2 4 1 ----------------------- [0, 16, 14, 17, 8, 7, 9, 10, 2, 4, 1] 16 14 17 8 7 9 10 2 4 1 i = 7 ----------------------- [0, 17, 14, 16, 8, 7, 9, 10, 2, 4, 1] 17 14 16 8 7 9 10 2 4 1 i = 3
def insert_heap(A,v):
A.append(v-1)
increase_key_heap(A,len(A)-1,v)
A = [0, 16, 7, 10, 8, 14, 9, 3, 2, 4, 1]
build_max_heap(A)
pretty_print_heap(A)
insert_heap(A,18)
16 14 10 8 7 9 3 2 4 1 ----------------------- Changed heap: 16 14 10 8 7 9 3 2 4 1 18 ----------------------- [0, 16, 14, 10, 8, 18, 9, 3, 2, 4, 1, 7] 16 14 10 8 18 9 3 2 4 1 7 i = 11 ----------------------- [0, 16, 18, 10, 8, 14, 9, 3, 2, 4, 1, 7] 16 18 10 8 14 9 3 2 4 1 7 i = 5 ----------------------- [0, 18, 16, 10, 8, 14, 9, 3, 2, 4, 1, 7] 18 16 10 8 14 9 3 2 4 1 7 i = 2