\(\) \(%\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Cormen et al}}\) \(\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Introduction to Algorithms, 3rd ed.} (\href{http://libproxy.aalto.fi/login?url=http://site.ebrary.com/lib/aalto/Doc?id=10397652}{Aalto access})}\) \(%\newcommand{\CLRS}{\href{https://mitpress.mit.edu/books/introduction-algorithms}{Introduction to Algorithms, 3rd ed.} (\href{http://libproxy.aalto.fi/login?url=http://site.ebrary.com/lib/aalto/Doc?id=10397652}{online via Aalto lib})}\) \(\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Algorithms, 4th ed.}}\) \(%\newcommand{\SW}{\href{http://algs4.cs.princeton.edu/home/}{Sedgewick and Wayne}}\) \(\) \(\newcommand{\Java}{\href{http://java.com/en/}{Java}}\) \(\newcommand{\Python}{\href{https://www.python.org/}{Python}}\) \(\newcommand{\CPP}{\href{http://www.cplusplus.com/}{C++}}\) \(\newcommand{\ST}[1]{{\Blue{\textsf{#1}}}}\) \(\newcommand{\PseudoCode}[1]{{\color{blue}\textsf{#1}}}\) \(\) \(%\newcommand{\subheading}[1]{\textbf{\large\color{aaltodgreen}#1}}\) \(\newcommand{\subheading}[1]{\large{\usebeamercolor[fg]{frametitle} #1}}\) \(\) \(\newcommand{\Blue}[1]{{\color{flagblue}#1}}\) \(\newcommand{\Red}[1]{{\color{aaltored}#1}}\) \(\newcommand{\Emph}[1]{\emph{\color{flagblue}#1}}\) \(\) \(\newcommand{\Engl}[1]{({\em engl.}\ #1)}\) \(\) \(\newcommand{\Pointer}{\raisebox{-1ex}{\huge\ding{43}}\ }\) \(\) \(\newcommand{\Set}[1]{\{#1\}}\) \(\newcommand{\Setdef}[2]{\{{#1}\mid{#2}\}}\) \(\newcommand{\PSet}[1]{\mathcal{P}(#1)}\) \(\newcommand{\Card}[1]{{\vert{#1}\vert}}\) \(\newcommand{\Tuple}[1]{(#1)}\) \(\newcommand{\Implies}{\Rightarrow}\) \(\newcommand{\Reals}{\mathbb{R}}\) \(\newcommand{\Seq}[1]{(#1)}\) \(\newcommand{\Arr}[1]{[#1]}\) \(\newcommand{\Floor}[1]{{\lfloor{#1}\rfloor}}\) \(\newcommand{\Ceil}[1]{{\lceil{#1}\rceil}}\) \(\newcommand{\Path}[1]{(#1)}\) \(\) \(%\newcommand{\Lg}{\lg}\) \(\newcommand{\Lg}{\log_2}\) \(\) \(\newcommand{\BigOh}{O}\) \(%\newcommand{\BigOh}{\mathcal{O}}\) \(\newcommand{\Oh}[1]{\BigOh(#1)}\) \(\) \(\newcommand{\todo}[1]{\Red{\textbf{TO DO: #1}}}\) \(\) \(\newcommand{\NULL}{\textsf{null}}\) \(\) \(\newcommand{\Insert}{\ensuremath{\textsc{insert}}}\) \(\newcommand{\Search}{\ensuremath{\textsc{search}}}\) \(\newcommand{\Delete}{\ensuremath{\textsc{delete}}}\) \(\newcommand{\Remove}{\ensuremath{\textsc{remove}}}\) \(\) \(\newcommand{\Parent}[1]{\mathop{parent}(#1)}\) \(\) \(\newcommand{\ALengthOf}[1]{{#1}.\textit{length}}\) \(\) \(\newcommand{\TRootOf}[1]{{#1}.\textit{root}}\) \(\newcommand{\TLChildOf}[1]{{#1}.\textit{leftChild}}\) \(\newcommand{\TRChildOf}[1]{{#1}.\textit{rightChild}}\) \(\newcommand{\TNode}{x}\) \(\newcommand{\TNodeI}{y}\) \(\newcommand{\TKeyOf}[1]{{#1}.\textit{key}}\) \(\) \(\newcommand{\PEnqueue}[2]{{#1}.\textsf{enqueue}(#2)}\) \(\newcommand{\PDequeue}[1]{{#1}.\textsf{dequeue}()}\) \(\) \(\newcommand{\Def}{\mathrel{:=}}\) \(\) \(\newcommand{\Eq}{\mathrel{=}}\) \(\newcommand{\Asgn}{\mathrel{\leftarrow}}\) \(%\newcommand{\Asgn}{\mathrel{:=}}\) \(\) \(%\) \(% Heaps\) \(%\) \(\newcommand{\Downheap}{\textsc{downheap}}\) \(\newcommand{\Upheap}{\textsc{upheap}}\) \(\newcommand{\Makeheap}{\textsc{makeheap}}\) \(\) \(%\) \(% Dynamic sets\) \(%\) \(\newcommand{\SInsert}[1]{\textsc{insert}(#1)}\) \(\newcommand{\SSearch}[1]{\textsc{search}(#1)}\) \(\newcommand{\SDelete}[1]{\textsc{delete}(#1)}\) \(\newcommand{\SMin}{\textsc{min}()}\) \(\newcommand{\SMax}{\textsc{max}()}\) \(\newcommand{\SPredecessor}[1]{\textsc{predecessor}(#1)}\) \(\newcommand{\SSuccessor}[1]{\textsc{successor}(#1)}\) \(\) \(%\) \(% Union-find\) \(%\) \(\newcommand{\UFMS}[1]{\textsc{make-set}(#1)}\) \(\newcommand{\UFFS}[1]{\textsc{find-set}(#1)}\) \(\newcommand{\UFCompress}[1]{\textsc{find-and-compress}(#1)}\) \(\newcommand{\UFUnion}[2]{\textsc{union}(#1,#2)}\) \(\) \(\) \(%\) \(% Graphs\) \(%\) \(\newcommand{\Verts}{V}\) \(\newcommand{\Vtx}{v}\) \(\newcommand{\VtxA}{v_1}\) \(\newcommand{\VtxB}{v_2}\) \(\newcommand{\VertsA}{V_\textup{A}}\) \(\newcommand{\VertsB}{V_\textup{B}}\) \(\newcommand{\Edges}{E}\) \(\newcommand{\Edge}{e}\) \(\newcommand{\NofV}{\Card{V}}\) \(\newcommand{\NofE}{\Card{E}}\) \(\newcommand{\Graph}{G}\) \(\) \(\newcommand{\SCC}{C}\) \(\newcommand{\GraphSCC}{G^\text{SCC}}\) \(\newcommand{\VertsSCC}{V^\text{SCC}}\) \(\newcommand{\EdgesSCC}{E^\text{SCC}}\) \(\) \(\newcommand{\GraphT}{G^\text{T}}\) \(%\newcommand{\VertsT}{V^\textup{T}}\) \(\newcommand{\EdgesT}{E^\text{T}}\) \(\) \(%\) \(% NP-completeness etc\) \(%\) \(\newcommand{\Poly}{\textbf{P}}\) \(\newcommand{\NP}{\textbf{NP}}\) \(\newcommand{\PSPACE}{\textbf{PSPACE}}\) \(\newcommand{\EXPTIME}{\textbf{EXPTIME}}\)
\(\newcommand{\LO}{{\textit{lo}}}\) \(\newcommand{\HI}{{\textit{hi}}}\)

Insertion sort

Insertion sort is another elementary sorting algorithm whose algorithmic idea is rather easy to understand. However, as we’ll see, it can be considered efficient only for small arrays. The algorithmic idea of insertion sort can be stated as follows:

  • ​ Assume that the sub-array with the first \(i\) elements of the array is already sorted. In the first round, \(i = 1\).

  • ​ Take the \(i+1\):th element \(e\). Shift the last elements in the sorted sub-array prefix one step right until the correct position for \(e\) is reached. Insert \(e\) to that position.

  • ​ Now the sub-array of the first \(i+1\) elements is sorted.

  • ​ Repeat until the whole array is sorted.

In pseudocode, insertion sort can be described as follows. Please notice that, unlike in Introduction to Algorithms, 3rd ed. (online via Aalto lib), we start array indexing from \(0\), not from \(1\).

Insertion-sort(\( A \)): for \( i \Asgn 1 \) until \( \ALengthOf{A} \): \( e \Asgn A[i] \) // element to be moved \( j \Asgn i \) while \( j > 0 \) and \( A[j-1] > e \): \( A[j] \Asgn A[j-1] \) \( j \Asgn j-1 \) \( A[j] \Asgn e \)

An implementation for integer arrays in Scala looks quite similar:

def insertionSort(a: Array[Int]): Unit = {
  var i = 1
  while (i < a.length) {
    val key = a(i)
    var j = i;
    while (j > 0 && a(j - 1) > key) {
      a(j) = a(j - 1)
      j -= 1
    }
    a(j) = key
    i += 1
  }
}

Example: Insertion sort

The animation below shows how selection sort works on a smallish array of integers. The sorted sub-array is shown in blue.

Some properties of the algorithm:

  • ​ Only the element currently moved is stored outside the array at any point of time. Thus the amount of extra memory required is \(\Theta(1)\) and the algorithm works in place.

  • ​ It is stable because we always find the rightmost position where the currently considered element belongs to, meaning that it is never moved left past an equal element.

Running time analysis

In the following analyses it is assumed that both (i) the comparison of two elements, as well as (ii) the array accesses are constant-time operations.

In the best case, insertion sort runs in time \(\Theta(n)\). To see this, consider how the algorithm behaves on arrays that are already sorted. In such arrays, in each round, the next element considered is already at its correct position and this can be detected by using just one comparison and no shifts.

In the worst case, the running time of the algorithm is \(\Theta(n^2)\). To see this, consider an array of \(n\) distinct elements that is sorted in reverse order. Now

  • ​ the second element is moved 1 steps,

  • ​ the third element is moved 2 steps,

  • ​ and so on, and

  • ​ the \(n\):th element is moved \(n-1\) steps.

Each move of \(i\) steps induces the same amount of shifts. Therefore, at the \(i\)Theta(i)` comparisons and array accesses. Thus the running time recurrence is \(T(n) = \Theta(n) + T(n-1) = \Theta(n^2)\).

What about average case performance? Consider an array consisting of \(n\) distinct random elements. At each iteration, when moving the \(i\) th element \(a_i\) to its correct position, there are on average \(i/2\) elements in the sub-array \(\Arr{a_0,...,a_{i-1}}\) that are smaller than \(a_i\). Therefore, the work on average is \(\sum_{i=1}^{n-1} i/2 = \Theta(n^2)\).

How about the performance on a specific application? This depends on the inputs that the application provides. If the input is usually almost sorted already, insertion sort may work fine in practise. But if the inputs are usually in random order or in some order causing \(\Oh{n}\) elements to be moved long distances, we should probably consider some other algorithm.

Observe that insertion sort code is very “compact” (small constants) and the memory accesses are very local. Therefore, for small arrays, insertion sort can be quite competitive as we will see later.

If the key comparisons are expensive, one could use binary search for finding the correct place for each insertion. The causes number of comparisons to drop to \(\Oh{n \Lg n}\) in the worst case. But the overall running time stays \(\Theta(n^2)\) as the shifts still need to be performed.

Experimental evaluation

The plots below show the performance of the above Scala implementation of insertion sort with respect to selection sort (Section Selection sort) and the sorted method of the Scala Array class. As we can see, insertion sort is roughly twice as fast as selection sort on these input arrays consisting of random integers. However, their performance scales similarly and they are not that efficient as the algorithm in the sorted method on these inputs.

_images/selection-vs-insertion-vs-sorted.jpg _images/selection-vs-insertion-vs-sorted-logy.jpg