You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Divide and conquer is one of the most powerful and widely-used algorithm-design paradigms in Computer Science. Rather than attacking a large problem head-on, it divides the problem into smaller sub-problems of the same type, conquers each sub-problem recursively (dividing further until the pieces are trivially small), and finally combines the sub-solutions into a solution for the original. Two of the most important algorithms on the specification — binary search and merge sort — are instances of this single idea, and recognising the shared structure is what lets you reason about both with the same tools. At A-Level you must understand the divide/conquer/combine framework, identify which algorithms use it, derive the resulting complexity (often via a recurrence relation), and distinguish it sharply from the related paradigm of dynamic programming.
This lesson addresses AQA A-Level Computer Science (7517), Section 4.3 Fundamentals of algorithms. Divide and conquer is the unifying paradigm behind binary search (4.3.4) and merge sort (4.3.5), and the recursive structure it depends on is the recursion material of 4.1. The complexity analysis — counting work per level of the recursion tree and forming a recurrence relation — applies the Big-O vocabulary of 4.4.4. The contrast with dynamic programming and greedy approaches situates the paradigm within the broader landscape of algorithm-design techniques examined across the 4.3 strand.
Every divide-and-conquer algorithm is built from three phases:
| Step | Description | Example (merge sort) |
|---|---|---|
| Divide | Split the problem into one or more smaller sub-problems of the same kind | Split the array into two halves |
| Conquer | Solve each sub-problem recursively (the recursion bottoms out at a base case) | Recursively sort each half |
| Combine | Assemble the sub-solutions into a solution to the original problem | Merge the two sorted halves into one sorted array |
The recursion terminates at a base case — a sub-problem small enough to solve directly without further division. For merge sort the base case is an array of length 0 or 1, which is already sorted by definition; for binary search it is an empty search range, meaning the target is absent. Every recursive algorithm must have a reachable base case, or it recurses forever — a connection back to the recursion fundamentals of 4.1.
The three steps are not all equally costly, and which step dominates determines the algorithm's complexity. In binary search the combine step is trivial (nothing to merge — you simply return the answer found in one half) and the divide discards half the data; in merge sort the divide is trivial (split at the midpoint) but the combine — merging — does real O(n) work. Identifying the expensive step is the first move in any complexity analysis.
| Algorithm | Divide | Conquer | Combine | Complexity |
|---|---|---|---|---|
| Binary search | Inspect the middle, discard the half that cannot contain the target | Search the surviving half | Trivial — return the result | O(logn) |
| Merge sort | Split the array into two equal halves | Recursively sort each half | Merge the two sorted halves | O(nlogn) |
| Quick sort | Partition around a pivot into "smaller" and "larger" groups | Recursively sort each group | Trivial — partitions are already in place | O(nlogn) average, O(n2) worst |
The contrast between merge sort and quick sort is instructive: they place the expensive work in different phases. Merge sort divides cheaply and combines expensively (the merge); quick sort divides expensively (the partition) and combines for free (the two halves are already correctly positioned around the pivot). Both achieve O(nlogn) on average, but the location of the work explains their different behaviours — merge sort's guaranteed O(nlogn) versus quick sort's O(n2) worst case when partitions are badly unbalanced.
The recurring pattern "divide a problem of size n into two halves and do O(n) work to combine" yields O(nlogn), and it is worth seeing exactly why through the recursion tree.
| Level | Number of sub-problems | Size of each | Total work at this level |
|---|---|---|---|
| 0 | 1 | n | O(n) |
| 1 | 2 | n/2 | 2×O(n/2)=O(n) |
| 2 | 4 | n/4 | 4×O(n/4)=O(n) |
| 3 | 8 | n/8 | 8×O(n/8)=O(n) |
| ⋮ | ⋮ | ⋮ | ⋮ |
| log2n | n | 1 | n×O(1)=O(n) |
Two observations make the complexity fall out. First, the work at every level sums to O(n): although there are more sub-problems lower down, each is proportionally smaller, and the merging cost across an entire level always re-touches all n elements once. Second, the number of levels is log2n, because the sub-problem size halves at each step and the number of halvings needed to reduce n to 1 is exactly log2n. Multiplying:
work per levelO(n)×number of levelslog2n=O(nlogn).
Contrast this with binary search, whose tree is a single path rather than a branching tree: it makes only one recursive call per level (it discards the other half entirely rather than recursing into it), so each level does O(1) work and there are still log2n levels, giving O(logn).
A recurrence relation expresses an algorithm's running time T(n) in terms of its running time on smaller inputs, capturing the divide/conquer/combine structure directly. It is the formal counterpart of the recursion-tree picture.
For merge sort, the relation is
T(n)=2T(2n)+O(n),T(1)=O(1).
Read this aloud: "to sort n items, sort two halves of size n/2 each (the 2T(n/2) term, from the two recursive calls), then do O(n) work to merge them". Unrolling the recurrence reproduces the recursion-tree table above and resolves to T(n)=O(nlogn).
For binary search, only one half is searched, and the combine cost is constant:
T(n)=T(2n)+O(1),T(1)=O(1).
"To search n items, search one half of size n/2, plus a constant amount of work (one comparison)." This unrolls to T(n)=O(logn) — the single-path tree.
The difference between the two recurrences is precisely the coefficient of the recursive term: 2 sub-problems (merge sort) versus 1 (binary search), and an O(n) versus O(1) combine. Those two numbers are exactly the parameters the Master Theorem below formalises.
Divide and conquer is not only about searching and sorting. A clean illustration is computing xn (x raised to the power n).
FUNCTION power(x, n)
result = 1
FOR i = 1 TO n
result = result * x
END FOR
RETURN result
END FUNCTION
This performs n multiplications: linear in the exponent.
The insight is that xn=(xn/2)2 when n is even — so computing one half-power and squaring it costs one recursive call plus one multiplication, halving the exponent each time.
FUNCTION fastPower(x, n)
IF n = 0 THEN
RETURN 1
END IF
IF n is even THEN
half = fastPower(x, n DIV 2)
RETURN half * half
ELSE
RETURN x * fastPower(x, n - 1)
END IF
END FUNCTION
Trace of fastPower(2, 8) (each indented line is one level deeper into the recursion):
| Call | n | Even/odd | Action | Returns |
|---|---|---|---|---|
| fastPower(2, 8) | 8 | even | half = fastPower(2, 4) | 16×16=256 |
| fastPower(2, 4) | 4 | even | half = fastPower(2, 2) | 4×4=16 |
| fastPower(2, 2) | 2 | even | half = fastPower(2, 1) | 2×2=4 |
| fastPower(2, 1) | 1 | odd | 2× fastPower(2, 0) | 2×1=2 |
| fastPower(2, 0) | 0 | base | return 1 | 1 |
Reading the Returns column from the bottom up: 1→2→4→16→256. The result 28=256 is obtained with only 4 multiplications rather than 8. The saving is dramatic at scale: for n=1,000,000 the naive method needs a million multiplications, while fast exponentiation needs only about log21,000,000≈20. The recurrence is T(n)=T(n/2)+O(1), the same shape as binary search, giving O(logn).
For a divide-and-conquer recurrence of the standard form
T(n)=aT(bn)+O(nd),
where
the complexity is determined by comparing d with logba:
| Condition | Complexity | Intuition |
|---|---|---|
| d>logba | O(nd) | the combine step dominates (work concentrated at the top) |
| d=logba | O(ndlogn) | work is evenly spread across all levels |
| d<logba | O(nlogba) | the leaves dominate (work concentrated at the bottom) |
| Algorithm | a | b | d | logba | Result |
|---|---|---|---|---|---|
| Binary search | 1 | 2 | 0 | 0 | d=logba → O(logn) |
| Merge sort | 2 | 2 | 1 | 1 | d=logba → O(nlogn) |
| Naive recursive matrix multiply | 8 | 2 | 2 | 3 | d<logba → O(n3) |
Exam Tip: You are not required to memorise or apply the Master Theorem formula at A-Level. You are expected to explain why binary search is O(logn) and merge sort is O(nlogn) from the structure of their recursion — the recursion-tree and recurrence-relation reasoning above. Treat the Master Theorem as a confirming check, not the primary argument.
This is the most heavily examined comparison in the lesson, so it deserves care. Both paradigms break a problem into sub-problems, but they differ in whether those sub-problems overlap.
The classic illustration is the Fibonacci numbers. A naive divide-and-conquer-style recursion computes fib(n)=fib(n−1)+fib(n−2), but the two branches re-compute the same sub-problems over and over — fib(n−2) is calculated by both the fib(n−1) branch and directly, and so on, giving an exponential O(2n) blow-up. Because the sub-problems overlap, divide and conquer is the wrong tool. Dynamic programming computes each fib(k) once and stores it, collapsing the cost to O(n). The diagnostic question is therefore always: do the sub-problems overlap? If no, divide and conquer; if yes, dynamic programming.
| Paradigm | Sub-problems | Key technique | Example |
|---|---|---|---|
| Divide and conquer | Independent (disjoint) | Recurse and combine | Merge sort, binary search |
| Dynamic programming | Overlapping | Solve once, store, reuse | Fibonacci, shortest-path tables |
| Greedy | One choice extends the partial solution | Take the locally optimal option each step | Dijkstra's, Prim's |
| Brute force | All possibilities enumerated | Exhaustive search | Subset-sum by trying every subset |
| Suitable when… | Not suitable when… |
|---|---|
| The problem decomposes into independent sub-problems of the same type | Sub-problems overlap heavily — use dynamic programming instead |
| The combine step is efficient (cheap relative to the sub-problems) | The combine step is so expensive it dominates and erodes the benefit |
| The recursion depth is manageable (no stack-overflow risk) | The problem does not shrink with each division (no progress towards a base case) |
| A balanced split is achievable | Splits are wildly unbalanced (e.g. quick sort's O(n2) worst case) |
head + sum(tail)) is recursion but not really divide and conquer — there is no genuine division into multiple sub-problems.Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.