SEN2212 · Week 12 · Analyzing and implementing greedy algorithms.

Huffman coding: building the tree, codes, total bits

Building a Huffman tree by merging the two smallest frequencies, reading codes from the tree, computing total bits and the average, and decoding. A tracing question that appears in the exam almost every term.

Huffman codingprefix code (prefix-free)variable-length codefixed-length codefrequencypriority queuemergefull binary treeencodingdecodingcompression ratio

Huffman coding compresses a text by giving each character a bit string of variable length: a frequent character gets a short code, a rare one gets a long code. The algorithm is greedy: at each step it merges the two smallest frequencies. Almost always the same question comes in the exam: “Build the Huffman tree with these frequencies, write the codes, compute the total bits.” Sometimes “decode this bit string” or “what is the saving compared with a fixed-length code?” is added after it.

Golden rules:

  1. At each step, merge the two smallest nodes in the queue. The weight of the new node is the sum of the two.
  2. The new node goes back into the queue and competes like a leaf in the following steps.
  3. Left 0, right 1. Which one goes to the left depends on the rule in the question; write it down and follow it.
  4. Total bits = Σ (frequency × code length).
  5. The tie rule can change the codes, never the total bits.

Fixed length and variable length

Think of a 50-character text with 6 different characters. In a fixed-length code each character needs ⌈log⁡26⌉=3\lceil \log_2 6 \rceil = 3 bits: 50 × 3 = 150 bits. But the characters are not all equally frequent:

CharacterEBDAFC
Frequency247101215

If we give C a short code and E a long code, the total goes down. The only condition: decodability. For this the code must be a prefix code. No code can be the beginning of another code. If you put the codes on the leaves of a binary tree, this is satisfied automatically: the path to one leaf cannot be the beginning of the path to another leaf, because there is no other node below a leaf.

The algorithm

1. A leaf for each character, all into a min-PQ
2. While there is more than one node in the PQ:
     x = deleteMin   (left child)
     y = deleteMin   (right child)
     z = new node: weight x+y, children x, y
     add z to the PQ
3. The one remaining node is the root

For n characters exactly n−1 merges are made. With a heap each operation is O(log⁡n)O(\log n): the total is O(nlog⁡n)O(n \log n).

Worked example

Frequencies E=2, B=4, D=7, A=10, F=12, C=15. In each row write the sorted state of the queue; this is the safest way not to make mistakes in the exam.

queue (sorted)               merge
E2 B4 D7 A10 F12 C15         E2 + B4   = 6
6 D7 A10 F12 C15             6 + D7    = 13
A10 F12 13 C15               A10 + F12 = 22
13 C15 22                    13 + C15  = 28
22 28                        22 + 28   = 50
50                           root
  merge 1: E(2) + B(4) = 6
  merge 2: (EB)(6) + D(7) = 13
  merge 3: A(10) + F(12) = 22
  merge 4: ((EB)D)(13) + C(15) = 28
  merge 5: (AF)(22) + (((EB)D)C)(28) = 50

Note step 3: 13 was just created, but A10 and F12 are smaller than it, so they were merged first. A new node does not automatically enter the next merge; it does so only if its weight is one of the two smallest.

The tree (left edge 0, right edge 1):

              (50)
            0/    \1
          (22)    (28)
         0/ \1   0/  \1
        A10 F12 (13)  C15
               0/  \1
              (6)   D7
             0/ \1
            E2   B4

Read the codes from the path from the root to the leaf:

CharacterFrequencyCodeLengthFrequency × length
A1000220
F1201224
C1511230
D7101321
E2100048
B41001416
50119
  codes {A=00, B=1001, C=11, D=101, E=1000, F=01} total bits 119 of 50 symbols, avg 2.38

The average code length is 119/50=2.38119 / 50 = 2.38 bits. The fixed code took 150 bits; the saving is 31 bits, about 20.7%.

TIP · check the total bits in two ways. Add up the weights of the internal nodes: 6 + 13 + 22 + 28 + 50 = 119. The frequency of each character is counted once at each internal node on the path from the root to its leaf, that is, exactly as many times as its code length. If the two ways do not give the same number, there is a mistake in the tree.

Encoding and decoding

Encoding: write the code of each character one after another. FACE → 01 00 11 1000 = 0100111000 (10 bits).

Decoding: read the bits from the left, go down from the root in the tree (0 left, 1 right). When you reach a leaf, write that character and return to the root.

bit string: 1001001110100
1 0 0 1   → leaf B   return to the root
0 0       → leaf A
1 1       → leaf C
1 0 1     → leaf D
0 0       → leaf A
result: BACDA
  decode 1001001110100 -> BACDA
  encode FACE -> 0100111000 (10 bits)

Tie cases

If two nodes have the same weight, which one comes out first is not defined. In the exam the rule is given; if it is not, write your own rule and apply it consistently. A common rule: “first in, first out”, that is, leaves come before newly created internal nodes.

