Tutorials › Graph Theory (Undergraduate) › The Huffman Algorithm and Its Greedy Proof

Rooted Trees, Distance and Centers · Tutorial 166 of 799

The Huffman Algorithm and Its Greedy Proof

Build a Huffman code by hand and follow the sibling-leaf and contraction argument that proves the greedy algorithm is optimal.

Intermediate 10 min read

What You'll Learn

  • Define weighted path length for a binary prefix code.
  • Apply Huffman’s algorithm by repeatedly merging the two least weights.
  • Trace each merge and recover codewords from the resulting tree.
  • Prove that two least-weight symbols can be siblings in an optimal tree.
  • Use contraction to prove the greedy algorithm gives an optimal code.
  • Check a code’s weighted path length using both leaf depths and merge weights.

From Frequencies to a Binary Tree

When symbols occur with different frequencies, a useful code assigns shorter bit strings to more frequent symbols and longer strings to less frequent ones. The goal is not merely to make each symbol recognizable: among binary prefix codes, we want to minimize the average number of bits per symbol. Huffman’s algorithm solves this optimization problem by building a tree from the symbol weights, merging the two smallest weights at each step.

Let the symbols be a finite nonempty set, and let each symbol \(i\) have a positive weight \(w_i\), such as its frequency in a message. A binary code assigns a finite string of bits to each symbol. It is prefix-free if no codeword is a prefix of another. In the tree representation, each edge is labelled \(0\) or \(1\), and a symbol is placed at a leaf; its codeword is the sequence of labels on the path from the root to that leaf. The depth \(d_i\) of that leaf is the codeword length.

Definition: The weighted path length of a code is \(L=\sum_i w_i d_i\). If the weights are symbol frequencies, dividing \(L\) by the total frequency gives the average codeword length per symbol. A full binary tree is a rooted tree in which every internal vertex has exactly two children.

For a code with at least two symbols, we may seek an optimal code among full binary trees. If a code tree has an internal vertex with just one child, bypass that vertex and attach its child directly to its parent (or make the child the root if the vertex was the root). This preserves the code property and shortens the paths to all leaves below that vertex, without increasing \(L\). Repeating this operation removes every such vertex. There are finitely many full binary tree shapes with a fixed finite number of leaves, so an optimal full tree exists. The one-symbol case is separate: its optimal codeword can be the empty string, with length and weighted path length both zero. That empty codeword does not allow a receiver to determine how many copies were sent unless the number of symbols is known in advance.

The Huffman Algorithm

Definition: Start with one leaf for each symbol, labelled by its weight. While more than one tree remains, choose two trees with the least current weights, make them the children of a new parent, and label that parent by the sum of their weights. Stop when one tree remains. The resulting leaf-to-root structure gives a binary prefix code. Ties may be resolved in any way.

Each merge combines two current items, which may be original symbols or previously formed subtrees. The new weight is the sum of the two weights. To read off a code, label the two child edges at each merge \(0\) and \(1\), in either order. Different edge-label choices give different codewords but do not change their lengths or weighted path length.

Worked Example: Six Symbols and Their Codewords

Take symbols \(A,B,C,D,E,F\) with weights \(5,9,12,13,16,45\), respectively. The table records the current least items at each step; the two merged items are removed and their sum is inserted.

StepItems selectedNew weightItems after the merge
Start——\(5,9,12,13,16,45\)
1\(5,9\)\(5+9=14\)\(12,13,14,16,45\)
2\(12,13\)\(12+13=25\)\(14,16,25,45\)
3\(14,16\)\(14+16=30\)\(25,30,45\)
4\(25,30\)\(25+30=55\)\(45,55\)
5\(45,55\)\(45+55=100\)\(100\)

For example, assign \(0\) to the \(45\)-child and \(1\) to the \(55\)-child in the final merge. Within the \(55\)-subtree, assign \(0\) to the \(25\)-child and \(1\) to the \(30\)-child. Continue in the same way, assigning \(0\) to the \(12\)-child of the \(25\)-node and to the \(14\)-child of the \(30\)-node. The codewords are then \(F=0\), \(C=100\), \(D=101\), \(A=1100\), \(B=1101\), and \(E=111\).

