SEN2211 · Week 13 · Sorting Algorithms I

Sorting I: bubble, selection, insertion

Three basic O(n²) sorting algorithms, the state of the array after each pass, comparison and swap counts, and stability. The question “show the array after each pass” is almost guaranteed on the exam.

sortingbubble sortselection sortinsertion sortpasscomparisonswapshiftearly terminationin-placestable sortinversionadaptive

Sorting is the most studied problem in computer science: if you sort the data, search drops to O(log n), duplicates come side by side, and the smallest/largest are found immediately. This week there are three simple algorithms. All three are O(n²) and slow on large data, but their logic is intuitive and the exam asks for a pass-by-pass trace. Next week we move on to O(n log n) algorithms.

Golden rules:

  1. Bubble: in each pass the largest element “bubbles” to the end.
  2. Selection: in each pass the smallest of the remaining part is selected to the front.
  3. Insertion: in each pass the next element is inserted into its right place in the sorted part on the left.
  4. Watch the definition of “pass”. On this page a pass = one round of the outer loop.

Bubble sort

Compare two neighbouring elements; swap if the left one is larger. During one pass the largest element floats to the very end like a bubble. The next pass doesn’t need to look at the last element.

static void bubbleSort(int[] a) {
    int n = a.length;
    for (int pass = 1; pass < n; pass++) {
        boolean swapped = false;
        for (int j = 0; j < n - pass; j++) {
            if (a[j] > a[j + 1]) {
                int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t;
                swapped = true;
            }
        }
        if (!swapped) break;   // no swap means the array is sorted
    }
}

On {7, 3, 9, 1, 5}, after each pass:

pass 1: [3, 7, 1, 5, 9]
pass 2: [3, 1, 5, 7, 9]
pass 3: [1, 3, 5, 7, 9]
pass 4: [1, 3, 5, 7, 9]
comps=10 swaps=6 inversions(orig)=6

Inside pass 1, comparison by comparison:

[7, 3, 9, 1, 5]   7 > 3 swap  → [3, 7, 9, 1, 5]
                  7 < 9       → no change
                  9 > 1 swap  → [3, 7, 1, 9, 5]
                  9 > 5 swap  → [3, 7, 1, 5, 9]   9 settled in place

In pass 3 the array became sorted, but since a swap was made in that pass the algorithm can’t know it; pass 4 ends with no swaps and stops.

Inversion: a pair with i < j and a[i] > a[j]. Each adjacent swap fixes exactly one inversion; therefore the number of swaps of bubble sort = the number of inversions.

Selection sort

Scan the remaining part, find the index of the smallest, and swap it with the start of the remaining part. The prefix grows by one in every pass and is in its final state.

static void selectionSort(int[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        int min = i;
        for (int j = i + 1; j < a.length; j++)
            if (a[j] < a[min]) min = j;
        int t = a[i]; a[i] = a[min]; a[min] = t;
    }
}

{38, 12, 45, 7, 29, 16}:

pass 1: [7, 12, 45, 38, 29, 16]
pass 2: [7, 12, 45, 38, 29, 16]
pass 3: [7, 12, 16, 38, 29, 45]
pass 4: [7, 12, 16, 29, 38, 45]
pass 5: [7, 12, 16, 29, 38, 45]
comps=15 swaps=3

In passes 2 and 5 the smallest was already in place and no real swap happened (the counter counted only the swaps where min != i). The number of comparisons is independent of the input: always n(n−1)2\frac{n(n-1)}{2}. It makes 15 comparisons on a sorted array too.

PITFALL · Selection sort can’t stop early. The flag idea from bubble doesn’t work here; to find the minimum you have to scan all of the remaining part. The best case is also O(n²).

Insertion sort

Like arranging the cards in your hand in a card game: you take each new card and insert it in the right place among the sorted cards on the left. The larger cards shift one place to the right.

