Insertion Sort and Merge Sort
Two more powerful sorting algorithms. Insertion sort builds a sorted section one element at a time. Merge sort uses a divide-and-conquer strategy to sort in O(n log n) - dramatically better than bubble sort for large lists.
Bubble sort works, but it is slow for large lists. Its O(n2) worst case means that doubling the list size quadruples the time. For a list of 1,000,000 items, bubble sort would need up to a trillion operations. Insertion sort is still O(n2) in the worst case, but it is significantly more efficient in practice for nearly-sorted data. Merge sort solves the problem entirely with O(n log n) - fast enough to sort a million items in about 20 million operations instead of a trillion.
Insertion sort maintains a sorted sub-list at the left of the array. At the start, the sorted sub-list contains just the first element (a single element is trivially sorted). The algorithm then takes each subsequent element - the key - and inserts it into the correct position in the sorted sub-list by shifting larger elements one position to the right.
This is exactly how most people sort a hand of cards: the sorted sub-list is the cards you are already holding in order, and the key is the next card you pick up.
Trace insertion sort on [5, 2, 8, 1, 9]:
| Step | Key | Sorted sub-list | Action | List after |
|---|---|---|---|---|
| Start | - | [5] | First element is trivially sorted | [5, 2, 8, 1, 9] |
| i=1 | 2 | [5] | 2 < 5 - shift 5 right, insert 2 | [2, 5, 8, 1, 9] |
| i=2 | 8 | [2,5] | 8 > 5 - insert 8 at end | [2, 5, 8, 1, 9] |
| i=3 | 1 | [2,5,8] | 1 < 8, 1 < 5, 1 < 2 - shift all three, insert 1 | [1, 2, 5, 8, 9] |
| i=4 | 9 | [1,2,5,8] | 9 > 8 - insert at end | [1, 2, 5, 8, 9] |
Bold = confirmed sorted portion. The list is fully sorted after 4 insertions (n-1 insertions for n elements).
// Insertion sort - ascending order (AQA) SUBROUTINE insertionSort(myList) n ← LEN(myList) FOR i ← 1 TO n - 1 key ← myList[i] // Element to insert j ← i - 1 WHILE j >= 0 AND myList[j] > key myList[j+1] ← myList[j] // Shift right j ← j - 1 ENDWHILE myList[j+1] ← key // Insert key in correct position ENDFOR RETURN myList ENDSUBROUTINE
# Insertion sort - ascending order (OCR) procedure insertionSort(myList) n = len(myList) for i = 1 to n - 1 key = myList[i] j = i - 1 while j >= 0 and myList[j] > key myList[j+1] = myList[j] j = j - 1 end while myList[j+1] = key next i return myList endprocedure
Key exam fact: insertion sort has O(n2) worst case (reverse sorted input) but O(n) best case (already sorted input - the inner WHILE loop never executes). This makes it significantly better than bubble sort on nearly-sorted data. Exam questions often ask you to show the list after inserting each key element - always show the shifting of elements to the right, not just the final position.
Green = sorted sub-list. Orange = key element being inserted. Purple = element being compared to the key.
Merge sort is a divide-and-conquer algorithm. It works in two phases:
Phase 1 - Divide: Repeatedly split the list in half until you have sub-lists of length 1. A list of one element is trivially sorted.
Phase 2 - Merge: Repeatedly merge pairs of sorted sub-lists into larger sorted lists. When merging two sorted lists, always take the smallest remaining element from the front of either list.
Sorting [38, 27, 43, 3, 9, 82, 10]:
The key to merge sort is the merge step: given two sorted lists, produce one sorted list. Compare the first element of each list; take whichever is smaller and add it to the result. Repeat until one list is empty, then append the remainder of the other list.
Compare 3 and 10: 3 is smaller, take 3. Result: [3]
Compare 9 and 10: 9 is smaller, take 9. Result: [3, 9]
First list is empty. Append remainder of second list: [10, 82]
Final merged result: [3, 9, 10, 82]
Both input lists were sorted, so the comparison always correctly identifies the next-smallest element.
// Merge two sorted sub-lists into one sorted list (AQA) FUNCTION merge(left, right) result ← [] WHILE LEN(left) > 0 AND LEN(right) > 0 IF left[0] <= right[0] THEN result.append(left[0]) left ← left[1:] // Remove first element ELSE result.append(right[0]) right ← right[1:] ENDIF ENDWHILE result ← result + left + right // Append any remaining RETURN result ENDFUNCTION
# Merge two sorted lists (OCR) function merge(left, right) result = [] while len(left) > 0 and len(right) > 0 if left[0] <= right[0] then result.append(left[0]) left = left[1:] else result.append(right[0]) right = right[1:] end if end while result = result + left + right return result endfunction
Merge sort exam questions often ask you to: (1) Show the divide stage for a given list. (2) Trace the merge of two given sorted lists. (3) State why merge sort is more efficient than bubble sort. For (3), the answer is complexity: merge sort is O(n log n) in all cases, bubble sort is O(n2) worst case. For a list of 100 items, that is ~665 vs ~5000 operations.
| Algorithm | Best case | Worst case | Avg case | Requires sorted? | Good for |
|---|---|---|---|---|---|
| Bubble sort | O(n) * | O(n2) | O(n2) | No | Education, small lists, nearly-sorted data |
| Insertion sort | O(n) | O(n2) | O(n2) | No | Nearly-sorted data, small lists, online sorting |
| Merge sort | O(n log n) | O(n log n) | O(n log n) | No | Large lists, stable sort required, predictable performance |
| Binary search * | O(1) | O(log n) | O(log n) | Yes | Searching sorted lists efficiently |
* Bubble sort best case is O(n) only with early termination. * Binary search is a searching algorithm, not a sorting algorithm - included for reference.
Both are O(n2) in the worst case, but insertion sort is generally preferred over bubble sort for the following reasons:
1. Insertion sort has O(n) best case (already sorted list). Bubble sort also has O(n) best case with early termination, but without it, runs O(n2).
2. Insertion sort makes approximately half as many comparisons as bubble sort on random data in practice.
3. Insertion sort is an "online" algorithm - it can sort data as it arrives, one element at a time. Bubble sort requires all data to be present first.
Test yourself
1. After inserting the 3rd element during insertion sort on [7, 3, 5, 1, 8], what does the sorted sub-list contain?
2. In the divide phase of merge sort on [4, 7, 2, 1], what are the final sub-lists before merging begins?
3. What is the worst-case complexity of merge sort?
4. When merging two sorted lists [2, 5, 9] and [3, 6, 8], what are the first two elements added to the result?
5. Which sorting algorithm is most efficient for sorting a list that is already nearly in order?
Merge sort is described as a "divide and conquer" algorithm. Explain what this means in the context of merge sort, and identify one advantage and one disadvantage of merge sort compared to insertion sort.
Advantage of merge sort over insertion sort: O(n log n) worst case vs O(n2). For large lists, merge sort is dramatically faster. Sorting 1,000,000 items: merge sort needs ~20,000,000 operations; insertion sort needs up to 1,000,000,000,000 (a trillion) in the worst case.
Disadvantage of merge sort: Merge sort requires additional memory to store the merged sub-lists (O(n) extra space). Insertion sort is an in-place algorithm - it only needs the space of the original list plus one key variable. For very memory-constrained environments, insertion sort may be preferred.
Practice what you have learned
Three levelled worksheets. Download, print and complete offline.