Sorting algorithms are... algorithms used to sort lists (collections? I'm looking for a term without a more specific meaning in Python than there is in general programming) of objects.
Efficiency
In my experience, which is really not a lot, sorting algorithms are always taught alongside efficiency. This is because there are many sorting algorithms, all of which accomplish the same task, but will take differing amount of times to do so.
Efficiency is denoted by Big-O or O() notation. The term inside the brackets is some time of function, which is always plotted with number of items in the list as the independent variable, and time taken (which can be measured in various ways) as the dependent variable.
Some common efficiencies, in descending order are:
- c - constant, the time taken for a 1-item list is the same as the time taken for a 1000-item list
- c log n - logarithmic
- cn - linear
- cn^2 - quadratic
- cn^3 - cubic
- More Powers!
- c2^n - exponential
- cn! - factorial
- cn^n - not even sure what to call this
Within each category, there are different powers of c. For example, a O(2n^2) would have a higher efficiency than a O(3n^2), but the difference is minimal compared to the difference between O(2n^2) and O(2n^3).
Another thing to note, is that sorting algorithms can have a best-case and a worst-case efficiency. Meaning if you know you are dealing with small lists, you may want to pick a different algorithm than if you know you will be dealing with big lists.
Selection Sort
Selection sort has a best- and worst-case of O(n^2). Starting from the beginning of the list, you pick the item in that position. Then you move iteratively through the rest of the list, comparing all items to the original item. If the item further down the list is of lower value (sorting in ascending order), then the two items are switched. The new item at the original position is then compared with the rest of the list, starting from its old position. This is actually a lot simpler to put in code:
for i in range(len(L)):
for j in range(i + 1, len(L)):
if L[i] > L[j]:
L[i], L[j] = L[j], L[i]
The reason for the best- and worst-case complexities being the same, is that, even if L[i] is the smallest item in the list, it must still be compared to every item after it in the list.
And of course, I can't help but include this helpful visualization:
Quick Sort
From its name, you would assume quick sort to be fairly fast. It has a best-case performance of O(n log n) and a worst-case performance of O(n^2). The worst-case is fairly rare, so it is usually ranked by its best-case performance. I didn't list this one above, so it would be below O(log n) but above O(n).
Quick sort is a recursive algorithm. You start by splitting the list around one element, known as the pivot. All items smaller than the pivot go before it, all items larger than the pivot go after it. Thus, the pivot is in its correct position. Then the same algorithm is applied to two smaller lists to the left and right of the pivot. This continues until there are three or less items in the sublist, in which case those three items will all be correctly sorted.
Obviously, this algorithm works best when the pivot would end up in the centre of the sorted list. Otherwise, it is very similar to the above selection sort.
This algorithm also comes with a dance.
Merge Sort
Merge sort is another recursive sorting algorithm. (Wow, recursion is important!) It has a best- and worst-case performance of O(n log n), making it slightly better than quick sort if you think all your lists have a high chance of already being sorted.
Merge sort is performed by first dividing a list in half. Then the merge sort algorithm is performed on each half, and the two results are merged (concatenated). This algorithm keeps breaking the list down into smaller chunks until it reaches size 2, in which case the two items are simply compared and switched if needed.
By starting with splitting the list in half, merge sort avoids quick sort's problem of an already-sorted list.