6.2 Sorting Algorithms: Tracing, Comparisons, Correctness, and Efficiency
Key Takeaways
- Selection sort always makes n(n − 1)/2 comparisons and at most n − 1 swaps; after pass k, the k smallest values are in their final positions.
- Insertion sort makes only n − 1 comparisons on already-sorted data but n(n − 1)/2 on reverse-sorted data, so it is fast on nearly sorted lists.
- After each bubble sort pass, the largest remaining value has moved to the end of the unsorted part; a swap flag lets the algorithm stop early when a pass makes no swaps.
- Merge sort runs in O(n log n) in every case and is stable but needs O(n) extra memory; quicksort averages O(n log n) but degrades to O(n²) when pivots split the data badly.
- A stable sort keeps records with equal keys in their original relative order, which matters when you sort by one field after another.
What this competency asks
ETS asks you to understand searching and sorting algorithms and to analyze sorting algorithms for correctness. The discussion questions ask you to describe common comparison-based sorts, such as insertion sort and selection sort, and to analyze the number of comparisons. ETS's sample question on this topic shows an unfamiliar sorting procedure and asks for the array after six iterations of its loop. The core skill is careful tracing, not memorized names.
Selection sort
On each pass, find the smallest value in the unsorted part and swap it into the first unsorted position.
void selectionSort ( int[ ] a, int n )
for ( int i ← 0; i < n - 1; i ← i + 1 )
int minIndex ← i
for ( int j ← i + 1; j < n; j ← j + 1 )
if ( a[j] < a[minIndex] )
minIndex ← j
end if
end for
swap ( a, i, minIndex ) // exchanges a[i] and a[minIndex]
end for
end selectionSort
Trace on {29, 10, 14, 37, 13}:
| After pass | Smallest found | Array |
|---|---|---|
| 1 (i = 0) | 10 at index 1 | {10, 29, 14, 37, 13} |
| 2 (i = 1) | 13 at index 4 | {10, 13, 14, 37, 29} |
| 3 (i = 2) | 14 at index 2 (no change) | {10, 13, 14, 37, 29} |
| 4 (i = 3) | 29 at index 4 | {10, 13, 14, 29, 37} |
Comparisons: 4 + 3 + 2 + 1 = 10 = n(n − 1)/2 for n = 5. Selection sort always makes this many comparisons, even on sorted data. It does at most n − 1 swaps, which is useful when writing to memory is expensive.
Insertion sort
Keep a sorted prefix. Take the next value (the "key"), shift larger values in the prefix one place right, and insert the key into the gap.
void insertionSort ( int[ ] a, int n )
for ( int i ← 1; i < n; i ← i + 1 )
int key ← a[i]
int j ← i - 1
while ( ( j ≥ 0 ) and ( a[j] > key ) )
a[j + 1] ← a[j]
j ← j - 1
end while
a[j + 1] ← key
end for
end insertionSort
Trace on {5, 2, 4, 6, 1}:
| i | key | Comparisons made (a[j] > key) | Array after insertion |
|---|---|---|---|
| 1 | 2 | 5 > 2 | {2, 5, 4, 6, 1} |
| 2 | 4 | 5 > 4, 2 > 4 (false) | {2, 4, 5, 6, 1} |
| 3 | 6 | 5 > 6 (false) | {2, 4, 5, 6, 1} |
| 4 | 1 | 6 > 1, 5 > 1, 4 > 1, 2 > 1 | {1, 2, 4, 5, 6} |
That is 1 + 2 + 1 + 4 = 8 value comparisons. On an already-sorted array, each key needs one comparison: n − 1 total, O(n). On a reverse-sorted array, it needs n(n − 1)/2. Insertion sort is the best simple choice for nearly sorted data.
The condition ( j ≥ 0 ) and ( a[j] > key ) relies on checking j ≥ 0 first, so the array is never indexed at −1. This is short-circuit evaluation (Section 7.4).
Bubble sort
Compare adjacent pairs and swap any that are out of order. Each pass carries the largest remaining value to the end.
One pass on {5, 1, 4, 2, 8}: swap 5 and 1 → {1, 5, 4, 2, 8}; swap 5 and 4 → {1, 4, 5, 2, 8}; swap 5 and 2 → {1, 4, 2, 5, 8}; 5 and 8 stay. After pass 1: {1, 4, 2, 5, 8}, and 8 is in its final place.
With a swapped flag, bubble sort stops after a pass with no swaps. That gives n − 1 comparisons, O(n), on sorted data. Its worst case is n(n − 1)/2 comparisons and as many swaps, O(n²).
Divide-and-conquer sorts
Merge sort splits the array in half, sorts each half recursively, and merges the two sorted halves in linear time. The array can be halved about log₂ n times, and each level of merging does O(n) work, so the total is O(n log n) in every case. It is stable (when merging equal values, it takes from the left half first), but it needs O(n) extra memory for merging.
Quicksort picks a pivot, partitions the array so smaller values come before the pivot and larger ones after, and then recursively sorts each side. Its average is O(n log n) and it sorts in place, apart from its recursion stack. If the pivot is always the smallest or largest value (for example, taking the first element as the pivot on data that is already sorted), each partition removes only one element and the time degrades to O(n²). A random pivot or a median-of-three choice makes that unlikely. Standard quicksort is not stable.
Stability
A sort is stable if records with equal keys keep their original relative order. Suppose a class list is already alphabetical and you sort it by grade level. A stable sort keeps each grade's students alphabetical. An unstable sort may not.
Summary table
| Algorithm | Best | Average | Worst | Extra space | Stable? |
|---|---|---|---|---|---|
| Selection sort | O(n²) | O(n²) | O(n²) | O(1) | No (standard version) |
| Insertion sort | O(n) | O(n²) | O(n²) | O(1) | Yes |
| Bubble sort (with flag) | O(n) | O(n²) | O(n²) | O(1) | Yes |
| Merge sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes |
| Quicksort | O(n log n) | O(n log n) | O(n²) | O(log n) average (recursion) | No |
Analyzing an unfamiliar sort for correctness
When a question shows a sorting procedure you have never seen:
- Trace a small input exactly as written, keeping a table of the array and the index variables after each loop iteration.
- Check the loop bounds. An inner loop that stops one element early leaves the last element unsorted. Using
≤ nindexes past the end. - Check the comparison direction.
<versus>decides ascending versus descending order. - Test edge cases: an empty array, one element, duplicates, and already-sorted and reverse-sorted input.
- Count what the question asks for: passes, iterations of a specific loop, comparisons, or swaps. These are different quantities.
Selection sort (find the minimum of the unsorted part and swap it into place) is applied to {29, 10, 14, 37, 13}. What is the array after the first two passes?
How many comparisons does selection sort make on an array of 6 elements?
A list of employees is already sorted by hire date. It must now be sorted by department so that, within each department, employees stay in hire-date order. Which algorithm guarantees this?
A quicksort implementation always chooses the first element of the current subarray as the pivot. What is its running time on an array that is already sorted in ascending order?