Knowledge of selection sort is not required to understand insertion sort. This resource makes references selection sort to contrast the algorithms. Insertion sort is slightly more complex than selection sort and is often studied after selection sort.

Insertion sort algorithm & trace

Insertion sort logically partitions the array into 2 parts. The pipe character (|) represents the partition in this example. The partition is a logical structure. Nothing is physically inserted into the array.

The properties below are true before and after each pass (though not necessarily in the middle of each pass). More formally, the conditions below are the loop invariant for insertion sort.

With each pass, insertion sort inserts the first element in the unchecked part into its correct position in the sorted part. Each element already in the sorted part that must be moved is copied one spot to the right.

Before the first pass

[37 | 32 70 15 93 40 63 3 40 63]

The partition starts after the first element. The properties above are both true. There is only 1 element to the left of the |, so the left side is sorted.

Unlike with selection sort, the elements in the sorted part are not necessarily in their final positions.

Pass 1: inserting 32

Before finding insertion position

[37 | 32 70 15 93 40 63 3 40 63]

elementToInsert: 32

The first element in the unchecked part (32) is copied into the variable elementToInsert. The actual value of the element is copied, not the index.

After finding insertion position but before insertion

[37 | 37 70 15 93 40 63 3 40 63]
  ^
  insertion position

elementToInsert: 32

elementToInsert is to be inserted into its correct position in the sorted part. Moving from right to left, elementToInsert is compared against each element in the sorted part until the correct position is found. If elementToInsert is less than the element in the sorted part, the element in the sorted part is copied one spot to the right to make room for elementToInsert. The elements are not swapped.

In the example, 37 is copied one spot to the right.

The insertion position is found because index 0 is reached.

After insertion

[32 37 | 70 15 93 40 63 3 40 63]
  ^
  insertion position

elementToInsert: 32

elementToInsert is copied to the insertion position, replacing the element originally at that position.

The sorted part is 1 larger than it was before the pass. The properties above are again true.

In the example, 32 replaces 37.

Pass 2: inserting 70

Before finding insertion position

[32 37 | 70 15 93 40 63 3 40 63]

elementToInsert: 70

70 is the first element in the unchecked part and is copied into elementToInsert.

After finding insertion position but before insertion

[32 37 | 70 15 93 40 63 3 40 63]
          ^
          insertion position

elementToInsert: 70

The insertion position is found because 70 is not less than 37.

70 is already in its correct position. This is not a special case and no additional code is required to handle it.

After insertion

[32 37 70 | 15 93 40 63 3 40 63]
        ^
        insertion position

elementToInsert: 70

Insertion sort copies elementToInsert to the insertion position as with any other pass. In this case, it replaces 70 with 70.

Pass 3: inserting 15

Before finding insertion position

[32 37 70 | 15 93 40 63 3 40 63]

elementToInsert: 15

After finding insertion position but before insertion

[32 32 37 | 70 93 40 63 3 40 63]
  ^
  insertion position

elementToInsert: 15

The elements 70, 37, and 32 are each copied one spot to the right, in that order.

The insertion position is found because index 0 is reached.

After insertion

[15 32 37 70 | 93 40 63 3 40 63]
  ^
  insertion position

elementToInsert: 15

The first copy of 32 is replaced with 15.

Pass 4: inserting 93

Before finding insertion position

[15 32 37 70 | 93 40 63 3 40 63]

elementToInsert: 93

After finding insertion position but before insertion

[15 32 37 70 | 93 40 63 3 40 63]
                ^
                insertion position

elementToInsert: 93

The insertion position is found because 93 is not less than 70.

After insertion

[15 32 37 70 93 | 40 63 3 40 63]
              ^
              insertion position

elementToInsert: 93

93 is replaced with 93. Replacing the element with a copy of itself is faster and less error prone than checking if the replacing is necessary.

Pass 5: inserting 40

Before finding insertion position

[15 32 37 70 93 | 40 63 3 40 63]

elementToInsert: 40

After finding insertion position but before insertion

[15 32 37 70 70 | 93 63 3 40 63]
           ^
           insertion position

elementToInsert: 40

The elements 93 and 70 are each copied one spot to the right, in that order.

The insertion position is found because 40 is not less than 37.

After insertion

[15 32 37 40 70 93 | 63 3 40 63]
           ^
           insertion position

elementToInsert: 40

The 70 originally at the insertion position is replaced with 40.

Remaining passes

[15 32 37 40 70 93 | 63 3 40 63]
[15 32 37 40 63 70 93 | 3 40 63]
[3 15 32 37 40 63 70 93 | 40 63]
[3 15 32 37 40 40 63 70 93 | 63]
[3 15 32 37 40 40 63 63 70 93 |]

The process repeats until after the last element is inserted.

