Mastering Python's Heapq Module: Priority Queue Implementation
The heapq module in Python provides an implementation of the heap queue algorithm, also known as the priority queue algorithm. This module is invaluable for efficiently managing data where elements need to be retrieved in a specific order, typically from smallest to largest. This challenge will test your understanding of heapq's core functionalities by asking you to implement common priority queue operations.
Problem Description
Your task is to simulate the behavior of a priority queue using Python's heapq module. You will be given a sequence of operations to perform on an initially empty heap. These operations will include adding elements, retrieving and removing the smallest element, and examining the smallest element without removing it.
You need to implement a class, let's call it PriorityQueue, that encapsulates these operations. The class should maintain an internal heap structure using heapq.
Key Requirements:
- Initialization: The
PriorityQueueshould be initialized as an empty data structure. push(item): Adds anitemto the priority queue.pop(): Removes and returns the smallest item from the priority queue. If the queue is empty, it should raise anIndexError.peek(): Returns the smallest item from the priority queue without removing it. If the queue is empty, it should raise anIndexError.is_empty(): ReturnsTrueif the priority queue is empty, andFalseotherwise.
Expected Behavior:
The priority queue should always maintain the heap property, meaning the smallest element is always at the root (index 0). Operations should reflect the standard behavior of a min-heap priority queue.
Edge Cases:
- Attempting to
pop()orpeek()from an empty queue. - Pushing duplicate elements.
- Pushing elements of different but comparable types (e.g., integers and floats).
Examples
Example 1:
Input Operations:
pq = PriorityQueue()
pq.push(3)
pq.push(1)
pq.push(4)
print(pq.peek())
print(pq.pop())
pq.push(2)
print(pq.pop())
print(pq.is_empty())
print(pq.pop())
print(pq.is_empty())
Output:
1
1
2
False
3
True
Explanation:
pq = PriorityQueue(): An empty priority queue is created.pq.push(3): Heap:[3]pq.push(1): Heap:[1, 3]pq.push(4): Heap:[1, 3, 4]print(pq.peek()): Returns1(smallest element). Heap remains[1, 3, 4].print(pq.pop()): Returns1. Heap becomes[3, 4]after rearrangement.pq.push(2): Heap:[2, 4, 3](after push and heapify).print(pq.pop()): Returns2. Heap becomes[3, 4].print(pq.is_empty()): ReturnsFalse.print(pq.pop()): Returns3. Heap becomes[4].print(pq.is_empty()): ReturnsFalse.print(pq.pop()): Returns4. Heap becomes[].print(pq.is_empty()): ReturnsTrue.
Example 2:
Input Operations:
pq = PriorityQueue()
try:
pq.pop()
except IndexError as e:
print(e)
try:
pq.peek()
except IndexError as e:
print(e)
pq.push(5)
pq.push(5)
pq.push(1)
print(pq.pop())
print(pq.pop())
Output:
Queue is empty
Queue is empty
1
5
Explanation:
- The first two
pop()andpeek()calls on an empty queue correctly raiseIndexErrorwith the message "Queue is empty". - Elements
5,5, and1are pushed. The heap might look something like[1, 5, 5]. - The first
pop()returns1. - The second
pop()returns5.
Constraints
- The number of
pushoperations will not exceed 1000. - The number of
popandpeekoperations will not exceed 1000. - Items pushed into the queue will be integers or floats.
- The time complexity for
pushandpopoperations should be O(log n), where n is the number of elements in the queue. The time complexity forpeekandis_emptyshould be O(1).
Notes
- Remember that
heapqfunctions operate on standard Python lists. - When implementing
popandpeek, ensure you handle the empty queue scenario gracefully by raising anIndexError. - Consider how
heapqinternally manages the heap structure to ensure correctness when elements are added or removed.