static void insertionSort(int[] a) {
    for (int i = 1; i < a.length; i++) {
        int key = a[i];
        int j = i - 1;
        while (j >= 0 && a[j] > key) {
            a[j + 1] = a[j];   // shift
            j--;
        }
        a[j + 1] = key;
    }
}

{38, 12, 45, 7, 29, 16}:

pass 1: [12, 38, 45, 7, 29, 16]
pass 2: [12, 38, 45, 7, 29, 16]
pass 3: [7, 12, 38, 45, 29, 16]
pass 4: [7, 12, 29, 38, 45, 16]
pass 5: [7, 12, 16, 29, 38, 45]
comps=12 shifts=9 inversions=9

Inside pass 3 (key = 7):

[12, 38, 45, 7, 29, 16]   key = 7
45 > 7 → shift     [12, 38, 45, 45, 29, 16]
38 > 7 → shift     [12, 38, 38, 45, 29, 16]
12 > 7 → shift     [12, 12, 38, 45, 29, 16]
j = -1, stop       [7, 12, 38, 45, 29, 16]

Insertion sort is adaptive: the more sorted the array, the faster. n − 1 comparisons on a sorted array, n(n−1)2\frac{n(n-1)}{2} on a reverse-sorted one:

insertion sorted: comps=5 shifts=0
insertion reverse: comps=15 shifts=15

Recognizing the algorithm from a pass

On the exam, an intermediate state is sometimes given and “which algorithm?” is asked. The same input {4, 2, 6, 1, 3}, after the first pass:

AlgorithmAfter pass 1Trace
Bubble[2, 4, 1, 3, 6]the largest (6) is at the end, the others moved one place
Selection[1, 2, 6, 4, 3]the smallest (1) is at the front, the old first (4) is in 1’s place
Insertion[2, 4, 6, 1, 3]the first two elements are sorted, the rest untouched

Stability

Elements with equal keys keeping their original order after sorting. When you sort a student list already sorted by name by grade, you want those with the same grade to remain sorted by name; this is possible only with a stable algorithm.

Records with keys 3, 3, 1 (the letters show the original order):

selection [1, 3b, 3a] insertion [1, 3a, 3b] bubble [1, 3a, 3b]

Selection sort does a distant swap: while bringing 1 to the front it throws 3a to the very end and makes it jump past 3b. Bubble and insertion swap only neighbours and only if strictly larger (>), so equal elements don’t pass each other.

Comparison table

Bubble (with flag)SelectionInsertion
BestO(n)O(n²)O(n)
AverageO(n²)O(n²)O(n²)
WorstO(n²)O(n²)O(n²)
Swaps / writesmany (= inversions)at most n − 1many (shifts)
Extra memoryO(1)O(1)O(1)
Stableyesnoyes
Adaptiveyesnoyes

Number of comparisons on random arrays (n doubles each time):

n=1000 bubble comps=498275 selection comps=499500 insertion comps=245712
n=2000 bubble comps=1996855 selection comps=1999000 insertion comps=985303
n=4000 bubble comps=7996674 selection comps=7998000 insertion comps=3980580

All grow x4: quadratic. Insertion sort makes half as many comparisons as the others on average, because each element stops in the middle of the sorted part on average.

Common mistakes

WrongCorrect
Bubble inner loop j < nj < n - pass (otherwise a[j + 1] goes out of bounds and there are unnecessary comparisons)
In selection, swapping every time a smaller one is foundFirst find the index of the minimum, one swap at the end of the pass
Not saving the key in insertionThe shifting overwrites a[i]; int key = a[i];
a[j] >= key in insertionIt breaks stability; >
Selection is fast on a sorted arrayAlways n(n−1)/2 comparisons
Selection is stableA distant swap can break the order of equal elements
The prefix of insertion is finalIt is sorted only within itself; elements slip in later
Bubble always makes n − 1 passesWith the flag it stops at a pass with no swaps

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

Bubble sort (adjacent swaps, largest element bubbles to the end) is applied to {7, 3, 9, 1, 5}. Show the array after pass 1 and after pass 2.

