2.3. Selection Sort¶
Selection sort is another sorting algorithm based on finding a minimum in an array. Unlike insertion sort, which locates each new element in an already sorted prefix, selection sort obtains the sorted prefix by “extending” it, at each iteration, with a minimum of a not-yet sorted suffix of the array:
let select_sort arr =
let len = Array.length arr in
for i = 0 to len - 1 do
for j = i to len - 1 do
if arr.(j) < arr.(i)
then swap arr i j
else ()
done
done
2.3.1. Tracing Selection Sort¶
Let us print intermediate stages of the selection sort as follows:
let select_sort_print arr =
let len = Array.length arr in
for i = 0 to len - 1 do
print_int_sub_array 0 i arr;
print_int_sub_array i len arr;
print_newline ();
for j = i to len - 1 do
print_offset ();
Printf.printf "j = %d, a[j] = %d, a[i] = %d: " j arr.(j) arr.(i);
print_int_sub_array 0 i arr;
print_int_sub_array i len arr;
print_newline ();
if arr.(j) < arr.(i)
then swap arr i j
else ()
done;
print_int_sub_array 0 (i + 1) arr;
print_int_sub_array (i + 1) len arr;
print_newline (); print_newline ();
done
This results in the following output:
# select_sort_print a1;;
[| |] [| 6; 8; 5; 2; 3; 7; 0 |]
j = 0, a[j] = 6, a[i] = 6: [| |] [| 6; 8; 5; 2; 3; 7; 0 |]
j = 1, a[j] = 8, a[i] = 6: [| |] [| 6; 8; 5; 2; 3; 7; 0 |]
j = 2, a[j] = 5, a[i] = 6: [| |] [| 6; 8; 5; 2; 3; 7; 0 |]
j = 3, a[j] = 2, a[i] = 5: [| |] [| 5; 8; 6; 2; 3; 7; 0 |]
j = 4, a[j] = 3, a[i] = 2: [| |] [| 2; 8; 6; 5; 3; 7; 0 |]
j = 5, a[j] = 7, a[i] = 2: [| |] [| 2; 8; 6; 5; 3; 7; 0 |]
j = 6, a[j] = 0, a[i] = 2: [| |] [| 2; 8; 6; 5; 3; 7; 0 |]
[| 0 |] [| 8; 6; 5; 3; 7; 2 |]
[| 0 |] [| 8; 6; 5; 3; 7; 2 |]
j = 1, a[j] = 8, a[i] = 8: [| 0 |] [| 8; 6; 5; 3; 7; 2 |]
j = 2, a[j] = 6, a[i] = 8: [| 0 |] [| 8; 6; 5; 3; 7; 2 |]
j = 3, a[j] = 5, a[i] = 6: [| 0 |] [| 6; 8; 5; 3; 7; 2 |]
j = 4, a[j] = 3, a[i] = 5: [| 0 |] [| 5; 8; 6; 3; 7; 2 |]
j = 5, a[j] = 7, a[i] = 3: [| 0 |] [| 3; 8; 6; 5; 7; 2 |]
j = 6, a[j] = 2, a[i] = 3: [| 0 |] [| 3; 8; 6; 5; 7; 2 |]
[| 0; 2 |] [| 8; 6; 5; 7; 3 |]
[| 0; 2 |] [| 8; 6; 5; 7; 3 |]
j = 2, a[j] = 8, a[i] = 8: [| 0; 2 |] [| 8; 6; 5; 7; 3 |]
j = 3, a[j] = 6, a[i] = 8: [| 0; 2 |] [| 8; 6; 5; 7; 3 |]
j = 4, a[j] = 5, a[i] = 6: [| 0; 2 |] [| 6; 8; 5; 7; 3 |]
j = 5, a[j] = 7, a[i] = 5: [| 0; 2 |] [| 5; 8; 6; 7; 3 |]
j = 6, a[j] = 3, a[i] = 5: [| 0; 2 |] [| 5; 8; 6; 7; 3 |]
[| 0; 2; 3 |] [| 8; 6; 7; 5 |]
[| 0; 2; 3 |] [| 8; 6; 7; 5 |]
j = 3, a[j] = 8, a[i] = 8: [| 0; 2; 3 |] [| 8; 6; 7; 5 |]
j = 4, a[j] = 6, a[i] = 8: [| 0; 2; 3 |] [| 8; 6; 7; 5 |]
j = 5, a[j] = 7, a[i] = 6: [| 0; 2; 3 |] [| 6; 8; 7; 5 |]
j = 6, a[j] = 5, a[i] = 6: [| 0; 2; 3 |] [| 6; 8; 7; 5 |]
[| 0; 2; 3; 5 |] [| 8; 7; 6 |]
[| 0; 2; 3; 5 |] [| 8; 7; 6 |]
j = 4, a[j] = 8, a[i] = 8: [| 0; 2; 3; 5 |] [| 8; 7; 6 |]
j = 5, a[j] = 7, a[i] = 8: [| 0; 2; 3; 5 |] [| 8; 7; 6 |]
j = 6, a[j] = 6, a[i] = 7: [| 0; 2; 3; 5 |] [| 7; 8; 6 |]
[| 0; 2; 3; 5; 6 |] [| 8; 7 |]
[| 0; 2; 3; 5; 6 |] [| 8; 7 |]
j = 5, a[j] = 8, a[i] = 8: [| 0; 2; 3; 5; 6 |] [| 8; 7 |]
j = 6, a[j] = 7, a[i] = 8: [| 0; 2; 3; 5; 6 |] [| 8; 7 |]
[| 0; 2; 3; 5; 6; 7 |] [| 8 |]
[| 0; 2; 3; 5; 6; 7 |] [| 8 |]
j = 6, a[j] = 8, a[i] = 8: [| 0; 2; 3; 5; 6; 7 |] [| 8 |]
[| 0; 2; 3; 5; 6; 7; 8 |] [| |]
- : unit = ()
Notice that at each iteration of the inner loop, a new minimum of the
remaining suffix is identified and at the end this is what becomes and
“extension” of the currently growing prefix: 0
, 2
, 3
,
5
, etc. During the inner iteration, we look for minimum in the
same way we were looking for a minimum in a list. All elements in the
non-sorted suffix are larger or equal than elements in the prefix. The
current element arr.(i)
is, thus a minimum of the
prefix-of-the-suffix of the array, yet it’s larger than any element in
the prefix.
2.3.2. Invariants of Selection Sort¶
The observed above intuition can be captured by the following invariants:
let suffix_larger_than_prefix i arr =
let len = Array.length arr in
let prefix = array_to_list 0 i arr in
let suffix = array_to_list i len arr in
List.for_all (fun e ->
List.for_all (fun f -> e <= f) suffix
) prefix
let select_sort_outer_inv i arr =
sub_array_sorted 0 i arr &&
suffix_larger_than_prefix i arr
let select_sort_inner_inv j i arr =
is_min_sub_array i j arr arr.(i) &&
sub_array_sorted 0 i arr &&
suffix_larger_than_prefix i arr
leading to the following annotated version:
let select_sort_inv arr =
let len = Array.length arr in
for i = 0 to len - 1 do
assert (select_sort_outer_inv i arr);
for j = i to len - 1 do
assert (select_sort_inner_inv j i arr);
if arr.(j) < arr.(i)
then swap arr i j
else ();
assert (select_sort_inner_inv (j + 1) i arr);
done;
assert (select_sort_outer_inv (i + 1) arr);
done
Notice that the inner invariant, when j
becomes len
(i.e.,
right before the end of the last iteration), implies that the found
element arr.(i)
is the global minimum of the suffix (which is all
larger than prefix), and, hence the sorted prefix can be extended with
this element, while remaining sorted.
2.3.3. Termination of Selection Sort¶
the algorithm terminates, as both loops in it, inner and outer are bounded and iterate over finite sub-arrays of a given array.
2.3.4. Exercise 2¶
Rewrite selection sort, so it would walk the array right-to-left, looking for a maximum rather than a minimum for a currently unprocessed sub-array, while sorting the overall array in an ascending order. Write the invariants for this version and explain how the inner loop invariant, upon the loop’s termination, implies the outer loop’s invariant.
2.3.5. Exercise 3¶
Generalise either insertion or selection sort to take an array of
arbitrary type 'a array
and comparator less_than
of type 'a
-> 'a -> bool
, and return an array sorted in an ascending order
according to this comparator. Test your implementation by sorting an
array of lists by length.
2.3.6. Exercise 4¶
- Which sorting method executes less primitive operations, such as swapping and comparing array elements, for an array in reverse order, selection sort or insertion sort?
- Which method runs faster on a fully sorted array?
Conduct experiments and justify your answer by explaining the mechanics of the algorithms.
2.3.7. Exercise 5¶
Bubble Sort is a popular, but inefficient, sorting algorithm, similar to selection sort. Instead of selecting a new minimum, it works by repeatedly swapping adjacent elements in the suffix that are out of order. In pseudocode it is implemented as follows:
BubbleSort (A):
for i = 0 to A.length - 1
for j = A.length - 1 downto i + 1
if A[j] < A[j - 1]
swap A[j] and A[j - 1]
- Implement the algorithm in OCaml using
for-to
andfor-downto
constructs. - Implement tracing for it.
- State the inner and the outer loop invariants. Explain in text how the inner invariant, upon finishing the inner loop, implies the invariant of the outer loop.