COMP2521 1,778 words·9 min read

05. Elementary Sorting

Shared Setting and Invariants#

We sort an integer array in ascending order. The C routines take inclusive indices lo and hi; call them on a full nonempty array as sort(a,0,n-1). An empty range uses hi < lo and performs no accesses. A loop invariant is a fact true before and after each iteration. It explains how local steps accumulate a correct answer rather than merely describing what happens.

All three elementary sorts use constant auxiliary space. Their worst-case time is quadratic, but they differ in comparisons, movements, stability and adaptation. Those differences are meaningful for small or nearly ordered inputs.

Selection Sort: Choose the Next Minimum#

At position i, scan the remaining suffix for its smallest value and swap that value into i. Before iteration i, positions before i contain the smallest items in sorted order. Finding the smallest remaining item extends that prefix by one. Stop after placing the second-last item; the final one is already determined.

C
void selectionSort(int a[], int lo, int hi) {
    for (int i = lo; i < hi; i++) {
        int min = i;
        for (int j = i + 1; j <= hi; j++) {
            if (a[j] < a[min]) min = j;
        }
        swap(a, i, min);
    }
}

swap is the helper in Searching and Sorting. min stores an index, not the smallest value; otherwise the final swap would not know which position to exchange.

For original example [4,3,1,2]:

Position to fill Minimum in remaining suffix After exchange
0 1 at index 2 [1,3,4,2]
1 2 at index 3 [1,2,4,3]
2 3 at index 3 [1,2,3,4]

Watch the selected minimum change during the suffix scan; it is placed only after the whole suffix has been inspected.

Selection sort

Values: 4, 3, 1, 2. Sorting complete: every adjacent pair is in ascending order. Final values: 1, 2, 3, 4. Enable JavaScript to inspect each step.

Comparisons and Stability#

For n items, suffix comparisons number (n-1)+(n-2)+...+1 = n(n-1)/2. Already sorted input cannot avoid those scans, so best, average and worst time are all quadratic. There are n-1 swap calls, some exchanging a position with itself. It can therefore be attractive when writes are expensive compared with comparisons.

The lecture's duplicate example reveals instability. Label the two sixes to make the order visible:

Selection sort's long-distance exchange can reverse two equal sixes

[6_A,6_B,5,2] becomes [2,6_B,5,6_A], then [2,5,6_B,6_A]. The final equal-key order has reversed. Strictly choosing the first minimum does not rescue stability: the displaced item can jump over an equal item during the swap.

Practice. How many key comparisons does selection sort perform on six already sorted values? Could skipping self-swaps change the time class?

Note

- Answer
5+4+3+2+1=15. Skipping a self-swap avoids assignments, but the suffix scans remain. It changes movement counts and constants, not the quadratic comparison growth.

Note

Checkpoint
Selection grows a correct sorted prefix by finding each next minimum. Its comparisons are data-independent; few long-distance swaps give low movement counts but can destroy stability.

Modified Bubble Sort: Settle the Maximum#

Compare adjacent entries from left to right; exchange a pair only if the left one is larger. The maximum of the current unsorted range travels to its right end. The next pass excludes that settled entry. The lecture uses an early-exit flag: a pass with no exchanges proves the whole active range is already ordered. A traditional version that always completes every scheduled pass remains quadratic even on sorted input; the flag is what gives this modified version its linear best case.

C
#include <stdbool.h>

void bubbleSort(int a[], int lo, int hi) {
    for (int end = hi; end > lo; end--) {
        bool swapped = false;
        for (int j = lo; j < end; j++) {
            if (a[j] > a[j + 1]) {
                swap(a, j, j + 1);
                swapped = true;
            }
        }
        if (!swapped) break;
    }
}

During one pass, after comparing pair j,j+1, position j+1 holds the maximum seen so far in that pass. At the final pair, this is the maximum of the whole active prefix. The outer invariant is that the excluded suffix is sorted and at least as large as every remaining entry.

The first bubble pass moves 6 to the end of 4, 3, 6, 1, 2, 5

Read each row as the state after the highlighted pair is considered. On [4,3,6,1,2,5], swap 4,3, leave 4,6, then exchange 6 with 1, 2, and 5. The first pass finishes [3,4,1,2,5,6]. The next pass settles 5, and the third settles 4, leaving [1,2,3,4,5,6].

A pass with no swaps establishes sorted order and stops modified bubble sort

A further pass still matters: the algorithm does not magically know the array is sorted just because we can see it. The no-swap pass supplies that evidence. Watch the final confirming pass:

Bubble sort

Values: 4, 3, 6, 1, 2, 5. Sorting complete: every adjacent pair is in ascending order. Final values: 1, 2, 3, 4, 5, 6. Enable JavaScript to inspect each step.

Cost and Equal Keys#

On already sorted input, one pass makes n-1 comparisons and exits: linear time. On reverse input, every adjacent comparison swaps a pair and all shrinking passes are required, giving n(n-1)/2 comparisons and swaps: quadratic time. The slide analysis has a repeated n-1 label for the second pass; the actual code reduces the active end, so that pass has n-2 comparisons.

For uniformly random permutations of distinct keys, each pair is equally likely to be inverted. An inversion is a pair whose earlier element is larger than its later element. There are n(n-1)/2 pairs, half inverted on average, so expected inversions are n(n-1)/4. An adjacent out-of-order swap removes exactly one inversion. Thus expected swaps are quadratic, establishing quadratic expected time under this model.

Equal adjacent keys never swap because the condition is strict >. Equal items can change relative order only by crossing, and they cannot cross under that rule; bubble sort is stable. It is adaptive because the no-swap exit exploits order, although one badly positioned small entry can still require many passes.

