Skiena, Chapter 4

Sorting and Searching

Heapsort, mergesort, quicksort, binary search, and divide-and-conquer. The O(n log n) barrier and how the great algorithms reach it.

Prerequisites: Chapters 1-3 (especially heaps & BSTs). That's it.
9
Chapters
5+
Simulations
9
Quizzes

Chapter 0: Why Sort?

Sorting is the most fundamental algorithmic problem in computer science. Not because sorting itself is so exciting, but because so many other problems become easy once the data is sorted.

Need to find duplicates? Sort the data -- duplicates become neighbors. Need the median? Sort the data -- it's the middle element. Need to find the closest pair of numbers? Sort first -- the answer must be adjacent elements.

Skiena's take-home lesson: Sorting lies at the heart of many algorithms. Sorting the data is one of the first things any algorithm designer should try in the quest for efficiency.

Consider the problem of testing whether two sets are disjoint (share no elements). Without sorting, you must compare every element of set A against every element of set B: O(nm) time. But if you sort the smaller set first, you can binary-search for each element of the larger set: O((n+m) log m) time. For large inputs, that's the difference between hours and seconds.

The library sort function in your programming language is one of the most useful tools you have. Use it freely. But understanding how the major sorting algorithms work teaches design techniques that apply far beyond sorting.

Why is sorting often the first step in solving a different problem?

Chapter 1: Selection Sort

The simplest sorting algorithm is selection sort: repeatedly find the minimum element from the unsorted portion and put it next in the sorted output.

Find minimum
Scan the unsorted portion for the smallest element.
Swap to front
Swap it with the first unsorted element.
↻ repeat until sorted

Selection sort does n iterations. On each iteration, it scans the remaining unsorted elements to find the minimum. The first scan looks at n elements, the second at n-1, then n-2, and so on. The total number of comparisons is:

n + (n-1) + (n-2) + ... + 1 = n(n+1)/2 = O(n2)

Can we do better? The bottleneck is finding the minimum -- it takes O(n) per iteration because we use an unsorted array. But Chapter 3 gave us a data structure that finds the minimum in O(log n) time: the heap. Replacing the unsorted array with a heap immediately improves selection sort from O(n2) to O(n log n). That improved algorithm has a name: heapsort.

The lesson: Selection sort is not just a slow algorithm to discard. It reveals a design pattern: the right data structure can turn a slow algorithm into a fast one. Heapsort IS selection sort with a better data structure.
What makes selection sort O(n2)?

Chapter 2: Heapsort

A heap is a binary tree stored in an array where every parent dominates its children (smaller in a min-heap, larger in a max-heap). The key property: the minimum is always at the root, accessible in O(1).

The array trick is elegant. For element at position k:

parent(k) = ⌊k/2⌋,   left-child(k) = 2k,   right-child(k) = 2k + 1

Heapsort works in two phases. First, build a heap from the unsorted data (O(n) time using bottom-up construction). Then, repeatedly extract the minimum (O(log n) each), producing elements in sorted order.

Heap Extract-Min

Click "Extract Min" to remove the root and watch the heap repair itself by bubbling down. The sorted output grows on the right.

Total cost: O(n) to build the heap + O(n log n) for n extractions = O(n log n). Heapsort is guaranteed O(n log n) in the worst case, with no extra memory beyond the array itself.

Why does extracting the minimum from a heap take O(log n)?

Chapter 3: Mergesort

Mergesort is the classic divide-and-conquer sorting algorithm. The idea is beautifully simple:

Divide
Split the array into two halves.
Conquer
Recursively sort each half.
Merge
Combine the two sorted halves into one sorted array.

The merge step is the key. Given two sorted arrays, we can merge them into one sorted array in O(n) time by repeatedly taking the smaller of the two front elements.

How many levels of recursion are there? Each level halves the array, so there are log2 n levels. Each level does O(n) total work (merging). Therefore the total is:

O(n) × O(log n) = O(n log n)
Mergesort vs heapsort: Both are O(n log n) worst case. Mergesort has excellent cache behavior (sequential access) and is stable (equal elements keep their original order). The downside: mergesort needs O(n) extra space for the merge buffer. Heapsort is in-place but has poor cache locality due to the tree jumps.
Mergesort Visualization

Watch the array split, sort recursively, and merge back together. Each row shows a level of the recursion.

Ready
Why does mergesort take O(n log n) time?

Chapter 4: Quicksort

Quicksort is the most widely used sorting algorithm in practice. Like mergesort it divides and conquers, but with a twist: the hard work happens during the divide, not the merge.

Pick a pivot
Choose an element p from the array.
Partition
Move elements < p to the left, elements > p to the right.
Recurse
Recursively sort the left and right partitions.

After partitioning, the pivot is in its final sorted position. No merge step is needed -- the array is sorted in place.

