Insertion Sort in Python

Introduction

If you're majoring in Computer Science, Insertion Sort is most likely one of the first sorting algorithms you have heard of. It is intuitive and easy to implement, but it's very slow on large arrays and is rarely used to sort them.

Insertion Sort is very simple and intuitive to implement, which is one of the reasons it's generally taught at an early stage in programming. It's a stable, in-place algorithm, that works really well for nearly-sorted or small arrays.

Let's elaborate these terms:

  • In-place: Requires a small, constant additional space (no matter the input size of the collection), but rewrites the original memory locations of the elements in the collection.
  • Stable: The algorithm maintains the relative order of equal objects from the initial array. In other words, say your company's employee database returns "Dave Watson" and "Dave Brown" as two employees, in that specific order. If you were to sort them by their (shared) first name, a stable algorithm would guarantee that this order remains unchanged.

Note: Insertion Sort doesn't need to know the entire array in advance before sorting. The algorithm can receive one element at a time. This is great if we want to add more elements to be sorted - the algorithm only inserts that element in its proper place without "re-doing" the whole sort.

Insertion Sort is used rather often in practice, because of how efficient it is for small (~10 items) data sets. We will talk more about that later.

The Idea Behind the Insertion Sort

Insertion sort is often illustrated by comparing it to sorting a hand of cards while playing rummy. For those of you unfamiliar with the game, most players want the cards in their hand sorted in ascending order so they can quickly see which combinations they have at their disposal.

The dealer deals out 14 cards to you, you pick them up one by one and put them in your hand in the right order. During this entire process, your hand holds the sorted "subarray" of your cards, while the remaining face-down cards on the table are unsorted - from which you take cards one by one, and put them in your hand.

In that manner, the Insertion sort partitions an array into a "sorted" subarray and an "unsorted" subarray. In the beginning, the sorted subarray contains only the first element of our original array.

The first element in the unsorted array is evaluated so that we can insert it into its proper place in the sorted subarray. The insertion is done by moving all elements larger than the new element one position to the right. Continue doing this until our entire array is sorted, as simple as that.

Note: Keep in mind that when we say an element is larger or smaller than another element - it doesn't necessarily mean larger or smaller integers.

We can define the words "larger" and "smaller" however we like when using custom objects. For example, point A can be "larger" than point B if it's further away from the center of the coordinate system.

We will mark the sorted subarray with bolded numbers, and use the following array to illustrate the algorithm:

  • 8, 5, 4, 10, 9

The first step would be to "add" 8 to our sorted subarray.

  • 8, 5, 4, 10, 9

Now we take a look at the first unsorted element - 5. We keep that value in a separate variable, for example current, for safe-keeping. 5 is less than 8. We move 8 one place to the right, effectively overwriting the 5 that was previously stored there (hence the separate variable for safekeeping):

  • 8, 8, 4, 10, 9 (current = 5)

5 is lesser than all the elements in our sorted subarray, so we insert it into the first position:

  • 5, 8, 4, 10, 9

Next, we look at number 4. We save that value in the current. 4 is less than 8 so we move 8 to the right, and do the same with 5.

  • 5, 5, 8, 10, 9 (current = 4)

Again we've encountered an element lesser than our entire sorted subarray, so we put it in the first position:

  • 4, 5, 8, 10, 9

10 is greater than our rightmost element in the sorted subarray and is therefore larger than any of the elements to the left of 8. So we simply move on to the next element:

  • 4, 5, 8, 10, 9

9 is less than 10, so we move 10 to the right:

  • 4, 5, 8, 10, 10 (current = 9)

However, 9 is greater than 8, so we simply insert 9 right after 8.

Free eBook: Git Essentials

Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!

  • 4, 5, 8, 9, 10

How to Implement Bubble Sort in Python

As we have previously mentioned, Insertion Sort is fairly easy to implement. We'll implement it first on a simple array of integers and then on some custom objects.

In practice, it's much more likely that you'll be working with objects and sorting them based on certain criteria.

Sorting Arrays

def insertion_sort(array):

    # We start from 1 since the first element is trivially sorted
    for index in range(1, len(array)):
        currentValue = array[index]
        currentPosition = index

        # As long as we haven't reached the beginning and there is an element
        # in our sorted array larger than the one we're trying to insert - move
        # that element to the right
        while currentPosition > 0 and array[currentPosition - 1] > currentValue:
            array[currentPosition] = array[currentPosition -1]
            currentPosition = currentPosition - 1


        # We have either reached the beginning of the array or we have found
        # an element of the sorted array that is smaller than the element
        # we're trying to insert at index currentPosition - 1.
        # Either way - we insert the element at currentPosition
        array[currentPosition] = currentValue

Let's populate a simple array and sort it:

array = [4, 22, 41, 40, 27, 30, 36, 16, 42, 37, 14, 39, 3, 6, 34, 9, 21, 2, 29, 47]
insertion_sort(array)
print("sorted array: " + str(array))

Output:

