Skip to content

Instantly share code, notes, and snippets.

@kilian-gebhardt
Last active May 19, 2024 11:55
Show Gist options
  • Save kilian-gebhardt/6f1db877797d69fa1df6aa936feea607 to your computer and use it in GitHub Desktop.
Save kilian-gebhardt/6f1db877797d69fa1df6aa936feea607 to your computer and use it in GitHub Desktop.
Min-max heap in Python
class MinMaxHeap(object):
"""
Implementation of a Min-max heap following Atkinson, Sack, Santoro, and
Strothotte (1986): https://doi.org/10.1145/6617.6621
"""
def __init__(self, reserve=0):
self.a = [None] * reserve
self.size = 0
def __len__(self):
return self.size
def insert(self, key):
"""
Insert key into heap. Complexity: O(log(n))
"""
if len(self.a) < self.size + 1:
self.a.append(key)
insert(self.a, key, self.size)
self.size += 1
def peekmin(self):
"""
Get minimum element. Complexity: O(1)
"""
return peekmin(self.a, self.size)
def peekmax(self):
"""
Get maximum element. Complexity: O(1)
"""
return peekmax(self.a, self.size)
def popmin(self):
"""
Remove and return minimum element. Complexity: O(log(n))
"""
m, self.size = removemin(self.a, self.size)
return m
def popmax(self):
"""
Remove and return maximum element. Complexity: O(log(n))
"""
m, self.size = removemax(self.a, self.size)
return m
def level(i):
return (i+1).bit_length() - 1
def trickledown(array, i, size):
if level(i) % 2 == 0: # min level
trickledownmin(array, i, size)
else:
trickledownmax(array, i, size)
def trickledownmin(array, i, size):
if size > i * 2 + 1: # i has children
m = i * 2 + 1
if i * 2 + 2 < size and array[i*2+2] < array[m]:
m = i*2+2
child = True
for j in range(i*4+3, min(i*4+7, size)):
if array[j] < array[m]:
m = j
child = False
if child:
if array[m] < array[i]:
array[i], array[m] = array[m], array[i]
else:
if array[m] < array[i]:
if array[m] < array[i]:
array[m], array[i] = array[i], array[m]
if array[m] > array[(m-1) // 2]:
array[m], array[(m-1)//2] = array[(m-1)//2], array[m]
trickledownmin(array, m, size)
def trickledownmax(array, i, size):
if size > i * 2 + 1: # i has children
m = i * 2 + 1
if i * 2 + 2 < size and array[i*2+2] > array[m]:
m = i*2+2
child = True
for j in range(i*4+3, min(i*4+7, size)):
if array[j] > array[m]:
m = j
child = False
if child:
if array[m] > array[i]:
array[i], array[m] = array[m], array[i]
else:
if array[m] > array[i]:
if array[m] > array[i]:
array[m], array[i] = array[i], array[m]
if array[m] < array[(m-1) // 2]:
array[m], array[(m-1)//2] = array[(m-1)//2], array[m]
trickledownmax(array, m, size)
def bubbleup(array, i):
if level(i) % 2 == 0: # min level
if i > 0 and array[i] > array[(i-1) // 2]:
array[i], array[(i-1) // 2] = array[(i-1)//2], array[i]
bubbleupmax(array, (i-1)//2)
else:
bubbleupmin(array, i)
else: # max level
if i > 0 and array[i] < array[(i-1) // 2]:
array[i], array[(i-1) // 2] = array[(i-1) // 2], array[i]
bubbleupmin(array, (i-1)//2)
else:
bubbleupmax(array, i)
def bubbleupmin(array, i):
while i > 2:
if array[i] < array[(i-3) // 4]:
array[i], array[(i-3) // 4] = array[(i-3) // 4], array[i]
i = (i-3) // 4
else:
return
def bubbleupmax(array, i):
while i > 2:
if array[i] > array[(i-3) // 4]:
array[i], array[(i-3) // 4] = array[(i-3) // 4], array[i]
i = (i-3) // 4
else:
return
def peekmin(array, size):
assert size > 0
return array[0]
def peekmax(array, size):
assert size > 0
if size == 1:
return array[0]
elif size == 2:
return array[1]
else:
return max(array[1], array[2])
def removemin(array, size):
assert size > 0
elem = array[0]
array[0] = array[size-1]
# array = array[:-1]
trickledown(array, 0, size - 1)
return elem, size-1
def removemax(array, size):
assert size > 0
if size == 1:
return array[0], size - 1
elif size == 2:
return array[1], size - 1
else:
i = 1 if array[1] > array[2] else 2
elem = array[i]
array[i] = array[size-1]
# array = array[:-1]
trickledown(array, i, size - 1)
return elem, size-1
def insert(array, k, size):
array[size] = k
bubbleup(array, size)
def minmaxheapproperty(array, size):
for i, k in enumerate(array[:size]):
if level(i) % 2 == 0: # min level
# check children to be larger
for j in range(2 * i + 1, min(2 * i + 3, size)):
if array[j] < k:
print(array, j, i, array[j], array[i], level(i))
return False
# check grand children to be larger
for j in range(4 * i + 3, min(4 * i + 7, size)):
if array[j] < k:
print(array, j, i, array[j], array[i], level(i))
return False
else:
# check children to be smaller
for j in range(2 * i + 1, min(2 * i + 3, size)):
if array[j] > k:
print(array, j, i, array[j], array[i], level(i))
return False
# check grand children to be smaller
for j in range(4 * i + 3, min(4 * i + 7, size)):
if array[j] > k:
print(array, j, i, array[j], array[i], level(i))
return False
return True
def test(n):
from random import randint
a = [-1] * n
l = []
size = 0
for _ in range(n):
x = randint(0, 5 * n)
insert(a, x, size)
size += 1
l.append(x)
assert minmaxheapproperty(a, size)
assert size == len(l)
print(a)
while size > 0:
assert min(l) == peekmin(a, size)
assert max(l) == peekmax(a, size)
if randint(0, 1):
e, size = removemin(a, size)
assert e == min(l)
else:
e, size = removemax(a, size)
assert e == max(l)
l[l.index(e)] = l[-1]
l.pop(-1)
assert len(a[:size]) == len(l)
assert minmaxheapproperty(a, size)
print("OK")
def test_heap(n):
from random import randint
heap = MinMaxHeap(n)
l = []
for _ in range(n):
x = randint(0, 5 * n)
heap.insert(x)
l.append(x)
assert minmaxheapproperty(heap.a, len(heap))
assert len(heap) == len(l)
print(heap.a)
while len(heap) > 0:
assert min(l) == heap.peekmin()
assert max(l) == heap.peekmax()
if randint(0, 1):
e = heap.popmin()
assert e == min(l)
else:
e = heap.popmax()
assert e == max(l)
l[l.index(e)] = l[-1]
l.pop(-1)
assert len(heap) == len(l)
assert minmaxheapproperty(heap.a, len(heap))
print("OK")
@kilian-gebhardt
Copy link
Author

Hey @lewoudar, I have not tried to package cython so I cannot really help you with this… You certainly need to install cython/have it as a dependency – but I don't know if you can distribute this via pypi etc. Concerning the .pyx file: this is kind of a wrapper between the C/C++ world and the python world. Only from there you can use the .pxd-File and declare an instantiation of the generic Min_Max_Heap (see: https://github.com/kilian-gebhardt/MinMaxHeap/blob/master/testminmax.pyx). In a larger project, this could mean that you declare python-callable methods or a python-wrapper object in this file, which you then access from the rest of python your code base.

@lewoudar
Copy link

lewoudar commented Aug 1, 2023

Hi @kilian-gebhardt I found my mistake, it was a relative import issue from .minmaxheap cimport ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment