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).

 

 

Next Post Previous Post