COMPUTER ENGINEER

← BlogDATA STRUCTURES / ALGORITHMSPython example pack ↓

Searching and sorting · 14 / 53

Merge sort and external sorting

Understand → trace → implement → justify

3 min reading · allow extra time for practice

Merge sort splits data, sorts both halves, then merges with two pointers. Its invariant is that the chosen prefix is sorted and the next minimum is at one of the two remaining heads. Choosing the left item on equality preserves stability.

Balanced splitting gives Θ(n log n) time for sorted, reversed and duplicate-heavy inputs. Standard array variants need buffers. Python slices copy sublists, so allocation differs from an index-based implementation. Linked-list variants can relink nodes, but recursive stack space still counts.

External merge sort handles data exceeding RAM: sort memory-sized runs, write them, then perform a buffered k-way merge. A priority queue holds run heads; block I/O and buffer counts become central costs.

A sorting-selection atlas

AlgorithmWorst-case timeTypical auxiliary spaceStable?
InsertionΘ(n²)O(1), in-place modelYes with strict comparisons
MergeΘ(n log n)O(n), standard array mergeYes if ties favour the left
QuickΘ(n²)Worst-case O(n) stackNot standard swapping versions
HeapΘ(n log n)O(1), iterative in-placeNo
CountingΘ(n+k)O(n+k), stable outputWith a suitable implementation
RadixΘ(d(n+k))Depends on digit sortingIf every pass is stable

Expected Θ(n log n) quicksort does not erase its quadratic worst case. Linked-list merging can relink nodes with different space trade-offs. Python slicing allocates; do not transfer an abstract in-place claim to this code.

Merge invariant: the output prefix is sorted and contains each consumed input element exactly once. Choosing the smaller head preserves it. Choosing the left on equal keys preserves stability. The remaining suffix is already sorted and can be appended.

Foundation: Trace merging [2,5,8] and [1,5,7]. Intermediate: Test stability with (key, originalIndex) pairs. Advanced: Design a 100 GB external sort: runs, fan-in, buffers and disk passes. Use an I/O model, not only RAM complexity.

A worked trace

[5,1,4,2] yields [1,5] and [2,4]. Choose heads1,2,4, then append5. Left-first ties preserve record order.

Cost and assumptions

T(n)=2T(n/2)+Θ(n)=Θ(n log n). This slicing variant has O(n) peak auxiliary space and O(log n) stack.

Executable example

Run with Python 3. Assertions check a small example; they do not constitute a general correctness proof. Some advanced examples are explicitly small models or reference oracles, not full production implementations.

Python 3
def merge_sort(a):
    if len(a) < 2: return list(a)
    m = len(a)//2
    left, right = merge_sort(a[:m]), merge_sort(a[m:])
    out, i, j = [], 0, 0
    while i < len(left) and j < len(right):
        if left[i] <= right[j]: out.append(left[i]); i += 1
        else: out.append(right[j]); j += 1
    return out + left[i:] + right[j:]
assert merge_sort([5,1,4,2]) == [1,2,4,5]

TEST YOUR UNDERSTANDING

Your turn

How would you sort a 100 GB file with 1 GB RAM?

Reveal the reasoning

Memory-bounded runs, temporary disk files, buffered k-way merge, resource cleanup and disk/error handling. Do not load the entire file into a list.

Take it further

  1. Write down the input contract and the invariant before changing the example.
  2. Add empty, boundary, duplicate and adversarial cases where they are relevant. Compare with a simple oracle.
  3. Derive the cost under the stated model; measure several input sizes and explain discrepancies.

Primary reading & lecture notes

Original explanations in this course; these references provide proofs and further reading. Source links may cover the broader module.

Princeton · Algorithms, Sedgewick & Wayne ↗Pat Morin · Open Data Structures ↗
Solve the exercise before moving on.