CSE3144 Β· Advanced Data Structures Β· Jul–Nov Semester 2026 Β· Lecture 6 of 36 Β· CO CSE3144.1

Huffman Trees & Applications

One greedy idea, two payoffs: the cheapest order to merge unequal runs, and the compression algorithm inside every ZIP file, JPEG, and MP3.

Dr. Manu ShrivastavaCourse Instructor Β· Consultation Fri 2–5 PM, LHC 308F
40 minutesSession outcome: apply Huffman coding and tree construction methods
L6 Β· 00 β€” Agenda 40 min total

Today, minute by minute

The debt: why merge order suddenly matters

00–04

Replacement selection gave unequal runs. Lecture 4's lone "15" gets its answer.

Merge trees and WEPL β€” try both plans yourself

04–10

Interactive: two merge orders for runs 2, 4, 5, 15 β€” cost 43 vs 52.

Animation: Huffman's greedy algorithm, step by step

10–17

Weights 4, 6, 8, 9, 15, 28 β†’ the optimal tree, with a live cost ledger.

Back to external sorting: closing Unit I

17–21

Apply it to our own runs (4, 8, 1); why L4's balanced pairing was fine for equal runs.

Animation: Huffman coding β€” compressing text

21–30

Fixed vs variable-length codes, prefix property, the CLRS example: 300 β†’ 224 bits.

Why greedy is optimal + complexity

30–35

The exchange argument, sketched for undergraduates. O(n log n) with a heap.

Applications, recap, homework

35–40

ZIP, JPEG, MP3 β€” and the end of Unit I.

CSE3144 β€” Lecture 6
L6 Β· 01 β€” The need ~4 min

Two lectures ago we planted a question. Time to answer it.

The evidence so far

Lecture 4, the 13-record trace: the lone run 15 was read and rewritten in every single pass while bigger runs merged around it. You asked (we made you ask): could a smarter schedule avoid that waste?

Lecture 5, replacement selection: runs now come out unequal by design β€” ours were lengths 4, 8, 1. Unequal runs are no longer an accident; they are the normal output of a good Phase 1. So "in what order do we merge unequal runs?" is now a question we face on every single sort.

The key physical fact

When two runs of lengths a and b are merged, every record of both is read and written once β€” cost proportional to a + b. And the output run of length a + b may be merged again later, moving those same records again.

So a record's total movement = how many merges its run participates in. Records in a run merged early and often move many times; records in a run merged once move once. Intuition forming: small runs can afford many merges; big runs should merge as few times as possible. Let's make that precise β€” and provable.

Everyday version: you must combine several stacks of graded answer sheets into one sorted pile. Combining stacks means re-handling every sheet in both. Would you repeatedly re-handle the 500-sheet stack, or keep it aside and let the small stacks consolidate first? You already know the answer in your hands β€” today we learn its name: the Huffman tree.

CSE3144 β€” Lecture 6
L6 Β· 02 β€” Merge trees & WEPL ~6 min

A merge schedule is a tree; its cost is WEPL

Definitions
  • A merge tree records a merge schedule: square external nodes = the initial runs (with their lengths as weights); circular internal nodes = merges, weight = sum of children.
  • A record moves once for every internal node above its run β€” i.e. its run's depth.
  • Total merge cost = Ξ£ (run length Γ— its depth) = Weighted External Path Length:
WEPL(T) = Ξ£ wi Β· depth(i)   = Ξ£ (internal node weights)

The second equality is the practical one: just add up the merge sums. Same number, two readings.

Try both plans β€” 4 runs of lengths 2, 4, 5, 15

