How do divide-and-conquer sorts achieve O(n log n), and how do merge sort and quicksort differ?
Explain and trace merge sort and quicksort as divide-and-conquer algorithms, analyse their complexity, and compare their trade-offs
A focused answer to the H2 Computing outcome on efficient sorts. Merge sort and quicksort as divide-and-conquer, their O(n log n) behaviour, quicksort's worst case, and the trade-off between stability, memory and speed.
Reviewed by: AI editorial process; not yet individually human-reviewed
Have a quick question? Jump to the Q&A page
Jump to a section
What this dot point is asking
SEAB wants you to explain and trace merge sort and quicksort as divide-and-conquer algorithms, analyse their complexity, and compare their trade-offs. The central idea is that splitting a problem into halves and combining the results turns an sort into - and that merge sort and quicksort make different choices about where the hard work happens.
The answer
Divide and conquer
Both sorts follow the same template: divide the list into smaller parts, conquer by sorting each part recursively, and combine the results. The base case is a list of 0 or 1 element, which is already sorted.
Merge sort
Merge sort splits the list in half, sorts each half recursively, then merges the two sorted halves:
def merge_sort(data):
if len(data) <= 1:
return data
mid = len(data) // 2
left = merge_sort(data[:mid])
right = merge_sort(data[mid:])
return merge(left, right)
def merge(left, right):
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i]); i += 1
else:
result.append(right[j]); j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
The list is halved times, and merging at each level costs in total, so the time is in all cases. It needs extra space for the merged output and is stable (equal elements keep their order).
Quicksort
Quicksort picks a pivot and partitions the list so smaller elements go left and larger go right; the pivot lands in its final place. It then recurses on each side:
def quicksort(data, low, high):
if low < high:
p = partition(data, low, high)
quicksort(data, low, p - 1)
quicksort(data, p + 1, high)
On average the partitions are balanced, giving . But if the pivot is repeatedly the smallest or largest element (for example a sorted list with a naive pivot), partitions are maximally unbalanced and it degrades to . Quicksort sorts in place ( extra space).
Comparing the trade-offs
| Property | Merge sort | Quicksort |
|---|---|---|
| Average time | ||
| Worst time | ||
| Extra space | ||
| Stable | Yes | Not usually |
Merge sort guarantees and stability but uses more memory; quicksort is usually faster in practice and in place, but has a quadratic worst case mitigated by good pivot choice.
Examples in context
Example 1. Sorting external data. When sorting a dataset too large to fit in memory, merge sort's predictable merging of sorted chunks (an external merge sort) is the natural fit, because it streams data and combines sorted runs without random access - the same merge step scaled up.
Example 2. Library sort functions. Many standard libraries use a hybrid: quicksort or a quicksort variant for speed and in-place operation, switching to insertion sort for small partitions and sometimes to a guaranteed method to dodge quicksort's worst case. The choice reflects exactly the trade-offs in the table above.
Try this
Q1. State the time complexity of merge sort in the worst case and explain why it is the same as its average case. [2 marks]
- Cue. - it always halves the list ( levels) and always does merging per level, regardless of input.
Q2. What input causes quicksort's worst case with a first-element pivot? [1 mark]
- Cue. An already-sorted (or reverse-sorted) list, which makes every partition maximally unbalanced.
Q3. Give one advantage of merge sort over quicksort. [1 mark]
- Cue. It guarantees in all cases and is stable (equal elements keep their relative order).
Exam-style practice questions
Practice questions written in the style of SEAB exam questions on this dot point, with worked answer explainers. The year tag is the paper they imitate, not the source.
Original6 marks(a) Explain how merge sort uses divide and conquer to sort a list. (b) State its time complexity and explain why the factor appears. (c) Trace the merge step that combines the two sorted halves and .Show worked answer →
(a) Merge sort splits the list into two halves, recursively sorts each half, then merges the two sorted halves into one sorted list. The base case is a list of length 0 or 1, which is already sorted.
(b) Time complexity is . The list is halved times (the depth of recursion), and at each level the total merging work across all sublists is , giving .
(c) Merge by repeatedly taking the smaller front element:
[2,5,8] [1,6] -> take 1 -> [1]
[2,5,8] [6] -> take 2 -> [1,2]
[5,8] [6] -> take 5 -> [1,2,5]
[8] [6] -> take 6 -> [1,2,5,6]
[8] [] -> take 8 -> [1,2,5,6,8]
Markers reward the split-sort-merge description, with the halving-depth-times-linear-merge reason, and a correct merge producing .
Original5 marks(a) Describe how quicksort partitions a list around a pivot. (b) State quicksort's average and worst-case time complexity, and explain what input causes the worst case. (c) Give one advantage of quicksort over merge sort.Show worked answer →
(a) Quicksort chooses a pivot element and partitions the list so that everything smaller than the pivot is to its left and everything larger is to its right. The pivot is then in its final position; quicksort recurses on the left and right partitions.
(b) Average case ; worst case . The worst case occurs when the pivot is consistently the smallest or largest element (for example, an already-sorted list with a naive first-element pivot), so partitions are maximally unbalanced - one side empty, the other nearly full - giving levels of work.
(c) Quicksort sorts in place, needing only extra space for recursion, whereas merge sort needs extra space for the merge. So quicksort is more memory-efficient.
Markers reward the pivot-and-partition description, the average and worst with the unbalanced-partition cause, and a valid advantage such as in-place sorting.
Related dot points
- Analyse the time and space complexity of an algorithm using Big-O notation, and compare common growth rates
A focused answer to the H2 Computing outcome on algorithmic complexity. Big-O notation, deriving the order of growth from loops and recursion, the common complexity classes, and the trade-off between time and space.
- Implement and trace linear search and binary search, state their complexities, and justify when each applies
A focused answer to the H2 Computing outcome on searching. Linear search and binary search, tracing each on an example, their O(n) and O(log n) complexities, and the precondition that binary search needs sorted data.
- Implement and trace bubble sort and insertion sort, analyse their complexity, and compare their behaviour on nearly sorted data
A focused answer to the H2 Computing outcome on elementary sorts. Bubble sort and insertion sort, tracing each on an example, their O(n squared) worst case, and why insertion sort excels on nearly sorted input.
- Define recursion in terms of base and recursive cases, trace recursive calls, and compare recursion with iteration
A focused answer to the H2 Computing outcome on recursion. Base and recursive cases, tracing the call stack, the divide-and-conquer pattern, and the trade-offs between recursion and iteration.
- Describe arrays and records as data structures, explain constant-time array indexing and contrast static with dynamic arrays
A focused answer to the H2 Computing outcome on arrays and records. Contiguous storage and constant-time indexing, one and two dimensional arrays, records as fields of mixed type, and static versus dynamic arrays.