Practice. What happens on [2_A,2_B,1]? Would changing > to >= preserve stability?

Note

- Answer
First pass leaves the equal twos, then exchanges 2_B with 1, giving [2_A,1,2_B]. Next pass gives [1,2_A,2_B]. With >=, equal neighbours could swap, reversing their original order; the stable guarantee disappears.

Note

Checkpoint
A bubble pass moves the active maximum to its final position. A no-swap pass proves order. Strict adjacent exchanges preserve ties; reverse input still requires a triangular amount of work.

Insertion Sort: Extend a Sorted Prefix#

Treat the first item as a sorted one-item prefix. Take the next item, hold it outside the array conceptually, shift larger preceding items right, and place the held value into the resulting gap. Before iteration i, entries lo..i-1 are sorted. After insertion, lo..i are sorted and contain the same items as before the iteration.

C
void insertionSort(int a[], int lo, int hi) {
    for (int i = lo + 1; i <= hi; i++) {
        int item = a[i];
        int j = i;
        while (j > lo && item < a[j - 1]) {
            a[j] = a[j - 1];
            j--;
        }
        a[j] = item;
    }
}

The bounds check comes first in the &&: C short-circuit evaluation avoids accessing a[j-1] when the gap reaches lo. Saving item is essential because shifting into a[i] overwrites its original value.

For the lecture example [4,1,7,3,8,6,5,2], inserting 1 shifts 4; inserting 7 shifts nothing; inserting 3 shifts 7 then 4, leaving [1,3,4,7,8,6,5,2].

Insertion of 3 into the already sorted prefix 1, 4, 7

The slide uses adjacent swaps to display movement, while its implementation uses a held value and shifts. Both produce the same order after each insertion, but a shift trace can temporarily show repeated stored integers. One copy is the value being held, not an additional logical item.

Watch the player lift the held item and track the gap. Its implementation matches the shift-based C above.

Insertion sort

Values: 4, 1, 7, 3, 8, 6, 5, 2. Sorting complete: every adjacent pair is in ascending order. Final values: 1, 2, 3, 4, 5, 6, 7, 8. Enable JavaScript to inspect each step.

The final insertion moves 2 left across all larger entries in the sorted prefix

The last 2 shifts 8,7,6,5,4,3 and stops after 1. It requires six shifts. That is useful evidence about disorder: “one remaining item” does not mean constant work.

Derive the Adaptive Bound#

If input is sorted, every next item fails its first movement comparison: n-1 comparisons, no shifts, linear time. If reversed, the item at position i moves past all i predecessors, giving 1+2+...+(n-1) shifts and quadratic time. Under uniformly random distinct permutations, expected inversions are quadratic, so expected time is quadratic too.

More precisely, shifting larger predecessors removes one inversion per shift, so running time is Θ(n+I) where I is the number of input inversions. The n term includes considering each next item even when I=0. This explains why insertion sort is particularly good for nearly sorted arrays. Equal predecessors are not shifted by the strict comparison, so ties retain their relative order.

Practice. In [1,2,3,4,0], how many shifts insert the final item? Is “mostly sorted” a sufficient performance description?

Note

- Answer
Four shifts move 4,3,2,1 right before placing 0. This array has four inversions and only one displaced value, so total insertion work remains linear. For a general claim, count the amount of disorder rather than assuming every nearly sorted-looking input has the same cost.

Practice. Why might insertion beat bubble sort although both have linear best-case and quadratic average/worst-case bounds?

Note

- Answer
Insertion directly moves each held item to its proper prefix position and stops its inner search as soon as the predecessor fits. Bubble sort may revisit many adjacent pairs across passes. Their growth classes omit constants and redundant checks; measured comparison/movement counts can distinguish them.

Choose With the Mechanism in Mind#

Sort Best Expected on uniform distinct permutations Worst Stable Key advantage
Selection Quadratic Quadratic Quadratic No Few exchanges
Modified bubble Linear Quadratic Quadratic Yes Simple pass-based early exit
Insertion Linear Quadratic Quadratic Yes Work tracks inversions

All three have constant auxiliary space for arrays. For large arbitrary data, merge sort and quicksort usually improve growth. For a tiny or nearly sorted range, insertion sort's simple loop can still be a sensible component of a faster hybrid.

More exam-style practice#

Work each prompt before opening its answer. State the invariant or cost assumption that makes your reasoning valid.

Question 1. Trace one full left-to-right bubble-sort pass on [4,1,3,2]. What invariant holds afterward?

Note

- Answer
Adjacent swaps give [1,4,3,2], then [1,3,4,2], then [1,3,2,4]. The maximum element 4 is at the right end and need not be visited on the next pass.

Question 2. Count the shifts insertion sort makes on [5,4,3,2,1]. How does that support its worst-case bound?

Note

- Answer
Inserting 4, 3, 2 and 1 shifts 1, 2, 3 and 4 elements, for 10 shifts. In general a reverse-sorted length-n array makes 1+...+(n-1) = n(n-1)/2 = Θ(n²) shifts.

Question 3. Modified bubble sort resets a swapped flag before each pass and stops after a pass with no swaps. How many comparisons does it make on an already sorted length-n array, and why is that not Θ(1)?

Note

- Answer
The first pass compares each adjacent pair once, making n-1 comparisons for n >= 2. It finds no inversion and stops, so best-case work is Θ(n), not Θ(1). Early termination avoids later passes but cannot know the array is sorted without examining the adjacent relationships.

Note

Checkpoint
Derive costs from shrinking suffixes, passes or shifts. Selection finds minima; bubble settles maxima; insertion extends a sorted prefix. The handling of ties and the exact stopping condition determine properties, not just the algorithm's name.