Priority Queue Introduction and Concept in DSA Go - Time & Space Complexity
We want to understand how the time needed to use a priority queue changes as we add more items.
How does the work grow when we insert or remove elements?
Analyze the time complexity of the following code snippet.
package main
import (
"container/heap"
"fmt"
)
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } // max-heap
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
n := len(*h)
x := (*h)[n-1]
*h = (*h)[:n-1]
return x
}
func main() {
h := &IntHeap{3, 1, 4}
heap.Init(h)
heap.Push(h, 2)
fmt.Println(heap.Pop(h))
}
This code creates a max priority queue using a heap, inserts an element, and removes the highest priority element.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The heap operations (Push and Pop) reorganize the heap by moving elements up or down.
- How many times: Each operation moves through the height of the heap, which depends on the number of elements.
As the number of elements grows, the height of the heap grows slowly, so each insert or remove takes a bit more time.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 4 steps (height of heap) |
| 100 | About 7 steps |
| 1000 | About 10 steps |
Pattern observation: The steps grow slowly, roughly with the height of a tree, not the total number of elements.
Time Complexity: O(log n)
This means adding or removing an element takes time proportional to the height of the heap, which grows slowly as the queue grows.
[X] Wrong: "Adding or removing elements in a priority queue takes the same time no matter how many elements there are."
[OK] Correct: Actually, the time depends on the heap height, which grows with more elements, so operations take longer as the queue grows.
Understanding how priority queues work and their time costs helps you explain efficient ways to manage tasks by priority in real projects.
"What if we used a simple unsorted list instead of a heap for the priority queue? How would the time complexity change?"
Practice
priority queue compared to a regular queue?Solution
Step 1: Understand queue behavior
A regular queue removes elements in the order they were added (FIFO).Step 2: Understand priority queue behavior
A priority queue removes elements based on priority, not just insertion order.Final Answer:
Elements are dequeued based on their priority, not just order of insertion -> Option CQuick Check:
Priority queue = priority-based removal [OK]
- Confusing priority queue with FIFO queue
- Thinking priority queue only stores unique values
- Assuming insertion order always matters
Solution
Step 1: Recognize Go heap usage
Go's container/heap requires a type implementing heap.Interface.Step 2: Correct declaration
Creating a pointer to a struct implementing heap.Interface is correct.Final Answer:
var pq = &PriorityQueue{} // where PriorityQueue implements heap.Interface -> Option AQuick Check:
PriorityQueue struct + heap.Interface = correct declaration [OK]
- Declaring slice only without heap.Interface
- Using heap.Interface type directly without struct
- Not using pointer receiver for heap methods
package main
import (
"container/heap"
"fmt"
)
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] > h[j] } // max-heap
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
func main() {
h := &IntHeap{2, 1, 5}
heap.Init(h)
heap.Push(h, 3)
fmt.Println(heap.Pop(h))
}Solution
Step 1: Understand heap type and ordering
The Less function returns h[i] > h[j], so this is a max-heap (largest element on top).Step 2: Trace heap operations
Initial heap: [2,1,5]. After heap.Init, heap property is established. Push 3 adds 3. The max element is 5, so heap.Pop returns 5.Final Answer:
5 -> Option BQuick Check:
Max-heap pop returns largest = 5 [OK]
- Assuming min-heap by default
- Confusing Push and Pop order
- Not tracing heap.Init effect
package main
import (
"container/heap"
"fmt"
)
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
func main() {
h := IntHeap{3, 2, 1}
heap.Init(&h)
heap.Push(&h, 4)
fmt.Println(heap.Pop(&h))
}Solution
Step 1: Check heap.Pop usage
heap.Pop requires a pointer to the heap, but code calls heap.Pop(h) passing value.Step 2: Confirm other methods
Push, Less, Swap are correctly defined. The error is in calling heap.Pop without &.Final Answer:
heap.Pop called with value instead of pointer -> Option AQuick Check:
heap.Pop needs pointer argument [OK]
- Calling heap.Pop with value instead of pointer
- Confusing Less method for error
- Ignoring method signatures for Push/Pop
Less method implementations correctly creates a min-heap for this purpose?Solution
Step 1: Understand min-heap property
Min-heap means the smallest priority value is at the top, so Less should return true if i's priority is less than j's.Step 2: Analyze options
func (h TaskHeap) Less(i, j int) bool { return h[i].priority < h[j].priority } returns true if h[i].priority < h[j].priority, which is correct for min-heap. Others do not correctly implement min-heap ordering.Final Answer:
func (h TaskHeap) Less(i, j int) bool { return h[i].priority < h[j].priority } -> Option DQuick Check:
Min-heap Less returns true if i priority less than j [OK]
- Using > instead of < for min-heap
- Using equality in Less method
- Using >= which breaks heap property
Ready to prove it?
Pass the test and this pattern moves unknown
