Quick Sort

Context: FIT1008_MOC · a Divide and Conquer sort · solves the Sorting Problem · uses an Auxiliary Function (Recursion) · contrast with Merge Sort; falls back to Insertion Sort for tiny arrays FIT2004 emphasis: the three W4 design questions — how to partition efficiently ➔ Partitioning (Quicksort) · how to choose a good pivot (fixed → median-of-three → random → Quickselect → Median of Medians) · how to ensure the worst case never occurs. The same partition drives Quickselect, and W4 adds the average-case analysis FIT1008 never gave.

Quick Revision

  • 🎯 Objective: partition around a pivot, recurse both sides ➔ heavy-split / trivial-combine D&C sort, no merge.
  • 📦 Core Components: Partition ➔ smaller left, larger right | Pivot ➔ lands final | Recurse ➔ both sides.
  • ⚡ Key Constraint: in-place partition + smallest constant ➔ but on bad pivots; pivot choice is the whole game — and only a Median of Medians pivot converts “improbable” into “impossible”.

📝 How It Works

1. The Algorithm (Partition → Recurse)

  • Non-trivial split ➔ partition: smaller elements left of pivot, larger right ➔ Partitioning (Quicksort).
  • Trivial combine ➔ the pivot is already final, so the returned boundary is excluded from both recursive calls; recurse until a partition has size .
  • Mirror of Merge Sort ➔ merge sort splits trivially and combines expensively; quicksort splits expensively and combines for free. Both pay per level — the difference is where.

2. Partitioning — the Scheme Is a Separate Decision

  • Three schemes taught ➔ out-of-place ( extra space) · Hoare’s (in-place, swap per item) · Lomuto’s (in-place, more swaps, simplest) ➔ invariants and code in Partitioning (Quicksort).
  • 3-way / Dutch national flag ➔ partition into , , and recurse only on the outer two ⟹ the all-duplicates input drops from to , and depth becomes for distinct keys.
  • The choice never changes the complexity class (except the 3-way case) ➔ all schemes are comparisons per level; they differ in writes and space, which matters when records are large ➔ Sorting Problem.

3. Pivot Selection

  • Balance need not be perfect ➔ a median-ish pivot gives levels × ; even a fixed split gives depth ⟹ still . Only a split lopsided by a constant NUMBER of elements at every level reaches — that is the one question to ask of any pivot rule.
Pivot policyCost to chooseWorst caseTriggered byVerdict
First / last elementalready-sorted inputnever — sorted input is the common case, not a rare one
Minimum (or maximum) element—every inputworst and best are — the split is empty: always
th-percentile element—nothingfine — a constant fraction gives levels
Average (mean) element and similar skewed sequencesfails — the mean can sit arbitrarily far from the median
Middle elementorgan-pipe / crafted inputacceptable for coursework; still constructible
Median-of-threea crafted “killer” sequencethe practical library default
Random + RNG, probability an unlucky run onlyremoves the adversary, not the possibility
Exact median by Quickselect expected, worstquickselect’s own bad pivotsfails — see Quickselect §5
Median of Medians worstnothingwhen a guarantee is contractually required
  • The mean is not the median ➔ tempting and wrong. On the mean is , so the pivot is always the second-largest element ⟹ .
  • The general lesson (LO1) ➔ this is the failure mode of any Divide and Conquer design that does not guarantee proportional reduction — the same reason Binary Search works and a “shrink by one” search does not.

4. Ensuring the Worst Case Never Occurs

  • Randomisation is a probability claim ➔ it makes the input impossible to construct in advance, since the split no longer depends on the input’s arrangement; it does not bound any individual run. Lecturer’s verdict: in reality a random pivot works well enough on probability alone.
  • An exact median is not the fix ➔ computing it with Quickselect inherits quickselect’s own worst case ➔ Quickselect §5.
  • Median of Medians is the guarantee ➔ a provable split at every level ⟹ depth ⟹ worst case, at a constant factor large enough that practice still prefers random pivots.
  • The exam framing ➔ “how do you ensure the worst case never occurs?” asks you to distinguish expected from worst-case bounds, then name a mechanism that upgrades one to the other ➔ Algorithmic Complexity.
  • Introsort — the engineering answer ➔ run randomised quicksort but count the recursion depth; past , abandon and finish with Heapsort ⟹ worst case at quicksort’s average constant. (🔭 Beyond the lecture — named only because it is what real libraries ship.)

