Sorting
Sorting
From quadratic swaps to n log n divide and conquer
Putting things in order sounds trivial - until you count the work. A schoolbook method compares every pair and costs ; the clever ones halve-and-conquer for . For a million items that gap is fifty-thousand-fold. Sorting is where you first feel, in your bones, that the choice of algorithm matters more than the speed of the machine.
Two families, one job
The line-up, at a glance
Three words before the table. means equal keys keep their original relative order - vital when you sort by one field after another. means extra memory (beyond the recursion stack). And means already-ordered input runs faster - a property only some sorts have.
| Algorithm | Best | Average | Worst | Extra space | Stable? |
|---|---|---|---|---|---|
| Bubble | O(n) | O(n²) | O(n²) | O(1) | yes |
| Selection | O(n²) | O(n²) | O(n²) | O(1) | no |
| Insertion | O(n) | O(n²) | O(n²) | O(1) | yes |
| Merge | O(n log n) | O(n log n) | O(n log n) | O(n) | yes |
| Quicksort | O(n log n) | O(n log n) | O(n²) | O(log n) | no |
| Heapsort | O(n log n) | O(n log n) | O(n log n) | O(1) | no |
Read that table as a set of trade-offs, not a ranking. Merge sort guarantees and stability, but rents scratch memory to merge. Quicksort sorts in place and is usually fastest in practice, but a bad pivot can drag it to . Heapsort guarantees and sorts in place - its price is poor cache behaviour and no stability. There is no free lunch.
Race the algorithms yourself
Pick an algorithm and press Run (or Step through one operation at a time). Bars are array values; amber marks a comparison, red a swap / write, green a value now in its final place. Watch the two counters - comparisons and swaps - tell the real cost story.
Bubble: O(n²) average & worst, O(n) if already sorted (early exit) · in-place · stable.
Choose an algorithm and press Run - or Step through one operation at a time.
Now break quicksort - the "already sorted" trap
Selection is not adaptive - insertion is
Merge vs. quick vs. heap, in one breath
Merge sort divides the array in half, sorts each half, then merges two sorted runs by repeatedly taking the smaller front element - the merge is where all the work lives. Quicksort flips the order: it partitions around a pivot so everything smaller sits left and larger sits right, then recurses - the partition does the work and the "merge" is free. Heapsort builds a binary heap, then repeatedly pops the maximum to the end. Here is quicksort's engine, the Lomuto partition our demo uses:
def quicksort(a, lo, hi):
if lo >= hi: # 0 or 1 elements: already sorted
return
p = partition(a, lo, hi) # pivot lands at its FINAL index p
quicksort(a, lo, p - 1) # sort the left region
quicksort(a, p + 1, hi) # sort the right region
def partition(a, lo, hi):
pivot = a[hi] # LAST element as pivot (Lomuto)
i = lo - 1 # boundary of the "<= pivot" region
for j in range(lo, hi):
if a[j] <= pivot:
i += 1
a[i], a[j] = a[j], a[i] # grow the small region
a[i + 1], a[hi] = a[hi], a[i + 1] # drop the pivot between the two regions
return i + 1 # a sorted array makes every split n-1 / 0 -> O(n^2)Why is a wall
No comparison-based sort - one that learns about order only by asking "is before ?" - can beat in the worst case. The argument is short and beautiful, and it comes straight from logarithms.
The counting argument (the light version)
A sort must be able to produce any of the possible orderings of its input. Each comparison has two outcomes, so a sequence of comparisons can distinguish at most arrangements - a binary decision tree of height has at most leaves. To tell apart all inputs we therefore need
Stirling's approximation gives . So every comparison sort needs comparisons in the worst case - merge and heapsort already sit on that floor. (Non-comparison sorts like counting or radix sort escape it by not comparing keys - a story for another day.)
Check yourself
Why can quicksort degrade to O(n²) on an already-sorted array?
What sets the O(n log n) floor for comparison-based sorting?
Which common sorts are O(n log n) in the worst case, and which of them needs extra memory?
Pin down the worst-case guarantees and the memory cost together. Try to state it, then check.
Lock it in
- Elementary sorts (bubble, selection, insertion) cost ; divide-and-conquer sorts (merge, quick, heap) reach .
- The table is trade-offs, not a ranking: speed, extra memory, and stability each pull a different way.
- A last-element pivot makes an already-sorted array quicksort's worst case; insertion sort is adaptive and hits on the same input.
- No comparison sort beats : distinguishing all orderings needs comparisons.
Primary source
Ask your teacher