After some hiatus, let’s continue with the Sorting Algorithms Studies series. Let’s talk about Selection Sort.
This algorithm showcases a very natural way to order things, one that you probably already used yourself: repeatedly finding the lowest value, and putting it at the start of the list.
Explanation
This algorithm works by, starting at the first element of the list, repeatedly comparing the current value with the next value, searching for the lowest value - or the highest, if the order is decrescent -, until the end of the elements of the current iteration. When the inner loop ends, the value that was identified as being the lowest - or the highest, if the order is decrescent -, is placed at the start of the list. Having provided the clarifications, from now on this article will focus on the crescent order implementation. After the end of the last inner loop, the algorithm starts a new inner loop, excluding the value that was just swapped, and repeats the same procedure above.
Structure
It is comprised by two nested loops:
- The outer one loops amongst the numbers that comprise the range between 0 and the size of the array, minus 1. Example: if the array has size 5, the outer loop will loop from 0 to 4. Let’s call the number of the outer loop x.
- The inner loop is the one that actually makes the comparisons and swaps. It loops amongst the numbers that comprise the range between 0 plus the number of the outer loop (x) and the length of the array, minus 1. Example: if the array has size 5, the inner loop will loop from 0 to 4 on the first outer loop, 1 to 4 in the second outer loop, and so on. Let’s call the number of the inner loop y.
- For each y in the inner loop, it compares the element on the index y with the element on the index y + 1, searching for the element with the lowest value, placing its index in an auxiliary value. Let’s call it swap_index.
- At the end of the inner loop, it swaps the element on the index x with the element in the index swap_index.
Also, there is an Selection Sort implementation with a minor optimization. Instead of just looking for the lowest value at each loop, it also looks for the highest value, making two swaps per loop. And, as each loop progresses, the range of elements to be compared by the next loop diminishes by two.
Code
This is the implementation of Selection Sort in Python. It may look slighter more complex than the usual Selection Sort implementation, but that’s because I’ll to provide codes that work both for the crescent and decrescent sorting.
And this is the implementation of optimized Selection Sort in Python. Again, this codes works both for the crescent and decrescent sorting.
Finally, here’s the interactive algorithm visualization, with step-by-step execution.
Complexity
Worst-case performance: O(n²)
Best-case performance: O(n²)
Average performance: O(n²)
Space complexity 1: O(n)
Auxiliary space 2: O(1) for the normal implementation; O(2) for the optimized implementation. 3
Benchmark
As the posts from the Sorting Algorithms Studies are posted, I’ll benchmark the algorithms against each other. For now, there’s only Bubble Sort, Insertion Sort and Selection Sort to be shown.
Sorting 1000 different arrays with 1000 randomly sorted elements | |
---|---|
Algorithm | Average Time Spent (seconds) |
Selection Sort Optimized - Crescent | 0.0752946375 |
Selection Sort Optimized - Decrescent | 0.0753254230 |
Insertion Sort Optimized - Decrescent | 0.0754734700 |
Selection Sort - Decrescent | 0.0768013875 |
Selection Sort - Crescent | 0.0769227114 |
Insertion Sort Optimized - Crescent | 0.0774509237 |
Insertion Sort - Decrescent | 0.1340322953 |
Insertion Sort - Crescent | 0.1360035590 |
Bubble Sort - Decrescent | 0.1568078588 |
Bubble Sort - Crescent | 0.1597317403 |
Bubble Sort Optimized - Decrescent | 0.1602741867 |
Bubble Sort Optimized - Crescent | 0.1618632810 |
Sorting 1000 different arrays with 1000 elements in ascending order | |
---|---|
Algorithm | Average Time Spent (seconds) |
Bubble Sort Optimized - Crescent | 0.0002221006 |
Insertion Sort - Crescent | 0.0003292135 |
Insertion Sort Optimized - Crescent | 0.0003776510 |
Selection Sort Optimized - Decrescent | 0.0864848356 |
Selection Sort - Crescent | 0.0876183987 |
Selection Sort - Decrescent | 0.0900442983 |
Selection Sort Optimized - Crescent | 0.0924761386 |
Bubble Sort - Crescent | 0.0998125052 |
Insertion Sort Optimized - Decrescent | 0.1702091216 |
Bubble Sort - Decrescent | 0.2626039418 |
Bubble Sort Optimized - Decrescent | 0.2685824222 |
Insertion Sort - Decrescent | 0.3066988004 |
Sorting 1000 different arrays with 1000 elements in descending order | |
---|---|
Algorithm | Average Time Spent (seconds) |
Bubble Sort Optimized - Decrescent | 0.0001869991 |
Insertion Sort - Decrescent | 0.0002643334 |
Insertion Sort Optimized - Decrescent | 0.0004072941 |
Selection Sort - Decrescent | 0.0790777007 |
Selection Sort Optimized - Crescent | 0.0794137814 |
Selection Sort - Crescent | 0.0829683988 |
Selection Sort Optimized - Decrescent | 0.0836715313 |
Bubble Sort - Decrescent | 0.0884681934 |
Insertion Sort Optimized - Crescent | 0.1560818879 |
Bubble Sort - Crescent | 0.2418319963 |
Bubble Sort Optimized - Crescent | 0.2456611876 |
Insertion Sort - Crescent | 0.2854158659 |
Wrapping Up
Selection Sort is another of those simple algorithm that we use more often than we realize. As the Bubble and Insertion Sort, it doesn’t performs well, but it may be enough for some scenarios, like when you have really small lists.
Stay tuned for the next chapters of this series!
Auxiliary Space + Input space ↩
Extra or temporary space used by the algorithm during it's execution. ↩
Python's way of swapping two values dismisses the need of a temp variable to make the swap of values. Because of that, there normal implementation in Python uses O(1) auxiliary space. The optimized implementation still uses O(2) auxiliary space. ↩