5. Space — Quicksort Is Never In-place

  • The lecturer’s flagged claim ➔ quicksort is not in-place even with in-place partitioning — depth is at least and every live frame is auxiliary space ➔ Analysing Recursive Algorithms (Time and Auxiliary Space).
  • Naive risk ➔ a degenerate split stacks frames ⟹ worst-case auxiliary space .
  • Fix ➔ recurse the smaller partition first, loop (tail-call) on the larger ➔ depth ⟹ space in every case, independent of pivot quality.
  • Only Heapsort achieves auxiliary among the sorts ➔ Sorting Problem.

6. Stability

  • Unstable as written ➔ partition swaps across long distances, throwing equal keys past one another — the mechanism that makes selection sort unstable; each scheme fails for its own reason ➔ Partitioning (Quicksort) §2–§5.
  • Universal fallback ➔ index-tagging works here as on any comparison sort, at space and no time penalty ➔ Sorting Problem §5.

⚙️ Core Implementation

🔹 quick_sort — the driver

⚖️ Core Decision Matrix

AlgorithmBestAverageWorstAuxiliary spaceStableTrait
Quick Sort smaller-first · naiveNoin-place partition, fastest in practice
Quick Sort Median of MediansNoworst case eliminated, large constant
Quick Sort exact-median QuickselectNono gain — inherits quickselect’s worst case
Merge SortYesguaranteed + stable, scratch
HeapsortNoin-place + guaranteed
Quickselect iterative—one rank only, not a sort

When It Flips: a quicksort run mirrors the BST shape of inserting the same pivots, and its no-merge, cache-friendly, in-place partition gives the smallest constant among sorts — choose it unless a worst-case bound (Heapsort) or stability (Merge Sort) is required; merge also wins for linked lists and external data. Making quicksort stable costs either way, at which point merge sort's scratch is free. Switch to a 3-way partition once duplicates are common, and to Quickselect the moment only one rank is wanted.

📊 Exam Execution Trace & Applied Exercises

Manual Execution Trace — the average-case height argument

Colour the middle half of the pivot positions green ( to ). A uniformly random pivot is green with probability , and the worst green pivot leaves a larger side of .

LevelLargest subproblem if every level is greenWork at this level
base case reached

Read the result: the change-of-base rule licenses the last step — and differ by the constant , so the base is invisible to the complexity class (though very visible in practice).

Applied Exercise — average case from the recurrence (NOT EXAMINABLE)

Problem: with partitioning costed at operations and the pivot equally likely at any rank , derive the average-case bound directly.

Final Extracted Output: . Two moves carry it — subtracting the instance to kill the summation, and bounding the harmonic tail by . Learn the technique; the derivation is off the exam.

Applied Exercise — matching locks to keys in (applied W4)

Problem: a key can be tried in a lock (too big / too small / fits), but two keys cannot be compared to each other, nor two locks. Match them all. The move: partition needs only one comparable pair, so use a lock as the pivot for the keys, then the matching key as the pivot for the locks.

Final Extracted Output: average. The transferable move is cross-pivoting: when the comparison model forbids comparing within a set, pivot each set on an element of the other set. The bound is average-case for the same reason quicksort’s is — an adversarial lock order still gives .

⚠️ Common Mistakes

  • 💡 Calling quicksort in-place ➔ the partition is in-place; the sort is not, because recursion frames are auxiliary space.
  • 💡 Worst case needs lopsided-at-every-level ➔ one bad split is harmless; requires a constant-size split at every level. A split is still .
  • 💡 Worrying about the log base ➔ , and are the same complexity class by change of base; quote a base only when discussing constants.

🧠 Active Recall