Show answer
pass 1: [3, 7, 1, 5, 9]; pass 2: [3, 1, 5, 7, 9]

Pass 1: (7,3) swap → 3 7; (7,9) fine; (9,1) swap; (9,5) swap → [3, 7, 1, 5, 9]. The largest, 9, has settled at the end. Pass 2 (excluding the last element): (3,7) fine; (7,1) swap; (7,5) swap → [3, 1, 5, 7, 9].

Question 2medium

For the same input {7, 3, 9, 1, 5}, how many comparisons and how many swaps does bubble sort (with the early-exit flag) make in total?

Show answer
10 comparisons, 6 swaps

At the end of pass 3 the array is sorted, but since there was a swap in that pass, pass 4 also runs and ends with no swaps. Comparisons 4 + 3 + 2 + 1 = 10. The number of swaps equals the number of inversions in the array (pairs with i < j and a[i] > a[j]): (7,3), (7,1), (7,5), (3,1), (9,1), (9,5) = 6.

Question 3easy

Selection sort (find the minimum of the unsorted part, swap it to the front) is applied to {38, 12, 45, 7, 29, 16}. Show the array after passes 1, 2 and 3.

Show answer
[7, 12, 45, 38, 29, 16], [7, 12, 45, 38, 29, 16], [7, 12, 16, 38, 29, 45]

Pass 1: the smallest, 7, is swapped with 38. Pass 2: the minimum of the remaining part, 12, is already in place, no change. Pass 3: the minimum of the rest, 16, is swapped with 45. A pass with no change doesn't stop selection sort.

Question 4medium

Insertion sort is applied to {38, 12, 45, 7, 29, 16}. Show the array after passes 1 to 3 (pass i inserts a[i] into the sorted prefix).

Show answer
[12, 38, 45, 7, 29, 16], [12, 38, 45, 7, 29, 16], [7, 12, 38, 45, 29, 16]

Pass 1: 12 goes in front of 38. Pass 2: 45 is already larger and stays in place (1 comparison). Pass 3: 7 shifts three elements right and goes to the front. The difference from selection sort: the sorted prefix is not final; small elements that come later can slip in between.

Question 5medium

How many comparisons does selection sort make on an array of 6 elements? Does it depend on the input order?

Show answer
15, no

In pass i the remaining n − i elements are scanned: 5 + 4 + 3 + 2 + 1 = 15 = n(n−1)2\frac{n(n-1)}{2}. It is the same even if the array is already sorted: the program counted 15 comparisons and 0 swaps on a sorted array too. Selection sort is insensitive to the input (non-adaptive).

Question 6medium

Bubble sort with the early-exit flag is run on an already sorted array of 6 elements. How many comparisons are made? What is its best-case Big-O?

Show answer
5, O(n)

In the first pass n − 1 = 5 comparisons are made, there are no swaps, the flag stays false and the algorithm stops. Best case O(n). Without the flag it would always make n(n−1)/2 = 15 comparisons.

Question 7medium

Insertion sort on 6 elements: how many comparisons for an already sorted array, and how many for a reverse-sorted array {6, 5, 4, 3, 2, 1}?

Show answer
5 and 15

In a sorted array each new element is compared once with the one on its left and stays in place: n − 1 = 5 (best case O(n)). In a reverse-sorted array the i-th element is compared i times and shifted to the front: 1 + 2 + 3 + 4 + 5 = 15, and the shifts are also 15 (worst case O(n²)). The number of shifts always equals the number of inversions.

Question 8hard

Records with keys 3a, 3b, 1 (the letters only mark the original order) are sorted by key. What does each algorithm produce, and which ones are stable?

Show answer
selection: 1 3b 3a (not stable); insertion and bubble: 1 3a 3b (stable)

In pass 1 selection sort finds the smallest, 1, and swaps it with the first element 3a: 1, 3b, 3a. The distant swap moved 3a behind 3b, and the order of the equal ones was broken. Insertion and bubble swap only neighbours and only if >, so equal elements keep their order. Stability matters, for example, when sorting first by name and then by grade.

