Intuition
Consider a list with non-negative integers within some fixed range . For example, consider the list with integers in the range .
Because we know that all of the integers in our array are within a fixed range, we could theoretically sort by defining an array to store the counts of every integer in , and then use this array to reconstruct a sorted version of !
Let’s define an array of size 6, where each index stores the count of its respective integer. Creating , we obtain
We can use this array to reconstruct a sorted version of ! Note that because we know the counts of every integer, we can read left to right, and progressively add any integer whose count is to our new array.
- We will have no ’s in .
- We will have at index 0, and at index 1.
- We will have at index 2, and at index 3.
- …
Let’s transform our array to make this reconstruction process easier. Redefine every index in to store the cumulative count of every integer prior to and up to .
This transformed array now tells us the last index in the sorted array (non-inclusive) of the respective integer represented by index ! We can use this to easily reconstruct our sorted array.
- We will have 0’s up to and not including index 0 of the array.
- Then, we will have 1’s up to (and not including) index 2 of the array.
- Then, we will have 2’s up to (and not including) index 4 of the array.
- Then, we will have 3’s up to (and not including) index 7 of the array.
- …
Knowing these index positions, we can look up the position of elements in , and rearrange them to obtain our sorted list! Iterating through right to left (to maintain relative order),
- We see element 3, and because , we know that in our sorted array, this element will be at index . We insert, and decrement by one.
- We see element 4, and because , we know that in our sorted array, this element will be at index . We insert, and decrement by one.
- …
Repeating this process, we obtain the final sorted array.
Note that this method of “looking up” element indices lets us generalize counting sort beyond simple integers!
Counting Sort
Pseudocode
In Counting Sort, and given an array with integers in the known range , we can sort it by:
- Counting the number of occurrences of each integer in , and storing it into an array , where the index represents that integer’s count.
- Transforming the indices in so that every index stores the cumulative count of every integer prior. This new array now stores the last index (non-inclusive) that an integer represented by index will be found at.
- Then, for every integer in , we can use to lookup its index, and put it into a new array at this location! Note that to ensure the algorithm is stable, we will have to iterate through backwards.
The pseudocode for the algorithm is given below.
def counting_sort(array, k):
# Count Occurrences
p = [0] * (k + 1)
for x in array:
p[x] += 1
# Transform P
for i in range(1, len(p)):
p[i] += p[i - 1]
# Reconstruct A
new_array = [0] * len(array)
for x in array:
new_index = p[x] - 1 # Offset by 1 to find last index
new_array[new_index] = x
for i in range(0, len(array)):
array[i] = new_array[i]
return output
Note that we could also modify counting sort to work on other inputs!
- For example, we could sort a list of fractions following the form where is a positive integer, by inverting every fraction before sorting.
- For, we could sort a list of integers between and inclusive, by adding to all integers before sorting, then reverting this change.
Time Complexity and Other Properties
Let’s analyze Counting Sort for its time complexity. Suppose that our array has length , and contains integers within the range .
Then, note that:
- The first loop on line will take time
- The second loop on line will take time
- The third loop on line will take time
- The fourth loop on line will take time
Adding these times together, we find overall time complexity !
Now consider the case where is independent of the list size . Then, this makes a constant, giving us time complexity !
However, this does not necessarily mean Counting Sort is always linear time - for example, if , then we would have complexity ! So, counting sort depends on the entries in the list.
Furthermore, we see that:
- We have auxiliary space as
new_array
has length , andp
has length . - This implementation of Counting Sort is stable and not in place.