Updated On : Feb-10,2021 Tags heap, priority-queue
heapq - Heap Queue/Priority Queue Implementation in Python

heapq - Heap Queue/Priority Queue Implementation in Python

Heap queue or commonly referred to as priority queue is an algorithm that maintains elements sorted based on their priority using a data structure called the heap. Heap is a binary tree data structure where each node’s value is less than or equal to its children. This makes sure that the minimum element is present at the root of the tree. Each time we pop a value from the heap, it'll retrieve the root node value (which is smallest) and then rearrange the tree to make it heap again satisfying its property (node value smaller or equal to its children). Python provides us with the module named heapq which provides an implementation of heap queue hence we don't need to write one of our own.

As a part of this tutorial, we'll be demonstrating the usage of various methods of heapq module. We'll explain them with simple and easy to understand examples.

Example 1

As a part of our first example, we'll explain how we can create a heap from a list using heapify() method.

  • heapq.heapify() - It takes as input a list and transforms a list into a heap in linear time (O(n)). It performs this operation in place without returning anything.

We are first creating a list of 20 random numbers in the range 1-20. We are then calling heapify() method on them to translate the list to a heap. We are printing the list before it was heap and after it was converted to the heap.

In [45]:
import heapq
import random
random.seed(123)

heap = [random.randint(1, 20) for i in range(20)]

print("Initial List : {}".format(heap))

heapq.heapify(heap)

print("Heap         : {}".format(heap))
Initial List : [2, 9, 3, 14, 9, 4, 2, 13, 18, 18, 11, 11, 2, 6, 5, 11, 18, 11, 8, 6]
Heap         : [2, 6, 2, 8, 9, 2, 3, 11, 11, 9, 11, 11, 4, 6, 5, 13, 18, 14, 18, 18]

Below we have shown the heap that was created above. We have represented it as a binary tree to better understand it. We can confirm that all node has value less than or equal to its children.

Heap

                                  2
                     6                        2
             8              9           2          3
        11      11        9   11     11    4    6     5
      13  18  14  18   18

Example 2

Our second example is another example explaining the usage of the heapify() method. We are this time passing a tuple to the method. It takes the first element of the tuple to create a heap. This can be helpful in situations where we are some important number like priority present as the first element of the item.

In [46]:
import heapq
import random
random.seed(123)

heap = [(random.randint(1, 10), str(i+1)) for i in range(10)]

print("Initial List : {}".format(heap))

heapq.heapify(heap)

print("Heap         : {}".format(heap))
Initial List : [(1, '1'), (5, '2'), (2, '3'), (7, '4'), (5, '5'), (2, '6'), (1, '7'), (7, '8'), (9, '9'), (9, '10')]
Heap         : [(1, '1'), (5, '2'), (1, '7'), (7, '4'), (5, '5'), (2, '6'), (2, '3'), (7, '8'), (9, '9'), (9, '10')]

Example 3

Our third example also explains the usage of the heapify() method but it uses string elements.

In [43]:
import heapq
import random
random.seed(123)

heap = [(str(random.randint(1, 10))) for i in range(10)]

print("Initial List : {}".format(heap))

heapq.heapify(heap)

print("Heap         : {}".format(heap))
Initial List : ['1', '5', '2', '7', '5', '2', '1', '7', '9', '9']
Heap         : ['1', '5', '1', '7', '5', '2', '2', '7', '9', '9']

Example 4

Our fourth example again builds heap using heapify() method but uses alphabet characters as heap items.

In [1]:
import heapq

heap = ["C", "D", "E", "R", "A", "B", "Z", "S", "T", "F", "G"]

print("Initial List : {}".format(heap))

heapq.heapify(heap)

print("Heap         : {}".format(heap))
Initial List : ['C', 'D', 'E', 'R', 'A', 'B', 'Z', 'S', 'T', 'F', 'G']
Heap         : ['A', 'C', 'B', 'R', 'D', 'E', 'Z', 'S', 'T', 'F', 'G']

Heap

               A
          C         B
       R     D     E   Z
     S   T  F  G

Example 5

Our fifth example explains how we can push new items on the heap using heappush() method.

  • heappush() - It accepts the heap and the item we want to push as input and pushes that item on the heap. It’s an in-place operation.

We are looping 10 times and adding a random float to the heap each time using heappush(). We are the printing heap at last as well.

In [38]:
import heapq
import random
random.seed(123)

heap = []
for i in range(10):
    val = random.randint(1,10) + random.random()
    heapq.heappush(heap, val)

print("Heap : ", heap)
Heap :  [1.15966239672197, 1.2676771662174662, 1.3791243324890883, 7.773587980029087, 1.3154589737358633, 6.560962008936676, 4.163776844752361, 9.33219769850968, 10.377594776972211, 7.2665538125313835]