The pivot matters: If the pivot splits the array roughly in half each time, quicksort is O(n log n). But if the pivot is always the smallest or largest element (as happens with sorted input and a first-element pivot), the partitions are maximally unbalanced and quicksort degrades to O(n2). Solution: pick the pivot randomly. A random pivot gives expected O(n log n) regardless of input.

Despite the O(n2) worst case, quicksort is typically 2-3x faster than mergesort in practice. Why? The inner loop (partition step) has excellent cache locality, fewer data movements, and a very small constant factor. That's why most library sort functions use quicksort (or a hybrid like introsort).

When does quicksort degrade to O(n2)?

Chapter 5: Binary Search

Binary search is the key algorithm that makes sorted data powerful. Given a sorted array, we can find any element in O(log n) time by repeatedly halving the search space.

Compare with middle
Is the target equal to, less than, or greater than the middle element?
Eliminate half
If less, search the left half. If greater, search the right half.
↻ repeat until found or empty

Each comparison eliminates half the remaining elements. After k comparisons, only n/2k elements remain. We're done when n/2k = 1, which gives k = log2 n.

Binary Search

Watch binary search find the target value. Each step highlights the remaining search space and the comparison.

Ready
Binary search beyond arrays: The principle of "halving the search space" extends far beyond sorted arrays. You can binary-search on the answer to any monotonic function: "What's the smallest x such that f(x) ≥ target?" This technique appears in optimization, numerical methods, and competitive programming constantly.
How many comparisons does binary search need on a sorted array of 1 million elements?

Chapter 6: The n log n Barrier

Heapsort, mergesort, and quicksort all achieve O(n log n). Is this optimal, or could a sorting algorithm do better?

The answer is: no comparison-based sorting algorithm can do better than O(n log n). Here's the argument. A comparison sort can only learn about the order of elements by comparing pairs. With n elements, there are n! possible orderings. Each comparison has two outcomes (yes/no), so after k comparisons we can distinguish at most 2k orderings. To distinguish all n! orderings, we need:

2k ≥ n!  ⇒  k ≥ log2(n!) = Θ(n log n)

This is an information-theoretic lower bound. No cleverness in algorithm design can circumvent it, because the information simply isn't there in fewer comparisons.

Non-comparison sorts: The O(n log n) barrier only applies to comparison-based sorts. If we know the keys are integers in a bounded range, algorithms like counting sort, radix sort, and bucket sort can achieve O(n) time by exploiting the structure of the keys directly, bypassing comparisons entirely.
AlgorithmBestAverageWorstSpaceStable?
Selection SortO(n2)O(n2)O(n2)O(1)No
HeapsortO(n log n)O(n log n)O(n log n)O(1)No
MergesortO(n log n)O(n log n)O(n log n)O(n)Yes
QuicksortO(n log n)O(n log n)O(n2)O(log n)No
Why can't a comparison-based sorting algorithm beat O(n log n)?

Chapter 7: The Sort Race

Let's watch three sorting algorithms compete in real time. Each algorithm sorts the same random array while you watch the comparisons and swaps happen.

Sorting Algorithm Race

Click "Race!" to sort a random array with three algorithms simultaneously. Watch the bar heights converge to sorted order. The counter shows comparisons made.

n 30
What to notice: Selection sort moves methodically left-to-right, building the sorted portion one element at a time -- visibly slow. Mergesort works in recursive waves -- small sorted chunks merge into larger ones. Quicksort works chaotically at first but converges quickly. Watch the comparison counters: selection sort uses roughly n2/2 comparisons, while mergesort and quicksort use roughly n log n.
After racing the algorithms on n=50, which has the most comparisons?

Chapter 8: Connections

Sorting and searching are foundational tools that appear throughout the rest of the book. Here is where they connect:

ConceptWhere It Leads
Binary searchUsed in Chapter 6 for edge selection, Chapter 9 for decision problems
HeapsChapter 6: Priority queues power Prim's and Dijkstra's algorithms
Divide-and-conquerThe pattern behind mergesort reappears in algorithms for closest pair, matrix multiplication, and FFT
RandomizationQuicksort's random pivot idea extends to randomized algorithms throughout Chapter 7
The n log n barrierChapter 9: Lower bounds and impossibility results via reduction
Skiena's take-home lessons from Chapter 4:
• Sorting is the most fundamental operation in algorithm design. When in doubt, sort first.
• Heapsort = selection sort + the right data structure. This pattern (improving algorithms by improving data structures) recurs throughout CS.
• Quicksort is O(n2) worst case but O(n log n) expected with random pivots, and fastest in practice due to cache locality.
• The O(n log n) comparison-sort barrier is information-theoretic: you cannot distinguish n! orderings with fewer than log2(n!) comparisons.
• Binary search is the most important idea: halving the search space gives O(log n) queries on sorted data.
You need a guaranteed O(n log n) in-place sort. Which algorithm?