gl/ripetizioni

Sorting algorithms: bubble, selection, insertion sort

Bubble, selection and insertion sort explained with a step-by-step trace on 6 numbers: pseudocode, Python, comparisons, swaps, O(n²) and worked exercises.

by Gaetano Livornese

Read the original in Italian

If you are in Year 3 of the liceo scientifico (Italian science-focused high school), applied sciences option, and in October in computer science you have finished (or are finishing) loops and arrays, sorting algorithms are the next step: they usually arrive between October and November, and the test that covers them falls six to nine weeks away. There is time to really understand them, instead of learning them by heart. If you are missing the basics on arrays, start from loops and arrays; for the cost of algorithms in general there is complexity and big-O notation, here I only touch on it intuitively.

The idea in one line each

We have to sort an array in increasing order. Three strategies, all based on comparisons and swaps:

  • Bubble sort: compares adjacent pairs and swaps them if they are in the wrong order. On each pass the largest value “rises” to the end, like a bubble.
  • Selection sort: on each pass it looks for the minimum of the part not yet sorted and moves it to the first free position.
  • Insertion sort: keeps the left part sorted and inserts, one at a time, the next element in the right place, like when you sort the cards in your hand.

A trace on an array of 6 elements

We will always use . Hand traces are what you are asked for in a test, so read them with a pencil in your hand.

Bubble sort

Pass 1 (5 comparisons): swap → ; swap → ; no; swap → ; swap → . The 6 is in its final place.

Pass 2 (4 comparisons): no; no; swap; swap → .

Pass 3 (3 comparisons): no; swap; swap → .

Pass 4 (2 comparisons): swap → ; no.

Pass 5 (1 comparison): no. Total: 15 comparisons and 9 swaps.

Selection sort

  • : the minimum of the whole array is 1 (position 4); swap with the 5 → .
  • : the minimum of is 2, already in place; no swap.
  • : the minimum of is 3; swap with the 4 → .
  • : the minimum of is 4; swap with the 6 → .
  • : the minimum of is 5, already in place.

Total: 15 comparisons and 3 swaps (at most ).

Insertion sort

Here elements are “shifted” to the right to make room, then the key is inserted.

  • : the 5 is larger, it shifts → (1 shift).
  • : the 5 shifts, the 2 does not → (1).
  • : the 5 is not larger than 6, so it stays where it is (0).
  • : 6, 5, 4, 2 shift, then 1 goes to the front → (4).
  • : 6, 5, 4 shift, the 2 does not → (3).

Total: 12 comparisons and 9 shifts. The 9 is no accident: it coincides with the number of swaps in bubble sort, because both remove the inversions one by one, that is, the pairs in which a larger element comes before a smaller one. In there are .

Pseudocode and Python

Bubble sort, in pseudocode:

for i from 0 to n-2:
    for j from 0 to n-2-i:
        if a[j] > a[j+1]:
            tmp = a[j]
            a[j] = a[j+1]
            a[j+1] = tmp

In Python the swap can be written in one line, but understanding the version with tmp matters in all the other languages:

def bubble_sort(a):
    n = len(a)
    for i in range(n - 1):
        for j in range(n - 1 - i):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
def selection_sort(a):
    n = len(a)
    for i in range(n - 1):
        m = i
        for j in range(i + 1, n):
            if a[j] < a[m]:
                m = j
        a[i], a[m] = a[m], a[i]
def insertion_sort(a):
    for i in range(1, len(a)):
        k = a[i]
        j = i - 1
        while j >= 0 and a[j] > k:
            a[j + 1] = a[j]
            j -= 1
        a[j + 1] = k

When you pass an array to a function that modifies it, remember how parameter passing works: I talk about it in pass by value and by reference.

Why they are O(n²) and which one to choose

All three have two nested loops. The outer loop runs about times and the inner one, on average, about : the comparisons are of the order of

For that makes 15, which is exactly the number we counted. If you double the comparisons become about four times as many: that is the intuitive meaning of .

Which to choose:

AlgorithmComparisonsSwaps or shiftsWhen it is worth it
bubblemanyalmost never; useful for learning
selection alwaysat most when writing is expensive
insertionfrom to equal to the inversionssmall or almost sorted arrays

In bubble sort you can add a check: if in a pass no swap happens, the array is already sorted and you stop. On an already sorted array it makes only comparisons.

The mistakes I see most

Wrong loop limits. In bubble sort the inner index goes up to (in Python range(n-1-i)) because inside the loop a[j+1] is read: with a limit one too high you run off the end of the array. In the same way the outer loop must not reach . In selection sort the inner loop starts from , not from , otherwise you compare the element with itself.

Swapping without a temporary variable. Writing a[j] = a[j+1] and straight after a[j+1] = a[j] copies the same value twice and the original value of a[j] is lost.

Confusing selection and bubble. Selection sort first looks for the minimum and then swaps once; bubble swaps during the scan.

Forgetting the shifting in insertion sort. If you don’t save the key before shifting the elements, you overwrite it.

Three worked exercises

1. The worst case of bubble sort

Count the comparisons and swaps of bubble sort on .

Pass 1: , , are three comparisons and three swaps → . Pass 2: and , two swaps → . Pass 3: , one swap → . Total: comparisons and swaps, that is, all 6 pairs are inversions. With the already sorted array and the early-exit check, on the other hand, 3 comparisons and 0 swaps.

2. How fast the cost grows

How many comparisons are needed to sort 10, 100 and 1000 elements with selection sort? And 2000?

For 2000: , about 4 times the 499,500 from before: doubling makes the cost quadruple. Multiplying by 10 multiplies the comparisons by about 100.

3. Find the mistake

This bubble sort in a language without multiple assignment has two mistakes. Which ones?

for j from 0 to n-1-i:
    if a[j] > a[j+1]:
        a[j] = a[j+1]
        a[j+1] = a[j]

First mistake: with up to , when you reach and read a[n], outside the array; the correct limit is . Second mistake: the swap. With , the first assignment gives and the second leaves it as : the 7 has vanished. You need tmp = a[j] before the first assignment and a[j+1] = tmp at the end.

Test yourself

To train on timed reasoning you can use the practice tests for high school. There is not yet a dedicated practice test for computer science: the questions you find cover maths and physics. A good way to prepare is to redo the three traces without looking, changing the array. More resources are on the computer science page; if you need a hand, I give 1-to-1 online tutoring and you can start with a free introductory call. Close to this topic there is also recursion, which usually follows sorting.

Frequently asked questions

What is the difference between bubble sort and selection sort?

Bubble sort keeps swapping adjacent pairs that are out of order, so it can make many swaps. Selection sort looks for the minimum of the unsorted part and makes at most one swap per pass.

Why are these algorithms O(n²)?

They have two nested loops: for n elements the comparisons are about n(n-1)/2, so doubling n makes the comparisons about four times as many.

Which sorting algorithm should you choose for an almost sorted array?

Insertion sort: if every element is already close to its position, each insertion needs very few comparisons and the cost gets close to that of a single scan.

How do you swap two values in a language without multiple assignment?

You need a temporary variable: save a in tmp, copy b into a, then tmp into b. Without tmp, after a = b the old value of a is lost and the two elements become equal.

Keep reading

Want to put what you read into practice?

Let's talk for 30 minutes, free: we look together at where you are stuck and I tell you honestly how I can help.

Or message me on WhatsApp.