Example 6

As a part of our sixth example, we are demonstrating how we can pop items from the heap using heappop() method.

  • heappop() - It accepts heap as an input and returns root node element. It also recreates the heap after the root node is popped to make sure all nodes satisfy the heap property.

We are building on code from the last example in this example. We are first adding 10 numbers on the heap and then retrieving numbers one by one from the heap using heappop(). This returns us sorted elements.

In [41]:
import heapq
import random
random.seed(123)

heap = []
for i in range(10):
    val = random.randint(1,10) + random.random()
    heapq.heappush(heap, val)

print("Heap : {}\n".format(heap))

for i in range(10):
    popped_element = heapq.heappop(heap)
    print(popped_element)
Heap : [1.15966239672197, 1.2676771662174662, 1.3791243324890883, 7.773587980029087, 1.3154589737358633, 6.560962008936676, 4.163776844752361, 9.33219769850968, 10.377594776972211, 7.2665538125313835]

1.15966239672197
1.2676771662174662
1.3154589737358633
1.3791243324890883
4.163776844752361
6.560962008936676
7.2665538125313835
7.773587980029087
9.33219769850968
10.377594776972211

Example 7

As a part of our seventh example, we'll demonstrate how we can perform heap push and pop operation in a single method call using heappushpop() method.

  • heappushpop() - It accepts the heap and element to be pushed as input. It then pushes an element on the heap and then pops an element from the heap and returns it.

We are first creating a heap of 10 numbers. We are then looping 5 times, generating a random number between 1-20, pushing it on the heap, and popping an element from the heap. We are printing all these operations for tracking purposes as well.

In [57]:
import heapq
import random
random.seed(42)

heap = []
for i in range(10):
    val = random.randint(1,10)
    heapq.heappush(heap, val)

print("Heap : {}\n".format(heap))

for i in range(5):
    print("Heap Initially            : {}".format(heap))
    val = random.randint(1, 20)
    print("New value to be added     : {}".format(val))
    popped_elem = heapq.heappushpop(heap, val)
    print("Popped Smallest Element   : {}".format(popped_elem))
    print("Heap after Push/Pop Ops   : {}".format(heap))
Heap : [1, 2, 2, 2, 4, 5, 3, 9, 4, 10]

Heap Initially            : [1, 2, 2, 2, 4, 5, 3, 9, 4, 10]
New value to be added     : 14
Popped Smallest Element   : 1
Heap after Push/Pop Ops   : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
New value to be added     : 2
Popped Smallest Element   : 2
Heap after Push/Pop Ops   : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
New value to be added     : 1
Popped Smallest Element   : 1
Heap after Push/Pop Ops   : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
New value to be added     : 3
Popped Smallest Element   : 2
Heap after Push/Pop Ops   : [2, 2, 3, 3, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 3, 4, 5, 14, 9, 4, 10]
New value to be added     : 7
Popped Smallest Element   : 2
Heap after Push/Pop Ops   : [2, 3, 3, 4, 4, 5, 14, 9, 7, 10]

Example 8

As a part of our eighth example, we are demonstrating how we can pop an element from the heap and push the element on the heap in a single function call to heapreplace().

  • heapreplace() - It takes as input a heap and an element that we want to push onto it. It then first pops an element from the heap and then pushes an element on the heap.

The code for this example is exactly the same as our previous example with the only change that we are using heapreplace() in this example instead of heappushpop().

Please make a note that both this method can have different results even though both perform the same operations which we can notice if we compare the output of this example with the previous.

In [58]:
import heapq
import random
random.seed(42)

heap = []
for i in range(10):
    val = random.randint(1,10)
    heapq.heappush(heap, val)

print("Heap : {}\n".format(heap))

for i in range(5):
    print("Heap Initially            : {}".format(heap))
    val = random.randint(1, 20)
    print("New value to be added     : {}".format(val))
    popped_elem = heapq.heapreplace(heap, val)
    print("Popped Smallest Element   : {}".format(popped_elem))
    print("Heap after Push/Pop Ops   : {}".format(heap))
Heap : [1, 2, 2, 2, 4, 5, 3, 9, 4, 10]

