Suppose we have a text file. We want to represent the same information using fewer bits.
Fixed-length encoding: All characters use the same number of bits.
Variable-length encoding: Frequent characters get shorter codes, infrequent characters get longer codes.
E (most common) = ., Q = --.-A prefix code is a variable-length code where no codeword is a prefix of any other codeword.
This ensures unambiguous decoding — we can parse a bitstream uniquely left-to-right.
| Character | Frequency | Fixed Code | Variable Code |
|---|---|---|---|
| A | 60% | 00 | 0 |
| B | 25% | 01 | 10 |
| C | 10% | 10 | 110 |
| D | 5% | 11 | 111 |
Variable code uses 0.60 \times 1 + 0.25 \times 2 + 0.10 \times 3 + 0.05 \times 3 = 1.55 bits/char on average vs 2 bits/char fixed.
Every prefix code corresponds to a binary tree:
graph TD
Root["1.00"] -->|"0"| A["0.60<br/>A: 0"]
Root -->|"1"| N1["0.40"]
N1 -->|"0"| B["0.25<br/>B: 10"]
N1 -->|"1"| N2["0.15"]
N2 -->|"0"| C["0.10<br/>C: 110"]
N2 -->|"1"| D["0.05<br/>D: 111"]Optimal prefix code: the tree that minimizes \sum f(c) \cdot \text{depth}(c).
Input: A set of characters C, each with frequency f(c).
Output: A prefix code (binary tree) that minimizes the total cost:
B(T) = \sum_{c \in C} f(c) \cdot d_T(c)
where d_T(c) is the depth of character c in the tree T.
This is exactly the optimal prefix code or minimum-cost binary tree problem.
In an optimal prefix-code tree:
Observation: The two characters with the smallest frequencies should be at the bottom of the tree, as siblings. This is the greedy choice.
Huffman(C):
// C is a set of n characters with frequencies f(c)
n = |C|
Q = C // min-priority queue keyed by f(c)
for i = 1 to n - 1:
x = Extract-Min(Q) // smallest frequency
y = Extract-Min(Q) // second smallest
z = new node
f(z) = f(x) + f(y)
z.left = x
z.right = y
Insert(Q, z)
return Extract-Min(Q) // root of Huffman treeCharacters: a: 45, b: 13, c: 12, d: 16, e: 9, f: 5
Merge e (9) and f (5) → internal node with frequency 14.
Merge c (12) and b (13) → node (25).
Merge node (14) and d (16) → node (30).
Merge node (25) and node (30) → node (55).
Merge a (45) and node (55) → root (100).
graph TD
Root["100"] -->|"0"| a["a: 45"]
Root -->|"1"| N55["55"]
N55 -->|"0"| N25["25"]
N55 -->|"1"| N30["30"]
N25 -->|"0"| c["c: 12"]
N25 -->|"1"| b["b: 13"]
N30 -->|"0"| N14["14"]
N30 -->|"1"| d["d: 16"]
N14 -->|"0"| e["e: 9"]
N14 -->|"1"| f["f: 5"]| Char | Code | Bits |
|---|---|---|
| a | 0 | 1 |
| c | 100 | 3 |
| b | 101 | 3 |
| e | 1100 | 4 |
| f | 1101 | 4 |
| d | 111 | 3 |
Total: 45\!\times\!1 + 13\!\times\!3 + 12\!\times\!3 + 16\!\times\!3 + 9\!\times\!4 + 5\!\times\!4 = 224 bits.
Fixed-length (3 bits × 100 chars) = 300 bits. 25% savings.
Extract-Min + one Insert: O(\log n)Overall: \Theta(n \log n) time, O(n) space.
Note
Lemma: Let x and y be the two characters with the smallest frequencies. There exists an optimal prefix code where x and y are siblings at the maximum depth.
Proof. Take any optimal tree T. If x and y are not siblings at max depth, swap each into that position. Since x and y have the smallest frequencies, the cost does not increase. Repeating gives an optimal tree where x and y are siblings at max depth. \square
Note
Lemma: Let x and y be siblings in an optimal tree T for C. Then T' (replace x,y with z, f(z)=f(x)+f(y)) is optimal for C' = (C \setminus \{x,y\}) \cup \{z\}.
Proof. For any tree, B(T) = B(T') + f(x) + f(y), since the depth of x and y is one more than z.
Thus minimizing B(T) ⟺ minimizing B(T'). If T' were not optimal, we could improve T — contradiction. \square
Huffman’s merge preserves optimality at every step (induction on n).
CS F364: Design & Analysis of AlgorithmsTulasimohan Molli