Skip to main content

Sorting algorithms

By Afshine Amidi and Shervine Amidi

General concepts

In this part, arrays of nn elements are visually represented as histograms. The height of each bar represents the value of the associated element in the array.

Sorting algorithm

A sorting algorithm takes an unsorted array A=[a0,...,an1]A=[a_0, ..., a_{n-1}] as input and returns a sorted array A=[a0,...,an1]A'=[a_0', ..., a_{n-1}'] as output. AA' is a permutation of AA such that:

a0a1...an1\boxed{a_0'\leqslant a_1'\leqslant...\leqslant a_{n-1}'}

Stability

A sorting algorithm is said to be stable if the order of tied elements is guaranteed to remain the same after sorting the array.

Remark

Examples of stable sorting algorithms include merge sort and insertion sort.

The next sections will go through each sorting algorithm into more detail

Basic sort

Bubble sort

Bubble sort is a stable sorting algorithm that has a time complexity of O(n2)\mathcal{O}(n^2) and a space complexity of O(1)\mathcal{O}(1).

Intuition Compare consecutive elements and swap them if they are not in the correct order.

Algorithm

  • Compute step: Starting from the beginning of the array, compare the element ala_l at position ii with the element ara_r at position i+1i+1.

    • Case alara_l\leqslant a_r: They are already in the correct order. There is nothing to do.

    • Case al>ara_l>a_r: They are not in the correct order. Swap them.

      Repeat this process until reaching the end of the array.

  • Repeat step: Repeat the compute step until no swap can be done.

We note that:

  • At the end of the kthk^{\textrm{th}} pass, the last kk elements of the array are guaranteed to be in their final positions.

  • The algorithm finishes in at most n1n-1 passes. In particular:

    • If the input array is already sorted, then the algorithm finishes in 1 pass.

    • If the input array is reverse sorted, then the algorithm finishes in n1n-1 passes.

Insertion sort

Insertion sort is a stable sorting algorithm that has a time complexity of O(n2)\mathcal{O}(n^2) and a space complexity of O(1)\mathcal{O}(1).

Intuition Incrementally build a sorted subarray by successively inserting the next unsorted element at its correct position.

Algorithm

  • Initialization: The first element of the unsorted array can be interpreted as a subarray of one element that is already sorted.

  • Compute step: Starting from position i=1i=1, we want to insert element aia_{i} into the current sorted subarray of size ii. In order to do that, we compare aia_i with its preceding element apa_p:

    • As long as ap>aia_p > a_i, we iteratively swap apa_p with aia_i.

    • This process ends with either aia_i verifying apaia_p\leqslant a_i or aia_i being at position 0 of the array.

      At the end of this step, the sorted subarray is of size i+1i+1.

  • Repeat step: Repeat the compute step until the sorted subarray reaches a size of nn.

Selection sort

Selection sort is a stable sorting algorithm that has a time complexity of O(n2)\mathcal{O}(n^2) and a space complexity of O(1)\mathcal{O}(1).

Intuition Incrementally build a sorted subarray by successively inserting the minimum value among the remaining elements.

Algorithm

  • Initialization:

    • Find the minimum value of the unsorted array.

    • Swap it with the element at the beginning of the array. The first element can now be interpreted as a sorted subarray with one element.

  • Compute step: Starting from i=1i=1, we want to insert a new element into the sorted subarray of size ii.

    • Find the minimum value amina_{\textrm{min}} among the remaining elements at positions i,...,n1i, ..., n-1 of the array. By construction, amina_{\textrm{min}} is greater or equal than all elements of the current sorted subarray.

    • Swap amina_{\textrm{min}} with the element at position ii of the array.

      At the end of this step, the sorted subarray is of size i+1i+1.

  • Repeat step: Repeat the compute step until the sorted subarray reaches a size of nn.

Remark

Insertion and selection sort are very similar in that they build the sorted array from scratch and add elements one at a time.

Cycle sort

Cycle sort is an unstable sorting algorithm that has a time complexity of O(n2)\mathcal{O}(n^2) and a space complexity of O(1)\mathcal{O}(1).

Intuition Determine the index of the final position of each element in the sorted array.

Algorithm

  • Compute step: Starting from i=0i=0, we want to find the final position of element aia_i along with those of the other elements impacted by this move.

    To do so, we count the number of elements that are smaller than aia_i:

    Ni=#{k,ak<ai}\boxed{N_i=\#\left\{k, a_k < a_i\right\}}

    The final position of aia_i is at index NiN_i, since we know that there are NiN_i smaller elements than aia_i in the final sorted array.

    • Case Ni=iN_i=i: This is a self-cycle, meaning that the element is already at its correct position. There is nothing to do.

    • Case NiiN_i\neq i: This is the start of a cycle of moves. Place aia_i at position NiN_i (or to the right of any duplicates, if applicable) and keep the replaced value in a temporary variable.

      Keep moving elements using this logic until getting back to position ii.

  • Repeat step: Repeat the compute step until reaching the end of the array. We can see cycles being formed from the way elements are moved.

Remark

This algorithm sorts the array with a minimum amount of rewrites since it only moves elements to their final positions.

Efficient sort

Merge sort

Merge sort is a stable sorting algorithm that has a time complexity of O(nlog(n))\mathcal{O}(n\log(n)) and a space complexity of O(n)\mathcal{O}(n).

Intuition Build a sorted array by merging two already-sorted arrays.

Algorithm

  • Divide step: Divide the array into as many subarrays as there are elements. Each resulting subarray has only one element and can be considered as sorted.

  • Conquer step: For each pair of sorted subarrays, build a sorted array by merging them using the two-pointer technique.

    Repeat the process until all subarrays are merged into one final sorted array.

Remark

This algorithm is usually implemented recursively.

Heap sort

Heap sort is an unstable sorting algorithm that has a time complexity of O(nlog(n))\mathcal{O}(n\log(n)) and a space complexity of O(1)\mathcal{O}(1).

Intuition Incrementally build a sorted subarray by successively retrieving the maximum of remaining elements using a max-heap.

Algorithm

  • Initialization: Build a max-heap from the unsorted array in O(n)\mathcal{O}(n) time. This is done by recursively swapping each parent with their child of highest value.

    We can use the same array to represent the max-heap.

  • Compute step: Starting from i=0i=0, incrementally build a sorted subarray of size i+1i+1 that is placed at the end of the array.

    • Pop an element from the max-heap and place it at position ni1n-i-1 of the array.

      By construction, the popped element is greater or equal than the ni1n-i-1 elements of the heap and smaller or equal than the ii previously popped elements.

    • Heapify the resulting max-heap of size ni1n-i-1 in O(log(n))\mathcal{O}(\log(n)) time.

  • Repeat step: Repeat the compute step until the subarray reaches a size of nn.

Quick sort

Quick sort is an unstable sorting algorithm that has a time complexity of O(n2)\mathcal{O}(n^2) and a space complexity of O(n)\mathcal{O}(n).

Intuition Recursively choose an element of the array to be the pivot, and put:

  • Smaller elements to the left of the pivot.

  • Bigger elements to the right of the pivot.

Algorithm

  • Compute step:

    • Pivot isolation step: Choose an element of the array to be the pivot pp and swap it with the last element of the array.

    • Partition step: Starting from indices l=0l=0 and r=0r=0, we make sure that elements ara_r that are smaller or equal than the pivot pp are sent to the left of the array:

      • Case arpa_r \leqslant p: Swap element ara_r at index rr with element ala_l at index ll. Increment ll by 1.

      • Case ar>pa_r > p: There is nothing to do.

        This process is successively repeated for all indices rr until the second-to-last position of the array.

    • Pivot inclusion step: The final position of the left pointer ll represents the position in the array where its left elements are all array elements that are smaller than or equal to the pivot. We swap the pivot with the element from position ll.

      At the end of this step, the pivot is at its correct and final position.

  • Recursion step: The compute step is run recursively on the resulting subarrays on the left and right of the pivot. They are then merged back together to form the final sorted array.

We note that the runtime of the algorithm is sensitive to the choice of the pivot and the patterns found in the input array. In general, a time complexity of O(nlog(n))\mathcal{O}(n\log(n)) and a space complexity of O(log(n))\mathcal{O}(\log(n)) can be obtained when the pivot is a good approximation of the median of the array.

The two most common methods to choose the pivot are:

Special sort

Counting sort

Counting sort is a stable sorting algorithm that is efficient for integer arrays with values within a small range [ ⁣[0,k] ⁣][\![0, k]\!]. It has a time complexity of O(n+k)\mathcal{O}(n+k) and a space complexity of O(n+k)\mathcal{O}(n+k).

Intuition Determine the final position of each element by counting the number of times its associated value appears in the array.

Algorithm

  • Number of occurrences: This step takes O(n+k)\mathcal{O}(n+k) time and O(k)\mathcal{O}(k) space.

    • Initialization: Initialize an array CC of length k+1k+1.

    • Count step: Scan array AA and increment the counter cvc_v of each encountered value v{0,...,k}v\in\{0, ..., k\} by 1.

      Each count cvc_v represents the number of times the value v{0,...,k}v\in\{0, ..., k\} appears in array AA.

    • Cumulative step: Compute the cumulative sum of array C=[c0,..,ck]C=[c_0, .., c_k] and move each resulting element to the right. This operation is done in-place and takes O(k)\mathcal{O}(k) time and O(1)\mathcal{O}(1) space.

      For each value vv present in AA, the associated element cvc_v in the resulting array CC indicates its starting index ii in the final sorted array AA'.

  • Construction of the sorted array: This step takes O(n)\mathcal{O}(n) time and O(n)\mathcal{O}(n) space.

    • Initialization: Initialize an array AA' of length nn that will contain the final sorted array.

    • Main step: For each value vv of the unsorted array AA:

      • Write vv to index cvc_v of AA'.

      • Increment cvc_v by 1 so that any later duplicates can be handled.

At the end of this process, we obtain the final sorted array AA'.

Radix sort

Radix sort is a stable sorting algorithm that is well suited for integer arrays where elements are written with a limited number of digits dd, each digit being in the range [ ⁣[0,k] ⁣][\![0, k]\!]. This algorithm has a time complexity of O(d(n+k))\mathcal{O}(d(n+k)) and a space complexity of O(n+k)\mathcal{O}(n+k).

Intuition Successively sort elements based on their digits using counting sort.

Algorithm

  • Compute step: Perform counting sort based on the rightmost digit. This step takes O(n+k)\mathcal{O}(n+k) time and O(n+k)\mathcal{O}(n+k) space.

  • Repeat step: Repeat the *compute step on the remaining digits until reaching the leftmost digit.

    At the end of this process, the array is sorted.

The trick of this algorithm lies in the stability property of counting sort: the relative ordering based on a given (weak) digit helps in breaking ties of later (stronger) digits.


Want more content like this?

Subscribe here to be notified of new Super Study Guide releases!