Lesson 26 / 42

Merge Sort

Divide the array in half, sort each, then merge — O(n log n) guaranteed.

Divide, sort, merge

Split until each piece has one element (trivially sorted). Then repeatedly merge two sorted lists by comparing their fronts. log n levels of splitting × O(n) work to merge each level = O(n log n), always.

Implementation

The merge step is the heart: two indices walking two sorted halves.

def merge_sort(a):
    if len(a) <= 1:
        return a
    mid = len(a) // 2
    left = merge_sort(a[:mid])
    right = merge_sort(a[mid:])
    return merge(left, right)

def merge(l, r):
    out, i, j = [], 0, 0
    while i < len(l) and j < len(r):
        if l[i] <= r[j]:
            out.append(l[i]); i += 1
        else:
            out.append(r[j]); j += 1
    out.extend(l[i:]); out.extend(r[j:])
    return out

Trade-offs

Pros: predictable O(n log n), stable, great for linked lists and external (on-disk) sorting. Con: needs O(n) extra space for the merge buffer.

Quick check: Merge sort's worst-case time is...

  • O(n^2)
  • O(n log n)
  • O(n)
Answer

O(n log n) — The split is always balanced, so it's O(n log n) in every case — unlike quicksort.