Heap Initially            : [1, 2, 2, 2, 4, 5, 3, 9, 4, 10]
New value to be added     : 14
Popped Smallest Element   : 1
Heap after Push/Pop Ops   : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
New value to be added     : 2
Popped Smallest Element   : 2
Heap after Push/Pop Ops   : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 2, 4, 5, 14, 9, 4, 10]
New value to be added     : 1
Popped Smallest Element   : 2
Heap after Push/Pop Ops   : [1, 2, 3, 2, 4, 5, 14, 9, 4, 10]
Heap Initially            : [1, 2, 3, 2, 4, 5, 14, 9, 4, 10]
New value to be added     : 3
Popped Smallest Element   : 1
Heap after Push/Pop Ops   : [2, 2, 3, 3, 4, 5, 14, 9, 4, 10]
Heap Initially            : [2, 2, 3, 3, 4, 5, 14, 9, 4, 10]
New value to be added     : 7
Popped Smallest Element   : 2
Heap after Push/Pop Ops   : [2, 3, 3, 4, 4, 5, 14, 9, 7, 10]

Example 9

As a part of our ninth example, we'll explain how we can create a sorted list from a list of sorted lists using merge() method.

  • merge(*iterables, key=None, reverse=False) - It accepts a number of sorted lists and then sorts them all into one list. It has parameters named key and reverse which works exactly like Python's in-built sorted() method. The key accepts the lambda function which takes an individual element of the list and returns a value based on which sorting decision will be made. The reverse is a boolean value which if set to True will reverse sort the list.

Below we are creating five lists, each of length 5 which has random numbers in the range 1-50. We have kept all the lists sorted. We are then combining all lists using merge() method. We are then performing the same process and sorting all lists in reverse order.

Please make a note that in order to sort the list in reverse order, we need the original list also sorted in reverse order. Also, the method won't work on unsorted lists.

In [73]:
import heapq
import random
random.seed(123)

list1 = sorted([random.randint(1,50) for i in range(5)])
list2 = sorted([random.randint(1,50) for i in range(5)])
list3 = sorted([random.randint(1,50) for i in range(5)])
list4 = sorted([random.randint(1,50) for i in range(5)])
list5 = sorted([random.randint(1,50) for i in range(5)])

sorted_out = heapq.merge(list1, list2, list3, list4, list5)

print("Output : {}".format(sorted_out))
print("Output : {}".format(list(sorted_out)))

list1 = sorted([random.randint(1,50) for i in range(5)], reverse=True)
list2 = sorted([random.randint(1,50) for i in range(5)], reverse=True)
list3 = sorted([random.randint(1,50) for i in range(5)], reverse=True)
list4 = sorted([random.randint(1,50) for i in range(5)], reverse=True)
list5 = sorted([random.randint(1,50) for i in range(5)], reverse=True)

sorted_out2 = heapq.merge(list1, list2, list3, list4, list5, reverse=True)

print("Output : {}".format(list(sorted_out2)))
Output : <generator object merge at 0x7fee623ec660>
Output : [1, 3, 4, 4, 6, 7, 9, 11, 11, 16, 18, 18, 22, 22, 22, 22, 25, 27, 28, 35, 36, 36, 45, 50, 50]
Output : [50, 47, 43, 39, 37, 31, 31, 29, 28, 25, 22, 21, 20, 19, 17, 10, 9, 7, 6, 6, 5, 3, 3, 2, 1]

Example 10

Our tenth example further explains the usage of merge() method on how to use its key parameter.

Our code this time creates lists where an individual element is a tuple of two-element. The first element in the tuple is a string and a second element is a random number in the range 1-50. We are sorting lists based on the second element of the tuple. We are then also reverse sorting lists and combining them.

In [3]:
import heapq
import random
random.seed(123)

list1 = sorted([(str(i), random.randint(1,50)) for i in range(5)], key=lambda x : x[1])
list2 = sorted([(str(i), random.randint(1,50)) for i in range(5)], key=lambda x : x[1])
list3 = sorted([(str(i), random.randint(1,50)) for i in range(5)], key=lambda x : x[1])
list4 = sorted([(str(i), random.randint(1,50)) for i in range(5)], key=lambda x : x[1])
list5 = sorted([(str(i), random.randint(1,50)) for i in range(5)], key=lambda x : x[1])

sorted_out = heapq.merge(list1, list2, list3, list4, list5, key=lambda x: x[1])

print("Output : {}".format(list(sorted_out)))

list1 = sorted([(str(i), random.randint(1,50)) for i in range(5)], reverse=True, key=lambda x : x[1])
list2 = sorted([(str(i), random.randint(1,50)) for i in range(5)], reverse=True, key=lambda x : x[1])
list3 = sorted([(str(i), random.randint(1,50)) for i in range(5)], reverse=True, key=lambda x : x[1])
list4 = sorted([(str(i), random.randint(1,50)) for i in range(5)], reverse=True, key=lambda x : x[1])
list5 = sorted([(str(i), random.randint(1,50)) for i in range(5)], reverse=True, key=lambda x : x[1])