Note that insertion sort does NOT copy the array. The array is copied here only to show the progression.

The statements at the top of the page are true following each pass through the array.

Insertion sort skips the first element. Selection sort skips the last element.

Insertion sort implementation

public static void sort(int[] arr)
{
    for (int i = 1; i < arr.length; i++)
    {
        int elementToInsert = arr[i];
        int j = i;

        while (j > 0 && elementToInsert < arr[j - 1])
        {
            arr[j] = arr[j - 1];
            j--;
        }

        arr[j] = elementToInsert;
    }
}

Insertion sort is typically implemented with a while loop nested inside a for loop. The inner loop is responsible for finding the position at which elementToInsert should be inserted and for moving the necessary elements out of the way. The outer loop inserts elementToInsert into the correct position and tracks the partition.

At the beginning of each pass of the outer loop, i is the index of the first element of the unchecked part. At the end of each pass, i is the index of the last element of the sorted part.

j serves 2 related purposes. j is used by the inner loop to traverse the sorted part. After the inner loop, j is the index of the insertion point.

Alternative usage of j

public static void sort(int[] arr)
{
    for (int i = 1; i < arr.length; i++)
    {
        int elementToInsert = arr[i];
        int j = i - 1;

        while (j >= 0 && elementToInsert < arr[j])
        {
            arr[j + 1] = arr[j];
            j--;
        }

        arr[j + 1] = elementToInsert;
    }
}

Beginning programmers often start j at i - 1 since the value at i - 1 is the first value that must be checked by the inner loop. In this case, j is the index of the element to be compared against elementToInsert.

This is acceptable as long as the all lines use j consistently. The code above is correct.

Insertion sort variants

Recursive implementation (works)

// inserts value into correct position in arr[0] ... arr[index]
// precondition: arr[0] ... arr[index - 1] is sorted &&
//               arr[index] == value
private static void insert(int[] arr, int value, int index)
{
    if (index == 0 || value >= arr[index - 1])
    {
        arr[index] = value;
        return;
    }

    arr[index] = arr[index - 1];
    insert(arr, value, index - 1);
}

// sorts arr[index] ... arr[arr.length - 1]
public static void sort(int[] arr, int index)
{
    if (index >= arr.length)
        return;

    insert(arr, arr[index], index);
    sort(arr, index + 1);
}

public static void sort(int[] arr)
{
    sort(arr, 1);
}

This variant uses 2 private helper methods and a public method:

It is unlikely that this variant would be featured on the AP CS A Exam. The variant is presented here to improve understanding of the algorithm and as a demonstration of recursion. The sort(int[]) method shows the insertion sort algorithm elegantly.

  1. Insert the first element of the unchecked part into the sorted part.
  2. Sort the rest of the array.
public static void sort(int[] arr)
{
    for (int i = 1; i < arr.length; i++)
    {
        int elementToInsert = arr[i];

        int insertionPoint = binarySearch(arr, elementToInsert, 0, i - 1);
        if (insertionPoint < 0)
            insertionPoint = -(insertionPoint + 1);
        
        for (int j = i; j > insertionPoint; j--)
            arr[j] = arr[j - 1];

        arr[insertionPoint] = elementToInsert;
    }
}

The call to binarySearch refers to the method on my binary search page under Binary search with insertion point return. The description on that page explains what the method returns.

This variant of insertion sort uses binary search to find the position at which elementToInsert should be inserted into the sorted part of arr. This variant then uses a for loop to move the elements that need to be moved to make room for elementToInsert. The insertion itself is done normally.

This is likely too complicated for an AP CS A Exam question; however, it provides an additional opportunity to understand the behavior of insertion sort.

This approach makes fewer comparisons than the original version; however, it must still loop through the same number of elements in the sorted part to move those elements. The additional complexity of calling binary search means this is unlikely to be used in practice. In practice, the original version of insertion sort is often used as a base case for an algorithm such as merge sort. Merge sort runs quickly on a large number of elements but slowly on a small number of elements.

Insertion sort with binary search with ArrayList<String>

public static void sort(ArrayList<String> list)
{
    for(int i = 1; i < list.size(); i++)
    {
        int position = BinarySearch.binarySearch(list, list.get(i), 0, i - 1);

        if (position < 0)
            position = -(position + 1);
        
        list.add(position, list.remove(i));
    }
}

This is an interesting variant because its apparent simplicity hides complexity. There is no need for a loop to shift elements over to make room to insert the element at i. The variant doesn’t even require a variable to store a copy of the element. Instead, the ArrayList remove and add methods handle the shift. Unfortunately, the calls to remove and add are quite expensive, so this variant is unlikely to be used in practice.

AP CS A Exam sort & search algorithms

Comments

Comment on Insertion sort