Skip to content
Part IA Michaelmas Term

Merge and Mergesort

merge

merge combines two already sorted lists into a single sorted list. It generalises insertion to two lists:

let rec merge = function
  | [], ys -> ys
  | xs, [] -> xs
  | x::xs, y::ys ->
      if x <= y then
        x :: merge (xs, y::ys)
      else
        y :: merge (x::xs, ys)

At each step, merge compares the heads of both lists and takes the smaller. It does at most m + n − 1 comparisons, where m and n are the lengths of the input lists. If n = 1, merging degenerates to insertion: it does proportionally more work for less gain.

merge is not tail-recursive, but making it iterative would offer little benefit for the same reasons that apply to append.

Top-down mergesort

Mergesort divides the input into two equal halves (unlike quicksort, which divides by value), sorts each half recursively, and merges the sorted halves:

let rec tmergesort = function
  | [] -> []
  | [x] -> [x]
  | xs ->
      let k = List.length xs / 2 in
      let l = tmergesort (take k xs) in
      let r = tmergesort (drop k xs) in
      merge (l, r)

Mergesort recursion tree

Complexity

Because take and drop divide the input into equal parts (differing by at most one element), the recurrence is always T(n) = 2_T_(n/2) + n, giving guaranteed O(n log n) in the worst case. Mergesort is asymptotically optimal.

Comparison with quicksort

AlgorithmAverage caseWorst caseTypical speed
QuicksortO(n log n)O(_n_²)~0.74 sec
Append-free quicksortO(n log n)O(_n_²)~0.53 sec
MergesortO(n log n)O(n log n)~1.4 sec

Mergesort is safe (no quadratic worst case) but typically slower than quicksort on random data. The choice of algorithm depends on the application: if worst-case behaviour matters (e.g., real-time systems or adversarial inputs), mergesort is preferable. If average-case performance is the priority and inputs are not expected to be pathologically ordered, quicksort is the better choice.