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

Tournament Trees, Buffering & Run Generation

Three engineering upgrades that turn last lecture's external merge sort into the industrial-strength algorithm inside every database.

Dr. Manu ShrivastavaCourse Instructor Β· Consultation Fri 2–5 PM, LHC 308F
40 minutesSession outcome: explain tournament trees and optimal merge techniques
L5 Β· 00 β€” Agenda 40 min total

Today, minute by minute

Three unpaid bills from Lecture 4

00–04

Fast min-of-k, buffers that never stall, runs longer than memory.

Winner trees: knockout tournaments as data structures

04–09

n players, nβˆ’1 matches, replay one path in log k.

Animation: a winner tree drives a 4-way merge

09–16

Watch every replay; count every comparison.

Loser trees: the professional's version

16–21

Store the loser, halve the comparisons. Heap vs tournament.

Buffering: why merges stall, and double buffering

21–27

Animation: fixed buffers hit a wall.

Floating buffers, traced end to end

27–32

Animation: the textbook 2-run example, all 8 lines.

Run generation: replacement selection

32–38

Animation: Lecture 4's 13 records give 3 runs instead of 5.

Recap + homework

38–40

The complete industrial external sort, assembled.

CSE3144 β€” Lecture 5
L5 Β· 01 β€” The need ~4 min

Lecture 4 left three bills on the table

We proved that raising k (merging k runs at once) cuts passes to logk m and saves real disk I/O. But we ended with two catches, and quietly ignored a third problem. Today pays all three bills:

Bill 1 β€” CPU per record

Find the min of k fronts, fast

Outputting one record means finding the smallest among k run-fronts. Scanning costs kβˆ’1 comparisons per record β€” at k = 1000, that's 999 comparisons for every single record. We need the next minimum in log k. Answer: tournament trees.

Bill 2 β€” idle hardware

Keep disk and CPU busy at once

Disk reads, disk writes, and in-memory merging are three different pieces of hardware. With the minimum k+1 buffers, whenever the output buffer is being written the merge must halt β€” the CPU twiddles its thumbs at disk speed. Answer: double & floating buffers.

Bill 3 β€” short runs

Make runs longer than memory

Phase 1 produced runs of exactly M records β€” memory-sized. Fewer, longer runs mean a smaller m and fewer passes. Can a memory of M records emit runs longer than M? Astonishingly yes, ~2M on average. Answer: replacement selection.

Desk analogy, continued. Bill 1: with 1000 sorted bundles open, you don't want to glance at all 1000 top sheets to pick the smallest β€” you want a knockout bracket. Bill 2: while the office boy carries a finished stack to the store room, you shouldn't stop working. Bill 3: as you remove sheets from the desk, new sheets keep arriving β€” many can join the current sorted bundle instead of waiting for the next one.

CSE3144 β€” Lecture 5
L5 Β· 02 β€” Winner trees ~5 min

A knockout tournament, frozen into a tree

The structure

Think IPL playoffs or Wimbledon. A winner tree for n players is a complete binary tree with:

  • n external nodes (leaves) β€” the players. For us: the current front record of each of the k runs.
  • n βˆ’ 1 internal nodes β€” the matches. Each stores the winner of its two children. We want the minimum, so smaller wins: a min winner tree.
  • The root holds the overall winner β€” the smallest of all k fronts, readable in O(1).

Height (excluding the player level) = logβ‚‚ n. Building it plays every match once: n βˆ’ 1 matches, O(n).

Example β€” 8 players, built bottom-up
1
3
3
4
3
6
6
8
1
1
1
5
3
7
3
players 4 3 6 8 1 5 7 3 (bottom row) β†’ quarter-finals β†’ semi-finals β†’ champion 1 β€” every connecting line is a played match

Read it like a sports bracket: 4 vs 3 β†’ 3 wins; 6 vs 8 β†’ 6; 1 vs 5 β†’ 1; 7 vs 3 β†’ 3. Semis: 3 vs 6 β†’ 3; 1 vs 3 β†’ 1. Final: 3 vs 1 β†’ champion 1.

The key move β€” replace the winner and replay ONE path

When champion 1 is removed (output to the merge) and its leaf gets a new value, who might the new champion be? Only players on the path from that leaf to the root β€” every other match's result is untouched. So we replay just logβ‚‚ n matches, not n βˆ’ 1. That's the whole magic: initialize O(n) once, then every extraction costs Θ(log n). Sorting with it: n extractions Γ— log n = O(n log n) β€” this is "tournament sort."

CSE3144 β€” Lecture 5 Β· Winner trees
L5 Β· 03 β€” Animation ~7 min

A winner tree drives a 4-way merge

Four sorted runs β€” R1: 6, 8 Β· R2: 4, 9 Β· R3: 5, 7 Β· R4: 1, 3 β€” merged into one output. The leaves hold the current front of each run; an exhausted run's leaf becomes ∞ (a sentinel that loses every match). Legend: amber = changed this step (new leaf + replayed path) Β· teal = root, the next output.

Interactive β€” extract, replace, replay

Count what you just saw: 8 records output, and each extraction replayed exactly logβ‚‚ 4 = 2 matches. A naive scan would compare k βˆ’ 1 = 3 fronts per record. At k = 1024 the gap is 10 comparisons vs 1023 β€” per record, across billions of records. Internal merge time becomes O(n logβ‚‚ k) per pass Γ— logk m passes = O(n logβ‚‚ m), independent of k β€” exactly the claim from Lecture 4's "Catch 2."

CSE3144 β€” Lecture 5 Β· Winner trees
L5 Β· 04 β€” Loser trees ~5 min

Store the loser, halve the work

The subtle inefficiency in replay

Replaying a match in a winner tree needs the new value's opponent. But who is the opponent at each internal node? It is the player who lost the last match played there β€” the winner moved up and away! In a winner tree you must look at a node's sibling subtree to find that loser.

So flip the storage: let each match node store the loser, and keep the overall winner in one extra node above the root. Now, walking up from the changed leaf, your opponent is sitting right there in the node: one comparison, write the new loser, carry the winner up. Same Θ(log n) asymptotics, but fewer memory accesses and exactly one comparison per level.

Same 8 players, as a min loser tree
1 β€” overall winner
3
6
4
4
3
8
6
8
3
5
1
5
7
7
3
match nodes hold LOSERS; the champion 1 sits in the extra box above the root

Check one node: the final was 3 vs 1 β†’ the root stores the loser 3; winner 1 goes on top. When 1's leaf is replaced, its replay partners (5's node, 3's node…) are exactly the values stored on its path.

Finding min of k fronts, per recordComparisonsWhy
Linear scank βˆ’ 1look at everything
Binary min-heapβ‰ˆ 2 logβ‚‚ ksift-down compares both children at every level, root to bottom
Loser treelogβ‚‚ kbottom-up, opponent pre-stored: one comparison per level

This is why the tournament (loser) tree is the preferred implementation over the heap for k-way merging β€” same O(n log k), half the constant. The run-generation algorithm of Walters, Painter & Zalk (this lecture's finale) is built on a loser tree too.

CSE3144 β€” Lecture 5 Β· Loser trees
L5 Β· 04a β€” Animation ~5 min

The same merge, driven by a loser tree

Identical runs, identical extraction order as L5·03's winner-tree animation β€” R1: 6, 8 · R2: 4, 9 · R3: 5, 7 · R4: 1, 3 β€” so every step can be compared directly. Watch which node changes at each level, and notice how often the displayed loser value stays put even while the tree keeps working correctly underneath it.

Interactive β€” extract, replace, replay (loser tree)
CSE3144 β€” Lecture 5 Β· Loser trees
L5 Β· 05 β€” Buffering ~6 min

Bill 2: the merge that keeps stopping

Why I/O and CPU can overlap at all

Every animation so far has treated a "read" or "write" as one instantaneous thing. In reality, disk transfers use DMA (Direct Memory Access): the CPU tells the disk controller "copy this block to/from this memory address," then walks away β€” the controller moves the bytes in the background and interrupts the CPU only when done. So disk-in, disk-out, and CPU-merge really are three independent workers running at once, not a simplification for this course. That's what the animation below now shows explicitly.

But this only pays off if each worker always has somewhere to work. With just the minimum k + 1 buffers:

  • While the output buffer is being written to disk, merged records have nowhere to go β†’ merge halts. Fix: two output buffers β€” merge into one while the other writes.
  • While a run's input buffer is being refilled, if the merge needs that run's next record β†’ merge halts. Fix: read ahead into a second buffer for that run.

So aim for 2k input + 2 output = 2k + 2 buffers. But there's a trap: fixing two buffers per run still fails β€” watch it happen β†’

Animation β€” fixed buffers hit the wall (2-way merge)

Run 0 begins 1, 3, 5, 7, 8, 9… Β· Run 1 begins 2, 4, 6, 15, 20, 25… Buffers hold 2 records. in[0], in[2] are fixed to Run 0; in[1], in[3] to Run 1. Three independent workers β€” CPU merge, disk read, disk write β€” are tracked separately below; each step shows exactly one worker completing something, so you can see precisely when each one starts, finishes, or is still busy.

The fix: FLOATING buffers

Don't marry buffers to runs. Keep all 2k input buffers in a common pool (stack); each run owns a queue of the buffers currently holding its data β€” at least one each. After every block arrives, ask: which run will run dry first? Answer: the run whose last loaded key (lastKey) is smallest β€” its data is being consumed fastest. Pop a free buffer from the pool and start reading that run's next block. The buffer "floats" to wherever the need is. Sentinel +∞ blocks mark the end of each run.

CSE3144 β€” Lecture 5 Β· Buffering
L5 Β· 06 β€” Floating buffers, traced ~5 min

The textbook trace, line by line

Two runs on disk, blocks of 2 records, sentinel +∞ ending each run β€” Run 1: [2 4] [6 8] [9 10] [+∞], Run 2: [3 5] [7 16] [21 26] [+∞]. Four floating buffers in the pool, two output buffers. Each step: merge two records to the output buffer, write it out, and β€” in parallel β€” load the next block from the run whose lastKey is minimum.

Interactive β€” the full 8-line trace

What to notice: the prediction rule (load for the run with the smallest lastKey) means a buffer is always arriving just before it is needed β€” input, output, and merging overlap almost perfectly. When merge time β‰ˆ block read time β‰ˆ block write time, the disk never waits for the CPU and the CPU never waits for the disk.

CSE3144 β€” Lecture 5 Β· Buffering
L5 Β· 07 β€” Run generation ~6 min

Bill 3: runs longer than memory β€” replacement selection

The intuition (desk version)

Lecture 4's Phase 1 was: fill the desk, sort, ship the whole bundle, repeat β€” runs of exactly M. But notice: the moment you ship the smallest sheet, its spot is free and a new sheet arrives immediately. If the new sheet's number is larger than what you just shipped, it can still join the current sorted bundle! Only when it's smaller must it wait (frozen ✱) for the next bundle.

So: keep a pool of M records (in practice, a loser tree β€” Walters, Painter & Zalk). Repeatedly output the smallest unfrozen record, refill from input, freeze arrivals smaller than the last output. When everything is frozen, close the run, thaw, continue. Runs come out β‰ˆ 2M long on average (Knuth's "snowplow" argument), so m halves, and passes = logk m shrink.

Animation β€” Lecture 4's 13 records, M = 3, same data new trick
Phase 1 method (same 13 records, M = 3)Runs produced2-way merge passes = ⌈logβ‚‚ mβŒ‰
Fill–sort–ship (Lecture 4)5 runs (3+3+3+3+1)3 passes
Replacement selection3 runs (4+8+1)2 passes β€” a full read+write of the file saved

A run of length 8 from a memory of 3 β€” because the input happened to arrive in a friendly order. Best case: input already sorted β†’ one single run, regardless of M. Worst case: input reverse-sorted β†’ runs of exactly M, no worse than Lecture 4. Average: 2M. Bonus: the whole thing runs with parallel input/output, like everything else today.

CSE3144 β€” Lecture 5 Β· Run generation
L5 Β· 08 β€” Recap & what's next ~2 min

The industrial external sort, fully assembled

Pipeline: replacement selection β†’ long runs β†’ k-way merge via loser tree β†’ floating buffers keep everything busy. That, plus Lecture 4's pass-counting, is the algorithm inside Unix sort and every database spill.

Bonus application

Tournament trees aren't only for merging: bin packing / truck loading by First Fit runs in O(n log n) using a max winner tree where players are bins and values are remaining capacities β€” find the leftmost bin that fits in log n. Same structure, different game.

Lecture 6

Huffman Trees & Optimal Merging

Runs now come out unequal (4, 8, 1…). In what order should we merge unequal runs? The lone "15" question from Lecture 4, answered optimally β€” weighted external path length.

Homework β€” bring to Lecture 6
  • Build the min winner tree for 4 3 6 8 1 5 7 3 2 6 9 4 5 2 5 8; replace the winner with 6 and replay (show each match).
  • Build the min loser tree for the same 16 keys; replace the winner with 9 and replay. Count comparisons in both exercises.
  • Run replacement selection with M = 4 on: 50 3 27 9 41 12 88 2 60 35 7 91. How many runs? What are their lengths?
  • Trace a 3-run floating-buffer merge exactly like today's animation (blocks of 2 records, six floating buffers, sentinel +∞ ending each run): Run1 = [22 27][28 30][31 32], Run2 = [25 31][36 38][40 62], Run3 = [26 30][33 35][42 45]. Produce the full line-by-line table: each run's queue, the output block, and which run the lastKey rule prefetches.
  • For k = 8 runs and n total records, count comparisons per record for linear scan, heap, and loser tree.
  • Reading: Weiss Β§7.11 (external sorting and multiway merging).
CSE3144 β€” Lecture 5

Questions?

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

Next: Lecture 6 β€” Huffman Trees and Optimal Merging of Runs.