\(\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{\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}}\)

Selection sort

Selection sort is an elementary sorting algorithm. It is based on the following simple idea:

  • ​ Find a smallest element in the array, and swap it with the first one.

  • ​ Repeat the previous step when considering the sub-array obtained by excluding the first element, until the sub-array consists of only one element.

Some implementations of the algorithm are given below.

def selectionSort(a: Array[Int]) =
  val n = a.length
  var i = 0
  while i < n-1 do
    // Find the smallest element in the sub-array a(i..n-1)
    var smallestElement = a(i)
    var smallestIndex = i
    var j = i + 1
    while j < n do
      if a(j) < smallestElement then
        smallestElement = a(j)
        smallestIndex = j
      j += 1
    // Swap the elements at a(i) and a(smallestIndex)
    a(smallestIndex) = a(i)
    a(i) = smallestElement
    // The sub-array a(0..i) is now sorted
    i += 1

Example: Selection sort

Example: Instability of selection sort

The animation below illustrates that selection sort is not stable. In it, the relative positions of the elements 10 are swapped in the sorting process.

Observe that the algorithm only needs few indices and to store the current minimum element candidate outside the array at any point of time. Thus the amount of extra memory required is \(\Theta(1)\) and the algorithm works in place.

Running time analysis

Running time analysis of the algorithm is quite straightforward. The body of the inner while-loop takes constant time. So do all the other instructions, excluding the inner while-loop, in the outer while-loop. For the outer while-loop index \(i\), the inner loop body is executed \(n-1-i\) times. Thus the running time is \(\sum_{i=0}^{n-2}(c + (n-1-i)d)\), where \(c\) and \(d\) are constants capturing the constant running times of the outer while-loop body (excluding the inner while-loop) and the inner while-loop body. The sum expands to

\begin{array}{rcl} c\sum_{i=0}^{n-2}1 + d\sum_{i=0}^{n-2}(n-1-i)&=&\\ c(n-1) + d((n-1) + (n-2) + ... + 1)&=&\\ c(n-1) + d \frac{n(n-1)}{2} &=& \Theta(n^2) \end{array}

The asymptotic best-case and worst-case running times are the same as the outer and inner while-loops are always run to the end regardless of the array contents. Thus it is easy to analyze the amount of pairwise element comparisons performed by the algorithm.

  • ​ For finding the smallest element, the current first element is compared to all the other \(n-1\) elements.

  • ​ For finding the second smallest element, the current second element is compared to all the other \(n-2\) elements.

  • ​ And so on.

Therefore, for an array of \(n\) elements, the algorithm always performs \(\sum_{i=1}^{n-1}i = \frac{n(n-1)}{2}\) pairwise element comparisons.

Experimental evaluation

The plots below show the performance of the above selection sort code when compared to sorting methods found in standard libraries. When inspecting the plot with logarithmic time scale, we can see that the gap between the lines increases when the array sizes increase. Thus there is more than a constant time factor difference between the running times of the codes. Based on the plots, it is obvious that, for instance, the sorted method in Scala implements an algorithm that scales much better than selection sort.

These experiments were run in a desktop computer with a 6 cores 3.8GHz Intel Xeon W-2235 processor, 32 GiB of memory, Scala 3.3.0 compiler, openjdk 11.0.18 Java virtual machine, and Ubuntu 22.04 operating system.

Generics vs fixed types

By using Scala generics and C++ templates (see also this, this and this), one can build a generic selection sort implementation that works for any comparable object type (floating point numbers, strings, and so on).

def selectionSort[A](a: Array[A])(using ord: math.Ordering[A]) =
  val n = a.length
  var i = 0
  while i < n-1 do
    // Find the smallest element in the sub-array a(i..n-1)
    var smallestElement = a(i)
    var smallestIndex = i
    var j = i + 1
    while j < n do
      if ord.lt(a(j), smallestElement) then // CHANGED
        smallestElement = a(j)
        smallestIndex = j
      j += 1
    // Swap the elements at a(i) and a(smallestIndex)
    a(smallestIndex) = a(i)
    a(i) = smallestElement
    // The sub-array a(0..i) is now sorted
    i += 1

Above, A is the type of the elements in the array, and the Ordering ord is used for comparing the array elements. Note that only two lines have been changed in the code: the method declaration and the comparison line.

Unfortunately, with current Scala compiler, the generality does not come without a performance penalty. As an example, consider applying the generic selection sort to arrays of integers. To apply the lt comparison method in the Ordering trait, each integer fetched from the array is “boxed” (that is, wrapped into an object), causing a memory allocation and then later garbage collection. In addition, several virtual function invocations are performed.

We can see the practical effect of this by comparing the running times of our first selection sort working only on integer arrays to those of the generic selection sort algorithm above. If we use logarithmic time scale, we can see that the running times of the generic version are a constant factor (of roughly ten) slower than those of the non-generic integer array version.

In C++, the performance penalty is rather small as the templates are specialized to the required types in the compilation process.