sorted_out2 = heapq.merge(list1, list2, list3, list4, list5, key=lambda x: x[1], reverse=True)

print("\nOutput : {}".format(list(sorted_out2)))
Output : [('2', 1), ('2', 3), ('0', 4), ('3', 4), ('2', 6), ('1', 7), ('0', 9), ('4', 11), ('1', 11), ('0', 16), ('1', 18), ('0', 18), ('1', 22), ('2', 22), ('1', 22), ('3', 22), ('3', 25), ('4', 27), ('3', 28), ('4', 35), ('0', 36), ('2', 36), ('4', 45), ('3', 50), ('4', 50)]

Output : [('2', 50), ('1', 47), ('1', 43), ('1', 39), ('2', 37), ('3', 31), ('0', 31), ('2', 29), ('1', 28), ('2', 25), ('4', 22), ('0', 21), ('3', 20), ('0', 19), ('4', 17), ('2', 10), ('3', 9), ('3', 7), ('0', 6), ('0', 6), ('3', 5), ('4', 3), ('1', 3), ('4', 2), ('4', 1)]

Example 11

As a part of our eleventh example, we are demonstrating how we can get n largest element from the list using nlargest() and n smallest element from the list using nsmallest() method.

  • nlargest(n, iterable, key=None) - It accepts a number and a list as input and returns that many largest elements from the list. If we have a list where an individual element is a tuple, list, or user-defined class instance then we can provide a lambda function specifying which attribute to use to get n largest elements.
  • nsmallest(n, iterable, key=None) - It works exactly like nlargest() but returns n smallest elements.

Below we have explained the usage of both methods with a simple example.

In [90]:
import heapq
import random
random.seed(123)

list1 = [random.randint(1, 20) for i in range (20)]

print("List : {}\n".format(list1))

print("2 Largest Elements : {}".format(heapq.nlargest(2, list1)))
print("5 Largest Elements : {}\n".format(heapq.nlargest(5, list1)))

print("2 Smallest Elements : {}".format(heapq.nsmallest(2, list1)))
print("5 Smallest Elements : {}".format(heapq.nsmallest(5, list1)))
List : [2, 9, 3, 14, 9, 4, 2, 13, 18, 18, 11, 11, 2, 6, 5, 11, 18, 11, 8, 6]

2 Largest Elements : [18, 18]
5 Largest Elements : [18, 18, 18, 14, 13]

2 Smallest Elements : [2, 2]
5 Smallest Elements : [2, 2, 2, 3, 4]

Example 12

Our twelfth example further explains the usage of nlargest() and nsmallest() methods on the list where an individual element is a tuple of two elements. It returns the largest and smallest elements based on the 2nd value in the tuple.

In [91]:
import heapq
import random
random.seed(123)

list1 = [(str(i), random.randint(1, 20)) for i in range (20)]

print("List : {}\n".format(list1))

print("2 Largest Elements : {}".format(heapq.nlargest(2, list1, key=lambda x : x[1])))
print("5 Largest Elements : {}\n".format(heapq.nlargest(5, list1, key=lambda x : x[1])))

print("2 Smallest Elements : {}".format(heapq.nsmallest(2, list1, key=lambda x : x[1])))
print("5 Smallest Elements : {}".format(heapq.nsmallest(5, list1, key=lambda x : x[1])))
List : [('0', 2), ('1', 9), ('2', 3), ('3', 14), ('4', 9), ('5', 4), ('6', 2), ('7', 13), ('8', 18), ('9', 18), ('10', 11), ('11', 11), ('12', 2), ('13', 6), ('14', 5), ('15', 11), ('16', 18), ('17', 11), ('18', 8), ('19', 6)]

2 Largest Elements : [('8', 18), ('9', 18)]
5 Largest Elements : [('8', 18), ('9', 18), ('16', 18), ('3', 14), ('7', 13)]

2 Smallest Elements : [('0', 2), ('6', 2)]
5 Smallest Elements : [('0', 2), ('6', 2), ('12', 2), ('2', 3), ('5', 4)]

Example 13

Our thirteenth and last example demonstrates how we can sort an array using heapq module methods. We are first creating a heap using heappush() method. We are then using heapop() method to get elements from the list in sorted order. This is how the heapsort algorithm works.

In [6]:
import heapq
import random
random.seed(123)

heap = []
for i in range(15):
    val = random.randint(1,15)
    heapq.heappush(heap, val)

print([heapq.heappop(heap) for i in range(15)])
[1, 1, 2, 2, 5, 5, 6, 7, 7, 9, 9, 13, 14, 14, 15]

This ends our small tutorial explaining the API provided by heapq module. Please feel free to let us know your views in the comments section.



Sunny Solanki  Sunny Solanki