sorted array: [2, 3, 4, 6, 9, 14, 16, 21, 22, 27, 29, 30, 34, 36, 37, 39, 40, 41, 42, 47]

Note: It would have been technically incorrect if we reversed the order of conditions in our while loop. That is, if we first checked whether array[currentPosition-1] > currentValue before checking whether currentPosition > 0.

This would mean that if we did indeed reach the 0th element, we would first check whether array[-1] > currentValue. In Python, this is "fine", although technically incorrect, since it wouldn't cause the loop to end prematurely or continue when it shouldn't.

This is because if we had reached the zeroth element, the second condition of currentPosition > 0 would fail regardless of the first condition, and cause the loop to break. In Python array[-1] > currentValue is equivalent to array[len(array) - 2] > currentValue and the interpreter wouldn't complain, but this is not a comparison we actually want to happen.

This reversed order of conditions is a bad idea. In a lot of cases, it can lead to unexpected results that can be hard to debug because there is no syntactic or semantic error. Most programming languages would "complain" for trying to access the -1st element, but Python won't complain and it's easy to miss such a mistake.

The takeaway advice from this is to always check whether the index is valid before using it to access elements.

Sorting Custom Objects

We've mentioned before that we can define "greater than" and "lesser than" however we want - that the definition doesn't need to rely solely on integers. There are several ways we can change our algorithm to work with custom objects.

We could redefine the actual comparison operators for our custom class and keep the same algorithm code as above. However, that would mean that we'd need to overload those operators if we wanted to sort the objects of our class in a different way.

Arguably the best way to use Insertion Sort for custom classes is to pass another argument to the insertion_sort() method - specifically a comparison method. The most convenient way to do this is by using a custom lambda function when calling the sorting method.

In this implementation, we'll sort points where a "smaller" point is the one with a lower x coordinate.

First, we'll define our Point class:

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return str.format("({},{})", self.x, self.y)

Then we make a slight change to our insertion_sort method to accommodate custom sorting:

def insertion_sort(array, compare_function):
    for index in range(1, len(array)):
        currentValue = array[index]
        currentPosition = index

        while currentPosition > 0 and compare_function(array[currentPosition - 1], currentValue):
            array[currentPosition] = array[currentPosition - 1]
            currentPosition = currentPosition - 1

        array[currentPosition] = currentValue

Finally, we test the program:

A = Point(1,2)
B = Point(4,4)
C = Point(3,1)
D = Point(10,0)

array = [A,B,C,D]

# We sort by the x coordinate, ascending
insertion_sort(array, lambda a, b: a.x > b.x)

for point in array:
    print(point)

We get the output:

(1,2)
(3,1)
(4,4)
(10,0)

This algorithm will now work for any type of array, as long as we provide an appropriate comparison function.

Insertion Sort in Practice

Insertion sort may seem like a slow algorithm, and indeed in most cases, it is too slow for any practical use with its O(n2) time complexity. However, as we've mentioned, it's very efficient on small arrays and on nearly sorted arrays.

This makes Insertion Sort very relevant for use in combination with algorithms that work well on large data sets.

For example, Java used a Dual Pivot Quick Sort as the primary sorting algorithm but used Insertion Sort whenever the array (or subarray created by Quick Sort) had less than 7 elements.

Another efficient combination is simply ignoring all small subarrays created by Quick Sort, and then passing the final, nearly-sorted array, through Insertion Sort.

Another place where Insertion Sort left its mark is with a very popular algorithm called Shell Sort. Shell Sort works by calling Insertion Sort to sort pairs of elements far apart from each other, then incrementally reducing the gap between elements to be compared.

Essentially it makes a lot of Insertion Sort calls to first small, and then nearly-sorted larger arrays, harnessing all the advantages it can.

Conclusion

Insertion Sort is a very simple, generally inefficient algorithm that nonetheless has several specific advantages that make it relevant even after many other, generally more efficient algorithms have been developed.

It remains a great algorithm for introducing future software developers into the world of sorting algorithms and is still used in practice for specific scenarios in which it shines.

Last Updated: October 25th, 2023
Was this article helpful?

Improve your dev skills!

Get tutorials, guides, and dev jobs in your inbox.

No spam ever. Unsubscribe at any time. Read our Privacy Policy.

Olivera PopovićAuthor

LinkedIn: https://rs.linkedin.com/in/227503161
If you need any help - post it in the comments :) That way someone else can reply if I'm busy.

Free
Course

Graphs in Python - Theory and Implementation

# python# data structures# algorithms# computer science

Graphs are an extremely versatile data structure. More so than most people realize! Graphs can be used to model practically anything, given their nature of...

David Landup
Dimitrije Stamenic
Jovana Ninkovic
Details
Course

Data Visualization in Python with Matplotlib and Pandas

# python# pandas# matplotlib

Data Visualization in Python with Matplotlib and Pandas is a course designed to take absolute beginners to Pandas and Matplotlib, with basic Python knowledge, and...

David Landup
David Landup
Details

© 2013-2024 Stack Abuse. All rights reserved.

AboutDisclosurePrivacyTerms