The corresponding depths are \(d_F=1\), \(d_C=d_D=d_E=3\), and \(d_A=d_B=4\). Thus the weighted path length is \(45\cdot1+12\cdot3+13\cdot3+16\cdot3+5\cdot4+9\cdot4 =45+36+39+48+20+36=224\).

Check: Adding the weights created at every merge gives \(14+25+30+55+100=224\), agreeing with the leaf-depth calculation. The code is prefix-free because its words are exactly the root-to-leaf paths in a binary tree: a leaf cannot lie on the path to another leaf.

Why the Greedy Choice Can Be Optimal

The proof has two parts. First, some optimal tree has the two least-weight symbols as sibling leaves. Second, replacing those sibling leaves by a single leaf of their combined weight reduces the problem to a smaller instance. The cost change under this contraction is exactly the sum of the two weights.

Lemma: In a full binary tree with at least two leaves, there are two sibling leaves at maximum depth. Moreover, if \(x,y\) are symbols with the two least weights, there is an optimal full tree in which \(x\) and \(y\) are sibling leaves.

Proof. Choose a leaf \(a\) of maximum depth \(D\). Its sibling cannot be an internal vertex: an internal sibling has a descendant leaf at depth greater than \(D\), contradicting the choice of \(a\). Thus the sibling \(b\) is also a leaf, at depth \(D\). This proves the first claim.

Now take an optimal full tree and such a deepest sibling pair \(a,b\). Let \(x,y\) be symbols of least and second-least weight, so \(w_x\leq w_y\leq w_i\) for every other symbol \(i\). If a symbol of weight \(p\) at depth \(d\) is exchanged with one of weight \(q\) at depth \(D\), the change in weighted path length is \((p-q)(D-d)\). In particular, placing \(x\) at the position of a symbol \(q\) cannot increase the cost: \(w_x\leq q\) and \(D\geq d\). Place \(x\) at one of \(a,b\) this way. Then place \(y\) at the other position if it is not already there. The symbol displaced in this second exchange is neither \(x\) nor \(y\), so its weight is at least \(w_y\); the exchange again cannot increase the cost. The resulting tree still has optimal cost and has \(x,y\) as sibling leaves. \(\square\)

Theorem (Huffman’s algorithm): For any finite nonempty set of symbols with positive weights, Huffman’s algorithm produces a binary prefix code of minimum weighted path length.

Proof. We use induction on the number \(n\) of symbols. If \(n=1\), the empty codeword has weighted path length \(0\), which is minimum since codeword lengths cannot be negative. Suppose \(n\geq2\) and that the statement holds for every smaller positive number of symbols.

Let \(x,y\) be two symbols of least weight, and set \(s=w_x+w_y\). By the lemma, some optimal full tree has \(x,y\) as sibling leaves. Contract those two leaves and their parent to a single leaf \(z\) of weight \(s\). If the original tree has cost \(L\), the contracted tree has cost \(L-s\): the depths of \(x,y\) were each one more than the depth of \(z\), while all other depths are unchanged. Conversely, expanding a leaf \(z\) of weight \(s\) into sibling leaves \(x,y\) increases the cost by exactly \(s\).

Let the contracted problem consist of \(z\) and all the other symbols. Its minimum cost is \(OPT_c\), and the original minimum cost is \(OPT\). Contracting the optimal tree just described gives \(OPT_c\leq OPT-s\). Conversely, expanding \(z\) in an optimal contracted tree gives a valid original tree, so \(OPT\leq OPT_c+s\). These inequalities imply \(OPT=OPT_c+s\).

