If you're new to this blog, it would be great if you could check out the posts on Binary Trees, BSTs, and AVLs.
Binary Tree , BST, AVL
Let's get started!
A Binary Heap is a Binary Tree with following properties:
A binary heap can be a Min heap or Max heap. In a Min heap, the root key is the smallest among all keys in the heap. This property must hold true for all nodes in the tree.
It's a complete tree (all levels are fully filled except possibly the last, which has keys as left as possible). This makes Binary Heaps suitable for storage in an array.
Due to the second property, it's ideal for implementation in an array or Python list, because we usually want to fill the maximum number of cells in a fixed-size array.
Why we need Binary Heap?
Let's look at a question: Find the minimum or the maximum number among a set of numbers in logN time. Also, make sure that insertion additional numbers does not take more than O(logN) time.
Possible solutions:
Store the numbers in sorted Array
| 10 | 20 | 30 | 40 | 50 | |
Find minimum: O(1)
Problem: When inserting an element (5) at the beginning of this list, we have to shift all elements one step to the right. In the worst case, this takes O(n) time complexity. So, this isn't a solution to our problem.
Store the numbers in Linked List in sorted manner
Problem : When we insert an element into linked list, we traverse through the linked list to find the appropriate place for the node. In the worst case, this process takes O(n) time complexity
We want a solution which takes O(logN) time complexity. So the only solution is to use Binary Heap. We already know it's a binary tree with additional property. With the use of BP, we can find the maximum/minimum number from a set of number. BP will not fail like array or linked list. BP will complete the task with the time complexity of O(logN)
Practical use :
Prim's Algorithm
Heap Sort
Priority Queue
BP types :
Min heap: the value of each node is less than or equal to the value of both its children
Max heap: The value of each node is more than or equal to the value of both its children.
Common operations
The common operations are - Creation, Peek top of BP, traversal, size of BP, insertion, extract min/max, deletion.
Implementation Options : Array implementation, Reference/pointer
These implementations are quite similar. We will use array based implementation because array ins the best fit for binary heap.
Based on this two formula, we insert values to the array/python list. The root node's index is 1. (left child = cell [2x]) So, 1 is multiplied with 2, so the index of left child will be two. Then, we are multiplying 2 with 1 and plus 1 which is 3, so we are inserting 20 there. [ x = index of parent ]
Creation of BP
Initialize fixed size list
set size of BP to 0
class Heap:
def __init__(self, size):
self.customList = (size+1) * [None]
self.heapSize = 0
self.maxSize = size + 1
newBinaryHeap = Heap(5)
The line (size+1) * [None]
is used to create a list (array) of a fixed size, filled with None
values initially. By creating an array of size size + 1
, you leave index 0 empty (None
) and use indices from 1 to size
.
Time complexity: O(1), Space complexity: O(N) [initializing fixed sized list]
Peek of BP
- Return list [1]
In our case, root of heap is 5. So, we are returning 5 (root is always located in the index of 1)
def peekOfHeap(rootNode):
if not rootNode:
return
return rootNode.customList[1]
newBinaryHeap = Heap(5)
Time complexity: O(1), Space complexity: O(1)
Size of binary heap
- return number of filled cells
Basically, it means how many elements are there in the heap
def sizeOfHeap(rootNode):
if not rootNode:
return
return rootNode.heapSize
newBinaryHeap = Heap(5)
print(sizeOfHeap(newBinaryHeap))
Time complexity: O(1), Space complexity: O(1)
Traversal of BP
level order traversal
(I have explained everything of level order traversal in Binary Tree blog, hence I'm not going to discuss about it here)
def levelOrderTraversal(rootNode): if not rootNode: return None else: for i in range(1, rootNode.heapSize+1): print(rootNode.customList[i])
Time complexity: O(n), Space complexity: O(1)
Insert a node in Binary Heap
According to the properties of binary heap, it should be a complete tree. But in the last level, we can have as many nodes left as possible. There is a unused cell, and in the list there's an empty index is present. We can insert a number over there.
After we insert 1 over here, it will look like this -
But it violates the rule of BP that is the root node must be smaller than every node in the heap. Now, we need to make adjustment. We will keep swapping the child node with the parent node until the property becomes maintained.
For creating method for insertion, we will create heapify
tree method (this method is needed for both insertion and deletion)
def heapifyTreeInsert(rootNode, index, heapType):
parentIndex = int(index/2)
if index <= 1:
return
if heapify == "Min":
if rootNode.customList[index] < rootNode.customList[parentIndex]:
temp = rootNode.customList[index]
rootNode.customList[index] = rootNode.customList[parentIndex]
rootNode.customList[parentIndex] = temp
heapifyTreeInsert(rootNode, parentNode, heapType) #O(logN)
elif heapify == "Max":
if rootNode.customList[index] > rootNode.customList[parentIndex]:
temp = rootNode.customList[index]
rootNode.customList[index] = rootNode.customList[parentIndex]
rootNode.customList[parentIndex] = temp
heapifyTreeInsert(rootNode, parentNode, heapType) #O(logN)
# Time complexity: O(logN), Space complexity: O(logN)
def insertNode(rootNode, nodeValue, heapType):
if rootNode.heapSize + 1 == rootNode.maxSize :
return "The binary heap is full"
rootNode.customList[rootNode.heapSize + 1] = nodeValue
rootNode.heapSize += 1
heapifyTreeInsert[rootNode, rootNode.heapSize, heapType) #O(logN)
return "The value has been successfully inserted"
newHeap = Heap(5)
insertNode(newHeap, 4, "Max")
insertNode(newHeap, 5, "Max")
insertNode(newHeap, 3, "Max")
insertNode(newHeap, 1, "Max")
levelOrderTraversal(newHeap)
# Time complexity: O(logN), Space complexity: O(logN)
Extract a node from Binary Heap
The only one element that can be extracted from heap is always the root. Other node extraction are not allowed because it hampers the property of heap. In that case, we need to do some adjustments.
Steps:
Find the last element of the heap (which is located at the last level)
Replace the last element with the root node
Adjustment : Promote one of the children of the root node, and replace their place (select the minimum child) Keep repeating the process.
Note: In case if Min BP, the left node is always bigger than the rootNode.
def heapifyTreeExtract(rootNode, index, heapType):
leftIndex = index * 2
rightIndex = index * 2 + 1
swapIndex = 0
if rootNode.heapSize < leftIndex:
return
elif rootNode.heapSize == leftIndex:
if heapType == "Min" :
if rootNode.customList[index] > rootNode.customList[leftIndex]:
temp = rootNode.customList[index]
rootNode.customList[index] = rootNode.customList[leftIndex]
rootNode.customList[leftIndex] = temp
return
else:
if rootNode.customList[index] < rootNode.customList[leftIndex]:
temp = rootNode.customList[index]
rootNode.customList[index] = rootNode.customList[leftIndex]
rootNode.customList[leftIndex] = temp
return
else:
if heapType == "Min" :
if rootNode.customList[index] < rootNode.customList[rightIndex]:
swapChild = leftIndex
else:
swapChild = rightIndex
if rootNode.customList[index] > rootNode.customList[swapChild]:
temp = rootNode.customList[index]
rootNode.customList[index] = rootNode.customList[swapChild]
rootNode.customList[swapChild] = temp
else:
if rootNode.customList[index] > rootNode.customList[rightIndex]:
swapChild = leftIndex
else:
swapChild = rightIndex
if rootNode.customList[index] < rootNode.customList[swapChild]:
temp = rootNode.customList[index]
rootNode.customList[index] = rootNode.customList[swapChild]
rootNode.customList[swapChild] = temp
heapifyTreeExtract(rootNode, index, heapType)
def extractNode(rootNode, heapType):
if rootNode.heapSize == 0: #O(1)
return
else:
extractedNode = rootNode.customList[1] #O(1)
rootNode.customList[1] = rootNode.customList[rootNode.heapSize] #O(1)
rootNode.customList[rootNode.heapSize] = None #O(1)
rootNode.heapSize -= 1 #O(1)
heapifyTreeExtract(rootNode, 1, heapType) #O(logN)
return extractedNode #O(1)
newHeap = Heap(5)
insertNode(newHeap, 4, "Max")
insertNode(newHeap, 5, "Max")
insertNode(newHeap, 3, "Max")
insertNode(newHeap, 1, "Max")
print(extractNode(newHeap, "Max"))
levelOrderTraversal(newHeap)
In case of minimum heap, we have to find the smallest child, then swap it with the parent
In case of maximum heap, we have to find the greatest child, then swap it with the parent
Time complexity: O(logN), Space complexity: O(logN)
Inside heapify
method, we are calling method recursively and that's why it takes O(logN) space complexity
Delete entire Binary Heap
def deleteEntireBP(rootNode):
rootNode.customList = None
newHeap = Heap(5)
insertNode(newHeap, 4, "Max")
insertNode(newHeap, 5, "Max")
insertNode(newHeap, 3, "Max")
insertNode(newHeap, 1, "Max")
deleteEntireBP(newHeap)
levelOrderTraversal(newHeap)
Time complexity: O(1), Space complexity: O(1)
Time and Space complexity of Binary Heap
| |
Time complexity | Space complexity |
| --- | --- | --- |
| Create BP | O(1) | O(N) |
| Peek of Heap | O(1) | O(1) |
| Size of heap | O(1) | O(1) |
| Traversal of heap | O(N) | O(1) |
| Insert a node to BP | O(logN) | O(logN) |
| Extract a node to BP | O(logN) | O(logN) |
| Delete entire BP | O(1) | O(1) |