Example: a=1, b=1, c=2, d=3, e=5.

a1 b1 c2 d3 e5      a1 + b1   = 2
c2 2 d3 e5          c2 + 2    = 4 (c first)
d3 4 e5             d3 + 4    = 7
e5 7                e5 + 7    = 12
      (12)
     0/  \1
     e   (7)
        0/ \1
        d  (4)
          0/ \1
          c  (2)
            0/ \1
            a   b
  codes {a=1110, b=1111, c=110, d=10, e=0} total bits 25 of 12 symbols, avg 2.0833333333333335

When the frequencies grow like Fibonacci, the tree leans to one side and the rarest character gets a code of n−1 bits. Conversely, if all frequencies are equal (w=x=y=z=4), Huffman returns to a fixed-length code: 00, 01, 10, 11.

PITFALL · what if c and (ab) swap places? The codes change (like c=111, a=1100) but the total is 25 bits in both trees. In the exam the codes may come out different depending on the rule; the grader checks the total bits and the validity of the tree. Write your rule at the top of your answer.

Why is greedy correct here?

Two observations are enough:

  1. In an optimal prefix code, the two characters with the lowest frequencies are siblings at the deepest level. If not, swapping with a character that is deeper does not increase the total.
  2. If you think of these two characters as a single “combined character”, what remains is the same problem with one fewer character (optimal substructure).

So the choice “merge the two smallest frequencies” always leads to an optimal tree.

Building it in Java

static class Node {
    char ch; int freq; Node left, right;
    Node(char ch, int freq, Node left, Node right) {
        this.ch = ch; this.freq = freq; this.left = left; this.right = right;
    }
}
static Node build(char[] ch, int[] freq) {
    PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.freq - b.freq);
    for (int i = 0; i < ch.length; i++) pq.add(new Node(ch[i], freq[i], null, null));
    while (pq.size() > 1) {
        Node l = pq.poll(), r = pq.poll();
        pq.add(new Node('\0', l.freq + r.freq, l, r));
    }
    return pq.poll();
}

Common mistakes

Wrong ideaThe truth
A newly created node always enters the next mergeOnly if it is one of the two smallest weights
Not re-sorting the queue at each stepWrite the sorted list in each row and take the two smallest
Total bits = the sum of the code lengthsAdd them up multiplied by the frequency: Σ f × length
A frequent character gets a long codeThe opposite: a frequent character is near the root, with a short code
{0, 10, 110, 11} is a valid prefix code11 is the beginning of 110; invalid
A different tree on a tie = a wrong answerIf the rule is consistent it is valid; the total bits stay the same
n merges for n symbolsn−1 merges

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

How many merge steps does Huffman's algorithm perform for n distinct symbols, and how many nodes does the final tree have?

Show answer
n − 1 merges; 2n − 1 nodes

Each merge reduces the number of elements in the queue by 1: to go from n down to 1 takes n-1 merges. Each merge produces one internal node: n leaves + (n-1) internal nodes = 2n-1. A Huffman tree is a full binary tree: every internal node has exactly two children.

Question 2medium

Frequencies: E=2, B=4, D=7, A=10, F=12, C=15. Build the Huffman tree (the first node removed becomes the left child, left edge = 0). Give the code of every symbol.

Show answer
A=00, F=01, C=11, D=101, E=1000, B=1001

1) E2 + B4   = 6
2) 6  + D7   = 13
3) A10 + F12 = 22
4) 13 + C15  = 28
5) 22 + 28   = 50 (root)
From the root, the left is 22 (A, F) and the right is 28. The left of 28 is 13 and the right is C. The left of 13 is 6 and the right is D. The left of 6 is E and the right is B. Read the paths: A 00, F 01, C 11, D 101, E 1000, B 1001. There are no ties in this example, so the tree is unique.

Question 3medium

For the tree in the previous question, what is the total number of bits needed to encode the whole text?

Show answer
119 bits

Σ frequency × code length: A 10·2 = 20, F 12·2 = 24, C 15·2 = 30, D 7·3 = 21, E 2·4 = 8, B 4·4 = 16. Total 119. Check: the sum of the internal node weights is also 6+13+22+28+50 = 119.

Question 4easy

With a fixed-length code, how many bits per symbol are needed for these 6 symbols, and how many bits in total for the 50-character text? What percentage does Huffman save?

Show answer
3 bits/symbol, 150 bits; Huffman saves 31 bits (about 20.7%)

For 6 symbols, ⌈log⁡26⌉=3\lceil \log_2 6 \rceil = 3 bits. 50 × 3 = 150. Huffman is 119: a difference of 31 bits, 31/150 ≈ 20.7% saving.

Question 5medium

What is the average code length (bits per symbol) of that Huffman code?

