Binomial Heap
Binomial Heap is a data structure that combines the advantages of both binary heap and linked list. It is used to efficiently support operations like insertion, deletion, and merging of heaps. Each Binomial Heap is composed of multiple Binomial Trees, which are defined recursively.
In a Binomial Tree of order k, there are k children rooted at the root node. The order of the tree corresponds to the number of children it has. The unique property of Binomial Heap is that the root of each tree in the heap has a key greater than or equal to the keys of its children, making it suitable for priority queue applications.
Let's implement a Binomial Heap in Python:
class BinomialNode:
def __init__(self, value):
self.value = value
self.order = 0
self.child = None
self.sibling = None
class BinomialHeap:
def __init__(self):
self.head = None
def merge(self, heap):
new_heap = BinomialHeap()
new_heap.head = self.merge_trees(self.head, heap.head)
self.head = None
heap.head = None
return new_heap
def merge_trees(self, tree1, tree2):
if tree1 is None:
return tree2
if tree2 is None:
return tree1
if tree1.value < tree2.value:
tree1.child = self.merge_trees(tree1.child, tree2)
tree1.order += 1
return tree1
else:
tree2.child = self.merge_trees(tree2.child, tree1)
tree2.order += 1
return tree2
def insert(self, value):
heap = BinomialHeap()
heap.head = BinomialNode(value)
self.head = self.merge(heap).head
def find_min(self):
if self.head is None:
return None
min_node = self.head
current = self.head
while current.sibling is not None:
if current.sibling.value < min_node.value:
min_node = current.sibling
current = current.sibling
return min_node.value
def extract_min(self):
if self.head is None:
return None
min_node = self.head
prev_min_node = None
current = self.head
while current.sibling is not None:
if current.sibling.value < min_node.value:
min_node = current.sibling
prev_min_node = current
current = current.sibling
if prev_min_node is None:
self.head = min_node.child
else:
prev_min_node.sibling = min_node.sibling
new_heap = BinomialHeap()
new_heap.head = self.reverse_children(min_node.child)
self.head = self.merge(new_heap).head
return min_node.value
def reverse_children(self, node):
if node is None or node.sibling is None:
return node
reversed_child = self.reverse_children(node.sibling)
node.sibling.sibling = node
node.sibling = None
return reversed_child
Now, let's solve a LeetCode problem using the Binomial Heap we implemented. The problem is "Find Median from Data Stream," where we need to design a data structure that supports adding numbers and finding the median of all elements seen so far efficiently.
class MedianFinder:
def __init__(self):
self.max_heap = BinomialHeap()
self.min_heap = BinomialHeap()
def addNum(self, num):
if self.max_heap.find_min() is None or num < self.max_heap.find_min():
self.max_heap.insert(num)
else:
self.min_heap.insert(num)
if self.max_heap.head is not None and self.min_heap.head is not None:
if self.max_heap.head.order > self.min_heap.head.order:
self.max_heap, self.min_heap = self.min_heap, self.max_heap
while (
self.max_heap.head is not None
and self.min_heap.head is not None
and self.max_heap.head.order == self.min_heap.head.order
):
self.max_heap_child = self.max_heap.extract_min()
self.min_heap_child = self.min_heap.extract_min()
self.max_heap.insert(self.max_heap_child)
self.max_heap.insert(self.min_heap_child)
def findMedian(self):
if self.max_heap.head is None and self.min_heap.head is None:
return None
if self.max_heap.head.order > self.min_heap.head.order:
return float(self.max_heap.find_min())
median = (self.max_heap.find_min() + self.min_heap.find_min()) / 2
return median
The MedianFinder
class uses two Binomial Heaps, one for storing the smaller half of the numbers (max heap) and another for storing the larger half of the numbers (min heap). The addNum
function inserts the number into the appropriate heap based on the current median. The findMedian
function calculates the median by comparing the sizes of the two heaps and finding the middle element(s).
The time complexity of the addNum
function is O(log n), where n is the total number of elements inserted. The findMedian
function has a time complexity of O(1).