Divide and Conquer: Merge Sort

Divide and conquer

Divide and conquer means: split a big problem into smaller pieces, solve the pieces, then combine the answers. Merge sort is the classic example:

  • Divide – cut the list in half.
  • Conquer – sort each half (by calling merge sort again – recursion!).
  • Combine – merge the two sorted halves into one sorted list.

Think of it like this: Sorting a huge pile of exam papers with friends: split the pile, each friend sorts a small pile, then you merge the sorted piles by always taking the paper with the smaller roll number from the top.

def merge(left, right):
    out = []
    i = j = 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:]

def merge_sort(nums):
    if len(nums) <= 1:                 # base case: already sorted
        return nums
    mid = len(nums) // 2
    return merge(merge_sort(nums[:mid]), merge_sort(nums[mid:]))

print(merge_sort([38, 27, 43, 3, 9, 82, 10]))

Output

[3, 9, 10, 27, 38, 43, 82]

Why O(n log n)?

The list can be halved about log₂ n times before the pieces have one item each. At every level, merging all the pieces together touches each of the n items once. So: n work × log n levels = O(n log n).

nn² (simple sorts)n log n (merge sort)
1,0001,000,000about 10,000
1,000,0001,000,000,000,000about 20,000,000

Merge sort is always O(n log n) and it's stable, but it needs O(n) extra memory for the merged lists.

HOME LABS COMMUNITY DASHBOARD