The "natural" balanced pairing β€” exactly what Lecture 4 did β€” loses by 9 records of movement here, because it drags the run of 15 through two merges. Plan A buries the tiny runs deep (they're cheap to re-move) and keeps 15 at depth 1. Generalize that instinct and you have Huffman's algorithm.

CSE3144 β€” Lecture 6 Β· WEPL
L6 Β· 03 β€” Huffman's algorithm ~7 min

The greedy rule: always merge the two lightest trees

The algorithm (D. Huffman, 1952)
put all n weights in a min-heap, each a 1-node tree
repeat n βˆ’ 1 times:
    a ← pop smallest tree
    b ← pop next smallest
    c ← new node, children a and b,
        weight = a + b          // one merge
    push c
// the single remaining tree is optimal
How to read the animation

The forest starts as n single runs. Each step, the two lightest trees fuse into a new tree whose weight is their sum β€” and that sum is exactly the cost of that merge, posted to the ledger. When one tree remains, the ledger total is the WEPL. Weights: 4, 6, 8, 9, 15, 28 (a favourite exam configuration).

Interactive β€” build the optimal merge tree
CSE3144 β€” Lecture 6 Β· Huffman construction
L6 Β· 04 β€” Closing the loop ~4 min

Apply it to our own runs β€” and close Unit I's story

Our replacement-selection runs: lengths 4, 8, 1

Huffman order: merge the two smallest first β€” 1 + 4 = 5 (cost 5), then 5 + 8 = 13 (cost 13). Total movement: 18 records.
WEPL check: 1Β·2 + 4Β·2 + 8Β·1 = 2 + 8 + 8 = 18 βœ“

The other order: 4 + 8 = 12 (cost 12), then 12 + 1 = 13 (cost 13). Total: 25 records.
WEPL: 4Β·2 + 8Β·2 + 1Β·1 = 25. The big run of 8 got dragged through two merges β€” 39% more I/O, for choosing the wrong order on just three runs.

Two loose ends, tied
  • Lecture 4's lone "15": carrying it down pass after pass was a bad merge tree β€” it sat at maximum depth. Huffman would merge it into the smallest partner immediately: minimum total movement, question answered.
  • Was Lecture 4 wrong to pair evenly? No β€” when all runs are equal, Huffman itself produces the balanced, complete tree (all weights tie, pairing is level by level). Balanced pairing is the special case; Huffman is the general law. (Homework asks you to argue this.)

For k-way merging the same idea generalizes: a k-ary Huffman tree (pad with dummy zero-length runs so the arity works out) β€” mentioned for completeness, not examined in depth.

Unit I is now complete: amortized analysis (the toolkit) β†’ external sorting (the problem) β†’ tournament trees, buffers, replacement selection (the engine) β†’ Huffman merging (the scheduler). Every piece exists because disks punish wasted movement.

CSE3144 β€” Lecture 6
L6 Β· 05 β€” Huffman coding ~9 min

The same tree, compressing every file you own

The compression problem

A 100-character file uses 6 letters with these counts: a:45 Β· b:13 Β· c:12 Β· d:16 Β· e:9 Β· f:5. A fixed-length code needs ⌈logβ‚‚ 6βŒ‰ = 3 bits per character β†’ 300 bits.

Idea: give frequent letters short codes and rare letters long ones β€” like Morse code gave 'E' a single dot. Danger: with variable lengths, where does one code end and the next begin? Solution: a prefix code β€” no codeword is a prefix of another. Then decoding is unambiguous: read bits, walk the tree, hit a leaf, emit, restart.

And here is the punchline: expected code length = Ξ£ freq Γ— depth = WEPL again. Minimizing bits is the same problem as minimizing merge cost β€” frequencies play the role of run lengths. Same greedy, same tree.

Reading the code off the tree
  • Build the Huffman tree on frequencies (animation β†’).
  • Label every left edge 0, every right edge 1.
  • A character's code = the 0/1 path from root to its leaf. Leaves only ⟹ prefix property is automatic.
  • Frequent characters end up shallow (short codes); rare ones deep (long codes) β€” Huffman guarantees the optimum.

Decode demo for later: with the tree built, 1100Β·0Β·100Β·1101 reads f–a–c–e: "face" in 12 bits instead of 4 Γ— 3 = 12… try "dead" and "cab" in the homework β€” that's where the savings show.

Interactive β€” build the code for a:45 b:13 c:12 d:16 e:9 f:5
CSE3144 β€” Lecture 6 Β· Huffman coding
L6 Β· 06 β€” Why greedy is optimal ~5 min

The exchange argument β€” grounded in the tree you already built

L6·03's animation showed greedy working on weights 4, 6, 8, 9, 15, 28 (final WEPL 164). This slide proves it must always work β€” using that exact same tree as the running example, not a fresh abstract one.

Obs 1, made concrete β€” a bad tree, then the fix

Suppose someone built a different tree on just {4, 6, 28}: merge 28 with 4 first (32), then merge with 6. Result: 6 sits at depth 1, while heavy 28 sits deeper, at depth 2.

WEPL = 6·1 + 28·2 + 4·2 = 6 + 56 + 8 = 70

Swap 6 and 28's positions (heavy rises to depth 1, light sinks to depth 2) β€” nothing else about the tree changes:

WEPL = 28·1 + 4·2 + 6·2 = 28 + 8 + 12 = 48

Cost dropped from 70 to 48 just by moving the heavier weight shallower. Any tree with a heavy weight sitting deeper than a light one can always be improved this way β€” so it was never optimal to begin with.

Obs 1, the general rule behind that swap

Swapping a heavy weight (at deep level ddeep) with a light weight (at shallow level dshallow) changes the cost by exactly:

ΔWEPL = −(wheavy − wlight) × (ddeep − dshallow)

Check it against the example: −(28−6)×(2−1) = −22 — exactly the 70→48 drop.

Both factors are always positive whenever heavy-sits-deeper happens (bigger weight, bigger depth-gap) β€” so ΔWEPL is always negative. Conclusion: in any optimal tree, no heavier weight can sit deeper than a lighter one β€” lighter weights always sink to the bottom.

Obs 2, made concrete β€” look at the bottom of your own tree

Go back to the real 6-weight tree from L6·03. Its deepest level (depth 4) holds exactly one sibling pair: 4 and 6 — the two lightest weights of all six — sitting under their shared parent node "10". This is not a coincidence: every internal node has exactly two children, so the deepest occupied level always comes in sibling pairs, and by Obs 1, the lightest available weights are exactly the ones Obs 1 will always push into those bottom slots. So some optimal tree always begins with the two lightest as siblings — precisely Huffman's first move.

…then induction finishes it, one familiar step at a time

Merge 4+6→10 (cost 10) and the 6-weight problem becomes a 5-weight problem: {10, 8, 9, 15, 28} — literally the forest after L6·03's animation step 1. The same two arguments now apply to this smaller forest: its own two lightest (8, 9) must again be optimal siblings at its own deepest level.

Repeat: 6 weights → 5 → 4 → 3 → 2 → 1, each step justified the same way. Complexity: heap init O(n); n−1 pops/pushes at O(log n) each → O(n log n) total — seconds by hand with the forest-and-ledger method.

Sanity check β€” Huffman vs the best "balanced" tree

Weights 2, 3, 5, 7, 9, 13: the Huffman tree's WEPL is 93; the best complete binary tree on the same weights scores 95. Balanced is close but not optimal the moment weights are unequal β€” the two-point gap is precisely the algorithm's edge, and a favourite exam comparison.

CSE3144 β€” Lecture 6 Β· Optimality
L6 Β· 07 β€” Recap & what's next ~3 min

One tree, three takeaways β€” and Unit I closes

Lecture 7 β€” Unit II begins

Binary Search Trees Review & AVL Trees

Back to in-memory structures: the degenerate-BST problem from Lecture 2, and the first self-balancing answer. Bring your rotations.

Homework β€” bring to Lecture 7
  • Build the Huffman tree for weights 2, 3, 5, 7, 9, 13 (forest + ledger method); verify WEPL = 93. Draw the best complete binary tree and verify 95.
  • Your L5 homework's replacement-selection runs: compute the optimal merge order and its WEPL; compare against merging in arrival order.
  • Using the a–f code built today: encode "deadface"; decode 0Β·101Β·0Β·111. How many bits does "deadface" save vs 3-bit fixed?
  • Argue (3–4 sentences): if all n runs have equal length, Huffman's construction yields a complete binary tree β€” so Lecture 4's balanced pairing was already optimal for equal runs.
  • Letter frequencies of MANIPAL UNIVERSITY JAIPUR (count them, ignore spaces): build the Huffman code and compute the compression ratio vs 5-bit fixed-length.
  • Reading: CLRS Β§16.3 (Huffman codes); Weiss Β§10.1.2.
CSE3144 β€” Lecture 6

Questions?

Dr. Manu Shrivastava β€” LHC 308F β€” Friday 2:00–5:00 PM

Next: Lecture 7 β€” Binary Search Trees Review and AVL Trees. Unit II begins.