Huffman Encoding in DSA Go - Time & Space Complexity
We want to understand how the time needed to build a Huffman tree changes as the number of characters grows.
How does the work increase when we have more characters to encode?
Analyze the time complexity of the following code snippet.
// Build Huffman tree from character frequencies
func buildHuffmanTree(freq map[rune]int) *Node {
pq := NewPriorityQueue()
for ch, f := range freq {
pq.Push(&Node{char: ch, freq: f})
}
for pq.Len() > 1 {
left := pq.Pop()
right := pq.Pop()
merged := &Node{freq: left.freq + right.freq, left: left, right: right}
pq.Push(merged)
}
return pq.Pop()
}
This code builds a Huffman tree by repeatedly combining the two smallest frequency nodes until one tree remains.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Removing two smallest nodes and inserting their merged node back into the priority queue.
- How many times: This happens about n-1 times, where n is the number of unique characters.
Each step removes two nodes and adds one, repeating until one node remains.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 log 10 operations |
| 100 | About 100 log 100 operations |
| 1000 | About 1000 log 1000 operations |
Pattern observation: The work grows a bit faster than the number of characters because of the sorting steps inside the priority queue.
Time Complexity: O(n log n)
This means the time grows a little faster than the number of characters because we keep sorting nodes as we build the tree.
[X] Wrong: "Building the Huffman tree takes only O(n) time because we just combine nodes."
[OK] Correct: Each combination requires removing and adding nodes in a priority queue, which involves sorting steps that take extra time, making it slower than just a simple loop.
Understanding how Huffman encoding scales helps you explain efficient data compression and priority queue use, a useful skill in many coding challenges.
"What if we used a simple list instead of a priority queue to pick the smallest nodes? How would the time complexity change?"
Practice
Solution
Step 1: Understand Huffman Encoding purpose
Huffman Encoding compresses data by using variable-length codes based on character frequency.Step 2: Identify code assignment rule
More frequent characters get shorter codes to reduce overall data size.Final Answer:
To assign shorter codes to more frequent characters -> Option DQuick Check:
Huffman Encoding = shorter codes for frequent chars [OK]
- Thinking all codes have same length
- Confusing compression with encryption
- Assuming alphabetical order affects codes
Solution
Step 1: Identify correct struct for Huffman node
HuffmanNode should have a character (rune) and frequency (int).Step 2: Check priority queue declaration
PriorityQueue should be a slice of pointers to HuffmanNode for heap operations.Final Answer:
type HuffmanNode struct {char rune; freq int}; type PriorityQueue []*HuffmanNode -> Option CQuick Check:
Priority queue = slice of HuffmanNode pointers [OK]
- Using map instead of slice for priority queue
- Using wrong data types for char or freq
- Declaring slice of values instead of pointers
Solution
Step 1: Build Huffman tree from frequencies
Combine lowest frequencies: 5('a') + 9('b') = 14, then combine 12('c') + 13('d') = 25, then combine 14 + 25 = 39.Step 2: Assign codes from tree
'a' is in the left subtree of the first combined node, so it gets code '10' (left-right).Final Answer:
10 -> Option AQuick Check:
'a' = code '10' [OK]
- Assigning longer codes to lowest freq chars
- Mixing up left and right branches
- Ignoring tree structure when assigning codes
func buildTree(freq map[rune]int) *Node {
pq := &PriorityQueue{}
heap.Init(pq)
for c, f := range freq {
heap.Push(pq, &Node{char: c, freq: f})
}
for pq.Len() > 1 {
left := heap.Pop(pq).(*Node)
right := heap.Pop(pq).(*Node)
merged := &Node{freq: left.freq + right.freq, left: left, right: right}
heap.Push(pq, merged)
}
return heap.Pop(pq).(*Node)
}Solution
Step 1: Check PriorityQueue usage with heap
Go's heap requires the PriorityQueue type to implement heap.Interface methods Len, Less, Swap, Push, and Pop.Step 2: Identify missing implementation
If PriorityQueue does not implement these methods, heap.Init and heap.Push will cause runtime errors.Final Answer:
PriorityQueue must implement heap.Interface methods -> Option AQuick Check:
heap.Interface implementation required [OK]
- Using wrong key type in freq map
- Calling heap.Init too early or late
- Trying to push non-pointer values
Solution
Step 1: Count character frequencies
For "aaabbc", frequencies are: 'a':3, 'b':2, 'c':1.Step 2: Build min-heap and merge nodes
Create nodes for each char, push into min-heap, merge two smallest nodes repeatedly until one tree remains.Step 3: Assign codes from the tree
Traverse tree assigning '0' for left and '1' for right edges to generate prefix codes.Final Answer:
Count frequencies, build min-heap, merge nodes until one tree, assign codes from tree -> Option BQuick Check:
Huffman = freq count + min-heap + merge + code assign [OK]
- Skipping tree building step
- Assigning fixed-length codes ignoring frequency
- Using random codes without structure
Ready to prove it?
Pass the test and this pattern moves unknown