Question 9medium

Write static void insertionSort(int[] a).

Show answer
For i = 1..n-1: key = a[i]; shift larger elements of a[0..i-1] right; place key.

static void insertionSort(int[] a) {
    for (int i = 1; i < a.length; i++) {
        int key = a[i];
        int j = i - 1;
        while (j >= 0 && a[j] > key) {
            a[j + 1] = a[j];   // shift the larger ones right
            j--;
        }
        a[j + 1] = key;        // place into the gap
    }
}
Saving key beforehand is a must; the shifting overwrites a[i]. If you write >= instead of a[j] > key, the algorithm becomes unstable.

Question 10easy

Write static void selectionSort(int[] a).

Show answer
For i = 0..n-2: find index of minimum in a[i..n-1]; swap with a[i].

static void selectionSort(int[] a) {
    for (int i = 0; i < a.length - 1; i++) {
        int min = i;
        for (int j = i + 1; j < a.length; j++)
            if (a[j] < a[min]) min = j;
        int t = a[i];
        a[i] = a[min];
        a[min] = t;
    }
}
It makes at most n − 1 swaps; this is an advantage for data where swapping is expensive (large records). On {38, 12, 45, 7, 29, 16}, 3 real swaps were made.

Question 11hard

Starting from {4, 2, 6, 1, 3}, an algorithm produced [2, 4, 6, 1, 3] after pass 1 and [2, 4, 6, 1, 3] after pass 2. Another produced [1, 2, 6, 4, 3] after pass 1. A third produced [2, 4, 1, 3, 6] after pass 1. Identify each.

Show answer
first: insertion, second: selection, third: bubble

Insertion: the first k + 1 elements are sorted among themselves but the rest is untouched (1, 3 are staying in place). Selection: the smallest, 1, came to the front, and 4 sits where it was swapped (index 3). Bubble: the largest, 6, went to the end and the others each moved one position. Hint: final minimum at the front → selection, final maximum at the end → bubble, locally sorted at the front with the rest unchanged → insertion.

Question 12easy

Give the best, average and worst case time complexity of bubble sort (with flag), selection sort and insertion sort.

Show answer
bubble O(n)/O(n^2)/O(n^2); selection O(n^2) in all cases; insertion O(n)/O(n^2)/O(n^2)

All three are in-place (O(1) extra memory). On random arrays the program counted comparisons: for n = 1000 → 2000 → 4000, selection 499 500 → 1 999 000 → 7 998 000 (x4), insertion about half of that (245 712 → 985 303 → 3 980 580), again x4. In practice insertion is the fastest; it is very good on small and nearly sorted arrays.

Verification: The three algorithms were run on the JDK in verify/SEN2211/W13.java, printing the array after every pass (java W13.java). The pass traces, the comparison/swap/shift counts and the inversion counts were taken from the program's counters. Stability was tested with labelled records with equal keys; growth was measured with comparison counts on random arrays of n = 1000, 2000, 4000.

Review card

BUBBLE: compare neighbours, swap if the left is larger; in each pass the LARGEST SETTLES AT THE END j < n - pass; if no swap, STOP (flag) → O(n) on sorted input number of swaps = number of inversions; STABLE SELECTION: find the MINIMUM of the remaining part, swap with a[i]; the prefix is FINAL comparisons ALWAYS n(n-1)/2; at most n-1 swaps; NOT STABLE (distant swap) INSERTION: save a[i] (key), shift the larger ones on the left right, put key in the gap the prefix is sorted but NOT final; sorted input n-1 comparisons O(n); reverse-sorted n(n-1)/2 number of shifts = number of inversions; STABLE (a[j] > key, NOT >=) All three: in-place O(1) memory, worst case O(n²) Recognizing a pass: final max at the end → bubble | final min at the front → selection | front locally sorted, rest untouched → insertion n=6: full scan 5+4+3+2+1 = 15 comparisons