Show answer
2.38

Total bits / total frequency = 119 / 50 = 2.38. The fixed length was 3 bits.

Question 6medium

Using the codes A=00, F=01, C=11, D=101, E=1000, B=1001, decode the bit string 1001001110100.

Show answer
BACDA

Read from the left and cut when a code is complete: 1001 = B, 00 = A, 11 = C, 101 = D, 00 = A. Because it is a prefix code, no separator is needed; no code is the beginning of another code.

Question 7easy

Encode the word FACE with the same codes. How many bits does it take?

Show answer
0100111000 (10 bits)

F 01, A 00, C 11, E 1000 → 01 00 11 1000 = 0100111000. 2+2+2+4 = 10 bits. With the fixed code it would be 4 × 3 = 12 bits.

Question 8easy

Is the code {0, 10, 110, 11} a prefix code? What about {0, 10, 110, 111}?

Show answer
First: no (11 is a prefix of 110). Second: yes

In a prefix code (prefix-free), no code can be the beginning of another code. In the first, 11 is the beginning of 110: when 110 is read it is unclear whether it is 11 or 110. The second can be read off the leaves of a binary tree: it is valid.

Question 9hard

Frequencies a=1, b=1, c=2, d=3, e=5. Tie rule: among equal weights, the node that entered the queue first is removed first (leaves enter in the order a, b, c, d, e). The first removed node is the left child (0). Give all codes and the total bits.

Show answer
e=0, d=10, c=110, a=1110, b=1111; 25 bits

1) a1 + b1   = 2  (the new node goes last)
2) c2 + (ab)2 = 4 (c entered first, left)
3) d3 + 4    = 7
4) e5 + 7    = 12
The tree is completely lopsided: at every merge the leaf is on the left. The codes: e 0, d 10, c 110, a 1110, b 1111. Bits: 5·1 + 3·2 + 2·3 + 1·4 + 1·4 = 25. If the tie rule changes, the codes change but the total bits (25) do not.

Question 10easy

What is the running time of Huffman's algorithm for n symbols using a binary heap?

Show answer
O(n log n)

n insertions (or O(n)O(n) with buildHeap), then n-1 times two deleteMin and one insert, each O(log⁡n)O(\log n). The total is O(nlog⁡n)O(n \log n).

Question 11hard

Write a method Node build(char[] ch, int[] freq) that builds the Huffman tree with PriorityQueue. Node has fields char ch; int freq; Node left, right;.

Show answer
Add all leaves to a min-PQ by freq; while size > 1, poll two, create parent, offer it; return the last node

static Node build(char[] ch, int[] freq) {
    PriorityQueue<Node> pq = new PriorityQueue<>((a, b) -> a.freq - b.freq);
    for (int i = 0; i < ch.length; i++)
        pq.add(new Node(ch[i], freq[i], null, null));
    while (pq.size() > 1) {
        Node left = pq.poll();               // the smallest
        Node right = pq.poll();              // the second smallest
        pq.add(new Node('\0', left.freq + right.freq, left, right));
    }
    return pq.poll();
}
Java's PriorityQueue does not guarantee any order on ties; if you want to apply the exam's tie rule exactly, add a second key (insertion order) to the comparator.

Question 12medium

Write a recursive method void printCodes(Node n, String code) that prints every symbol with its Huffman code (left = 0, right = 1).

Show answer
Leaf: print ch and code; otherwise recurse left with code + "0" and right with code + "1"

static void printCodes(Node n, String code) {
    if (n == null) return;
    if (n.left == null && n.right == null) {
        System.out.println(n.ch + " = " + code);
        return;
    }
    printCodes(n.left, code + "0");
    printCodes(n.right, code + "1");
}
The call: printCodes(root, ""). In a text with a single symbol the root is a leaf and the code comes out empty; in this special case you need to give "0".

Verification: All merge steps, codes, total bits, averages, encode/decode results and the prefix check were computed with Java's PriorityQueue in verify/SEN2212/W12.java (java W12.java). The tie rule: the one that entered the queue first comes out first (leaves enter in the given order). The buildTree and printCodes methods were tested in verify/SEN2212/WMethods11to14.java.

Review card

Huffman: put all frequencies into a min-PQ repeat: remove the two smallest nodes, make a new node with their sum, put it back first removed = LEFT (0), second = RIGHT (1) (read the rule in the question) n symbols → n-1 merges, 2n-1 nodes, a full binary tree code = the path from the root to the leaf; a frequent symbol gets a short code total bits = Σ frequency × code length = sum of the internal node weights average = total bits / total frequency fixed length = ceil(log2 number of symbols) bits prefix code: no code is the beginning of another → decoding without separators the tie rule changes the tree, it does NOT change the total bits time O(n log n) example E2 B4 D7 A10 F12 C15 → 119 bits, avg 2.38, fixed 150