Skip to content

Conversation

HouseOfVange
Copy link

Heaps Practice

Congratulations! You're submitting your assignment!

Comprehension Questions

Question Answer
How is a Heap different from a Binary Search Tree? a heap is always balanced
Could you build a heap with linked nodes? i think so. it would change how i wrote the heap up and heap down functions tho
Why is adding a node to a heap an O(log n) operation? because when u add a node it is the last item in the heap, and then the parent node has to be considered. the worst case is if the new node needs to heap-up to the top o the heap, but each time a node is swapped in a heap-up 1/2 the nodes in the heap are eliminated.
Were the heap_up & heap_down methods useful? Why? YES - they helped me maintain the single responsibility principal, and in the case of my heap_down function i used heap_down recursively!

thanks for looking at this down to the wire assignment!!!

@anselrognlie anselrognlie self-requested a review July 23, 2022 01:39
Copy link

@anselrognlie anselrognlie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💫 Overall, a nice implementation, Vange. For the comprehension questions, we can also say that a heap is weakly ordered compared to a BST. And from your response I see that mixing iterative and recursive approaches for heap_up and heap_down was intentional, but be aware that this actually has a complexity impact. While your add (heap_up) is time O(log n) and space O(1), your remove (head_down) is time O(log n) and space O(log n). In general, in Python an iterative approach will consume less space than a recursive approach.

I left some comments on your implementation below. Notice a tricky little issue in heap_down.

🟢

Comment on lines +22 to +23
Time Complexity: ? O(log n)
Space Complexity: ? O(1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨ Great! In the worst case, the new value we're inserting is the new root of the heap, meaning it would need to move up the full height of the heap (which is log n levels deep). Your implementation of the heap_up helper is iterative, meaning that while we do have to loop log n times, we don't have any recursive stack overhead. So the space complexity is constant (O(1)).

''' i tried to import MinHeap from the other file but it wasn't passing pytest tests
this is what i was writing:
from min_heap import MinHeap

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could either do

from .min_heap import MinHeap

or

from heaps.min_heap import MinHeap

Space Complexity: ? O(1)
"""
pass
if value == None:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 Prefer using is to compare to None


# Act
returned_items = ["Donuts", "Pizza", "Pasta", "Soup", "Cookies", "Cake"]
# assert heap.store == returned_items

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can't make a check like this since the store is a list of heap nodes, not just the values. We also wouldn't generally expect the order of the items being returned to match the final retrieved order, since a heap is only loosely ordered. We only know that the smallest (or largest if a max heap) value is at the front, but we can't say with certainty the specific ordering of the remaining items.

Comment on lines +35 to +36
Time Complexity: ? O(log n)
Space Complexity: ? O(1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the cost of add mostly comes from heap_up, the main cost of remove comes from heap_down. However, while you heap_up is implemented iteratively, achieving O(1) space complexity (and O(log n) time), your heap_down is implemented recursively. It still at worst must move the repositioned value from the top of the heap to the bottom (O(log n) operations), but for each move, it also makes a recursive call, adding stack space to the complexity. So this is O(log n) for both time and space complexity.

Comment on lines +86 to +88
if left_child_index <= len(self.store) - 1:
return True
return False

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 Performing a boolean check (if condition) then returning a boolean is an anti-pattern. We can return the value of the condition directly.

        return left_child_index <= len(self.store) - 1

Comment on lines +101 to +102
time complexity O(log n)
space complexity O(1)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 Nice of you to include the complexity here when we forgot to ask for it!

This is where the O(log n) time and space complexity in remove comes from. Since heap_down calls itself recursively, the space complexity will grow with the depth of the stack, which is the height of the heap, or O(log n).

Comment on lines +106 to +107
self.swap(index, left_child_index)
self.heap_down(left_child_index)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before deciding to swap here, we would also need to compare the left child to the right (if present). We only want to swap the one child which is the smallest.

Currently, if the left child were smaller then the parent, but larger than the right, first we would continue swapping the parent down the left subtree, then potentially need to swap the new parent down the right sub tree. Consider the arrangement [3, 2, 1]. 3 > 2 so it would swap with the left child → [2, 3, 1]. But notice the heap property is not re-established since 2 > 1. This code will continue and swap 2 and 1 → [1, 3, 2]. But if we had compare the values of the children first, we could see that 1 < 2 and swap that with 3 directly → [1, 2, 3] which re-establishes the heap in a single swap.

Comment on lines +80 to +81
Time Complexity: ? O(n)
Space Complexity: ? O(n)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👀 The theoretical best time complexity for a general sorting algorithm (like heap sort) is O(n log n). Here, we can see that we add n things to the heap (which each take O(log n)), then we remove n things from the heap, which again takes O(log n) each, for a total of O(2 n log n) → O(n log n). The space complexity is O(n) due to the internal store that MinHeap uses.

for num in list:
da_heap.add(num)

while len(result) != len(list):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using the empty helper

    while not da_heap.empty():

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants