3.6.9 · D2Sorting & Searching

Visual walkthrough — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

2,235 words10 min readBack to topic

Step 0 — The one thing we assume: comparisons only

WHAT. We consider an algorithm that sorts distinct items but is only allowed one kind of move: pick two items, ask "is this one smaller than that one?", and get back yes or no. Nothing else. No looking at the actual numbers, no arithmetic on them.

WHY. This is the whole game. Everything that follows is squeezing out of this single restriction. If we let the algorithm peek at values (like Counting Sort does), the proof does not apply — and we'll see exactly why at the end.

PICTURE. Two cards face-down. You may only ask "left < right?".

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 1 — Count the possible answers: there are of them

WHAT. Before any question is asked, the items could be in any order. The number of distinct orderings of different items is Each factor is "how many items are still unplaced when I fill this slot".

WHY. The job of a sort is to name which of these orderings the input actually is (equivalently, which shuffle to undo). So the number of possible correct answers is exactly . That count is the mountain the algorithm must climb.

PICTURE. For there are arrangements — all six shown as little card triples.

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 2 — Draw the algorithm as a tree of questions

WHAT. Whatever the code does — loops, recursion, swaps — the only thing that steers it differently on different inputs is the answer to a comparison. So the entire behaviour is a binary tree:

  • each internal node = one comparison "",
  • its two branches = the two answers (left for "yes / <", right for "no / ≥"),
  • each leaf = the ordering the algorithm finally announces.

We call this the decision tree.

WHY. This is the key modelling trick: it turns "an algorithm" (hard to reason about) into "a tree" (easy to count). Running the algorithm on one input = walking one root-to-leaf path, taking the branch each answer dictates.

PICTURE. A small decision tree for . Follow a path: two "yes"s send you left-left to a leaf.

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 3 — Every ordering needs its own leaf

WHAT. Take any two different correct answers, say ordering and ordering . Their leaves must be different leaves.

WHY. A leaf outputs one fixed permutation. If and landed on the same leaf, the algorithm would print the same permutation for both — but they need different answers, so it would be wrong for at least one input. Since all orderings can occur and each needs a distinct reachable leaf: Here just names "how many leaves the tree has".

PICTURE. Two inputs forced to different leaves; the "shared leaf" attempt is crossed out because it must give one input the wrong output.

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 4 — A binary tree of height has at most leaves

WHAT. Count leaves top-down. At depth 0: 1 node. Each comparison has 2 answers, so each level can at most double the number of nodes. After levels: The exponent is the height; the base is "two answers per question".

WHY. This is the ceiling on how much you can distinguish with yes/no questions. Two answers per node is a hard fact — a comparison cannot return three things. So information capacity is capped at .

PICTURE. Levels of a binary tree, each label showing leaves — the doubling made visible.

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 5 — Squeeze the two facts together

WHAT. Chain the two inequalities we earned:

  • : capacity beats how many leaves you actually have (Step 4).
  • : you have at least leaves (Step 3).
  • The middle drops out, leaving capacity vs. demand: .

Now take of both sides. is the inverse of "2 to the power" — it asks "2 to what power gives this number?", which is precisely "how many yes/no questions does this many outcomes need?". That is why and not or : our questions are binary.

WHY. turns the towering into the plain we're hunting, and turns into "the number of binary questions its size demands". The inequality survives the log because is increasing (bigger input → bigger output), so it never flips the .

PICTURE. The two curves (capacity) and the flat line (demand): the tree only works once capacity rises above demand, and that crossing point is .

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 6 — Turn into

WHAT. is exact but ugly. We want a clean, easily-read lower bound. Trick: keep only the top half of the factors of and throw the small ones away. To make "half" precise for every — even or odd — we use the floor (round down): it is the largest whole number , so counts exactly how many factors sit in the top half.

  • First ≥: dropping the factors (all ≥ 1) can only shrink the product.
  • Second ≥: the kept factors run from up to — at least of them, and every one is .

Take (which turns a product-power into a multiplication):

WHY. We only need a lower bound, so we're allowed to be wasteful and discard the small factors — that keeps the inequality honest while making it dead simple. The floor is not cosmetic: for odd (say ) there is no whole factor equal to , so we anchor on , which really is a factor and really bounds all the kept ones from below. Even just gives exactly — so the same formula covers both parities with no gap. The notation (see Big-O and Asymptotic Notation) means "grows at least this fast, ignoring constants," which is all a lower bound claims.

PICTURE. The factor bars ; the discarded left half greyed out, the kept right half each flattened down to the height — showing the " copies of " bound.

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

Step 7 — The escape hatch: why Counting Sort isn't bound

WHAT. Counting sort runs in (linear when the key range ) — apparently beating . No contradiction.

WHY. It does count[x]++ — it indexes an array by the value . That's not a comparison; it's peeking at the number itself. Our Step 0 assumption is broken, so the tree model never applied, so the bound never bound it. Same escape for Radix Sort.

PICTURE. Two doors: the "comparisons-only" door leads into the tree (bound applies); the "read the value" door bypasses the tree entirely.

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees

The one-picture summary

Everything on one canvas: orderings feed into a binary tree; each ordering claims a leaf; the tree of height holds at most leaves; so , hence .

Figure — Lower bound for comparison sorts — Ω(n log n) proof via decision trees
Recall Feynman retelling of the whole walkthrough

I hand you cards face-down and blindfold you. Your only move is asking "is this one smaller than that one?" — yes or no. (Step 0) But the cards could be shuffled different ways, and you must name the exact shuffle. (Step 1) Your whole strategy is a tree of yes/no questions; the slowest branch is your worst case. (Steps 2–3) Each shuffle needs its own ending spot, so you need at least endings. But questions create at most endings — each question only doubles your options. (Step 4) So ; taking the "how many binary questions" log gives . (Step 5) Keep just the big half of 's factors and that's already about . (Step 6) So blindfolded sorting must cost — and the only way to go faster is to take the blindfold off and read the numbers, which is exactly the cheat counting/radix sort use. (Step 7)


Active-recall

Why does the decision tree need at least leaves?
Each of the possible input orderings needs a distinct reachable leaf, else two inputs get the same (wrong) output.
Why exactly leaves, not more?
Each comparison has only 2 outcomes, so each tree level at most doubles the nodes; after levels there are at most .
Why take (not ) in Step 5?
Questions are binary (yes/no), so we count in powers of 2; answers "2 to what power reaches ".
What does physically mean?
The longest root-to-leaf path = the worst-case number of comparisons.
Why doesn't the bound catch counting sort?
It indexes arrays by value (count[x]++), not a comparison, so the comparisons-only premise fails.
Why use the floor in Step 6?
For odd , is not a whole factor; is the largest actual factor in the top half, so the bound stays exact for every parity.