Huffman’s algorithm on the original weights first merges \(x,y\), creating precisely the leaf \(z\) of weight \(s\), and then runs Huffman’s algorithm on the contracted weights. This last point matters: the later merges are those of the contracted problem, not a new merge that must combine \(z\) with a particular item. By the induction hypothesis, the tree built from those contracted weights has cost \(OPT_c\). Expanding its \(z\)-leaf into \(x,y\) gives the original Huffman tree and adds exactly \(s\) to the cost. Its cost is therefore \(OPT_c+s=OPT\), as required. \(\square\)

Cost from Merges, and Further Checks

There is a convenient way to calculate the cost without first listing all codewords. Each time two subtrees of weights \(p\) and \(q\) are merged, every original symbol in them gains one unit of depth. The total increase in weighted path length is therefore \(p+q\). Starting with all symbols at depth zero, the final weighted path length is the sum of the weights created at the merges, including the final root weight. This also follows by tracking the contribution of each symbol: its weight is counted once for every merge on its path to the root, which is exactly its final depth.

Worked Example: Four Symbols

Let the symbol weights be \(a:2\), \(b:3\), \(c:7\), and \(d:9\). The successive least-weight merges are \(2+3=5\), then \(5+7=12\), then \(9+12=21\). The current items are therefore \(2,3,7,9\); then \(5,7,9\); then \(9,12\); and finally \(21\).

Choose child labels so that the first merge gives \(a=00\), \(b=01\), the second merge places the \(5\)-subtree below a \(0\) edge and \(c\) below a \(1\) edge, and the last merge places \(d\) below a \(0\) edge and the \(12\)-subtree below a \(1\) edge. The codewords are \(d=0\), \(c=11\), \(a=100\), and \(b=101\). Their weighted path length is \(9\cdot1+7\cdot2+2\cdot3+3\cdot3=9+14+6+9=38\).

Check: The merge-weight calculation gives \(5+12+21=38\), matching the leaf-depth calculation. The code is prefix-free: \(0\) is not the start of any of \(11,100,101\), and none of those three words is a prefix of another.

Worked Example: Equal Least Weights

Take three symbols \(p,q,r\) with weights \(1,1,2\). The least weights are tied, so merge \(p,q\) first: \(1+1=2\). The remaining weights are \(2,2\), so merge those to obtain \(2+2=4\). One resulting code is \(p=00\), \(q=01\), \(r=1\).

The depths are \(d_p=d_q=2\) and \(d_r=1\), giving \(1\cdot2+1\cdot2+2\cdot1=2+2+2=6\). The merge-weight sum is \(2+4=6\), which verifies the calculation independently. Either symbol of weight \(1\) could have been assigned to either child at the first merge; ties do not affect optimality.

Common Pitfall and Use

Do not omit the final merge. In the six-symbol example, summing only the first four new weights gives \(14+25+30+55=124\), not the code’s weighted path length. The final merge creates the root of weight \(100\), and it also increases the depth of every symbol by one. Including it gives \(14+25+30+55+100=224\), exactly the weighted path length computed from the codewords. Another common error is to merge a convenient pair rather than the two least current weights: the algorithm’s choice is based on the current items, including subtrees formed at earlier steps.

Huffman coding is useful because it turns a global optimization problem—choosing all codeword lengths at once—into a sequence of local merges with a proof that each initial greedy choice is safe. The sibling-leaf argument identifies an optimal tree compatible with the first merge; contraction then transfers the remaining optimization to a smaller instance. The proof relies on positive weights and a finite symbol set, and it permits arbitrary tie-breaking among equal least weights.

Check Your Understanding

For each question, use weighted path length as the sum of weight times leaf depth.

  1. For weights \(3,4,8,10\), list each Huffman merge and compute the total weighted path length from both the leaf depths and the merge weights.
  2. In the sibling-leaf lemma, why must the sibling of a deepest leaf also be a leaf?
  3. When two sibling leaves of weights \(u\) and \(v\) are contracted, by how much does the tree’s weighted path length change? Explain using their depths.
  4. For a single symbol, what codeword and weighted path length are optimal, and what decoding ambiguity can arise if the number of symbols is unknown?
  5. Explain why the contracted problem in the greedy proof must start with the combined leaf of weight \(w_x+w_y\).