Check if an heap is a min-max heap

1.9k Views Asked by At

I've written a min-max heap, i.e. an heap where finding the minimum and the maximum are constant operations. Now I want to create tests for my class, so I decided to implement a function that checks if a heap is a min-max-heap. Here it is, but I'm not sure it's 100% correct.

def is_min_max_heap(h):
    if not isinstance(h, MinMaxHeap):
        return False
    if h.heap:
        for item in h.heap:
            if not isinstance(item, HeapNode):
                return False
        for i, item in reversed(list(enumerate(h.heap))):
            g = h.grandparent_index(i)
            if g is not None:
                if h.is_on_even_level(i):
                    if h.heap[g] > item:
                        return False
                else:
                    if h.heap[g] < item:
                        return False            
    return True     

Note that elements of this heap are represented with the class HeapNode, and that's why I am checking if self.heap only contains objects of that class. Even levels are for example 0, 2, 4, etc. The minimum of this heap is found at self.heap[0]. The maximum is max(self.heap[1], self.heap[2]) (provided that both exist). h.grandparent_index(i) returns None if the grandparent of the node at i does not exist.

The idea of my algorithm is very simple. I start from the bottom, and I check if I'm on an even of odd level. If on an even level, then I must make sure that the element is greater than its grandparent. If I'm on a odd level, I must make sure it's smaller than its grandparent.

Is my algorithm correct? Am I missing some points? If it's correct, suggestions to improve it are well-accepted.

Eventually my implementation might be useful for someone else.

Edit 1

I've just noticed that my function checks if the elements in the even (and respectively odd) levels are correctly disposed to each other, but it does not check if the maximum element is found either at self.heap[1] or self.heap[2] and that the minimum element is at self.heap[0].

Edit 2

I'm adding the new updated code according to edit 1 and to the answer of @goCards.

def is_min_max_heap(h) -> bool:
    """Returns `True` if `h` is a valid `MinMaxHeap` object. `False` otherwise."""
    if not isinstance(h, MinMaxHeap):
        return False

    if h.heap:
        for item in h.heap:
            if not isinstance(item, HeapNode):
                return False

        if h.size() == 1:
            return True

        if h.size() == 2:
            return max(h.heap) == h.heap[1] and min(h.heap) == h.heap[0]

        if h.size() >= 3:
            if (h.heap[0] != min(h.heap) or
                (h.heap[1] != max(h.heap) and
                 h.heap[2] != max(h.heap))):
                return False

        for i, item in reversed(list(enumerate(h.heap))):
            p = h.parent_index(i)

            if p != -1:
                if h.is_on_even_level(i):
                    if h.heap[p] < item:
                        return False
                else:
                    if h.heap[p] > item:
                        return False

            g = h.grandparent_index(i)
            if g != -1:
                if h.is_on_even_level(i):
                    if h.heap[g] > item:
                        return False
                else:
                    if h.heap[g] < item:
                        return False
    return True
2

There are 2 best solutions below

0
On

Your algorithm is missing some checks. Consider the example below that is not a min-max heap but passes your test. Consider 5 to be the root. The root has another branch but it is not shown for simplicity.

Using your algorithm, the heap below is declared as a min-max heap, but it does not satisfy min-max heap properties. Your algorithm needs to check the parent node as well.

EDIT: A min-max heap is a binary tree that satisfies two properties:

1) T has the heap-shape

2) T is min-max ordered: values stored at nodes on even (odd) levels are smaller (greater) than or equal to the values stored at their descendants (if any) where the root is at level zero.

Example

for i, item in reversed(list(enumerate(h.heap))):
    g = h.grandparent_index(i)
    p = h.parent_index(i)
    if g is not None and p is not None:
        if h.is_on_even_level(i):
            if item > h.heap[g]: pass #grandparent should be smallest in its subtree
            else: return False
            if item < h.heap[p]: pass #parent should be greatest in its subtree
            else: return False
        else: #odd level
            if item < h.heap[g]: pass #grandparent should be greatest in its subtree
            else: return False
            if item > h.heap[p]: pass #parent should be smallest in its subtree
            else: return False
2
On

A simpler way is to remove elements from the heap. The algorithm would be something like this:

  • pop min-max and store them in an array
  • repeat until you no longer have elements in the heap
  • check if the array have the characteristics that you expect.

Checking the array you generate after you pop all elements in the heap, you should have even indices to be strictly increasing and odd indices strictly decreasing. If that is not true, your heap implementation is wrong.