Subsections

11.2 Counting Sort and Radix Sort

In this section we study two sorting algorithms that are not comparison-based. Specialized for sorting small integers, these algorithms elude the lower-bounds of Theorem 11.5 by using (parts of) the elements in $ \ensuremath{\ensuremath{\mathit{a}}}$ as indices into an array. Consider a statement of the form

$\displaystyle \ensuremath{\ensuremath{\ensuremath{\mathit{c}}[\ensuremath{\mathit{a}}[\ensuremath{\mathit{i}}]}]} = 1 \enspace .
$

This statement executes in constant time, but has $ \ensuremath{\mathrm{length}(\ensuremath{\mathit{c}})}$ possible different outcomes, depending on the value of $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{\mathit{i}}]}$. This means that the execution of an algorithm that makes such a statement cannot be modelled as a binary tree. Ultimately, this is the reason that the algorithms in this section are able to sort faster than comparison-based algorithms.

11.2.1 Counting Sort

Suppose we have an input array $ \ensuremath{\ensuremath{\mathit{a}}}$ consisting of $ \ensuremath{\ensuremath{\mathit{n}}}$ integers, each in the range $ 0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}}-1$. The counting-sort algorithm sorts $ \ensuremath{\ensuremath{\mathit{a}}}$ using an auxiliary array $ \ensuremath{\ensuremath{\mathit{c}}}$ of counters. It outputs a sorted version of $ \ensuremath{\ensuremath{\mathit{a}}}$ as an auxiliary array $ \ensuremath{\ensuremath{\mathit{b}}}$.

The idea behind counting-sort is simple: For each $ \ensuremath{\ensuremath{\ensuremath{\mathit{i}}}}\in\{0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}}-1\}$, count the number of occurrences of $ \ensuremath{\ensuremath{\mathit{i}}}$ in $ \ensuremath{\ensuremath{\mathit{a}}}$ and store this in $ \ensuremath{\ensuremath{\mathit{c}}[\ensuremath{\mathit{i}}]}$. Now, after sorting, the output will look like $ \ensuremath{\ensuremath{\mathit{c}}[0]}$ occurrences of 0, followed by $ \ensuremath{\ensuremath{\mathit{c}}[1]}$ occurrences of 1, followed by $ \ensuremath{\ensuremath{\mathit{c}}[2]}$ occurrences of 2,..., followed by $ \ensuremath{\ensuremath{\mathit{c}}[\ensuremath{\mathit{k}}-1]}$ occurrences of $ \ensuremath{\ensuremath{\mathit{k}}-1}$. The code that does this is very slick, and its execution is illustrated in Figure 11.7:
\begin{leftbar}
\begin{flushleft}
\hspace*{1em} \ensuremath{\mathrm{counting\_so...
...bf{return}} \ensuremath{\ensuremath{\mathit{b}}}\\
\end{flushleft}\end{leftbar}

Figure 11.7: The operation of counting sort on an array of length $ \ensuremath{\ensuremath{\ensuremath{\mathit{n}}}}=20$ that stores integers $ 0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}}-1=9$.
\includegraphics[width=\textwidth ]{figs-python/countingsort}

The first $ {\color{black} \textbf{for}}$ loop in this code sets each counter $ \ensuremath{\ensuremath{\mathit{c}}[\ensuremath{\mathit{i}}]}$ so that it counts the number of occurrences of $ \ensuremath{\ensuremath{\mathit{i}}}$ in $ \ensuremath{\ensuremath{\mathit{a}}}$. By using the values of $ \ensuremath{\ensuremath{\mathit{a}}}$ as indices, these counters can all be computed in $ O(\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}})$ time with a single for loop. At this point, we could use $ \ensuremath{\ensuremath{\mathit{c}}}$ to fill in the output array $ \ensuremath{\ensuremath{\mathit{b}}}$ directly. However, this would not work if the elements of $ \ensuremath{\ensuremath{\mathit{a}}}$ have associated data. Therefore we spend a little extra effort to copy the elements of $ \ensuremath{\ensuremath{\mathit{a}}}$ into $ \ensuremath{\ensuremath{\mathit{b}}}$.

The next $ {\color{black} \textbf{for}}$ loop, which takes $ O(\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}})$ time, computes a running-sum of the counters so that $ \ensuremath{\ensuremath{\mathit{c}}[\ensuremath{\mathit{i}}]}$ becomes the number of elements in $ \ensuremath{\ensuremath{\mathit{a}}}$ that are less than or equal to $ \ensuremath{\ensuremath{\mathit{i}}}$. In particular, for every $ \ensuremath{\ensuremath{\ensuremath{\mathit{i}}}}\in\{0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}}-1\}$, the output array, $ \ensuremath{\ensuremath{\mathit{b}}}$, will have

$\displaystyle \ensuremath{\ensuremath{\ensuremath{\mathit{b}}[\ensuremath{\math...
...\mathit{i}}]-1}]}=\ensuremath{\ensuremath{\ensuremath{\mathit{i}}}} \enspace .
$

Finally, the algorithm scans $ \ensuremath{\ensuremath{\mathit{a}}}$ backwards to place its elements, in order, into an output array $ \ensuremath{\ensuremath{\mathit{b}}}$. When scanning, the element $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{i}]\gets \ensuremath{j}}$ is placed at location $ \ensuremath{\ensuremath{\mathit{b}}[\ensuremath{\mathit{c}}[\ensuremath{\mathit{j}}]-1}]$ and the value $ \ensuremath{\ensuremath{\mathit{c}}[\ensuremath{\mathit{j}}]}$ is decremented.

Theorem 11..7   The $ \ensuremath{\mathrm{counting\_sort}(\ensuremath{\mathit{a}},\ensuremath{\mathit{k}})}$ method can sort an array $ \ensuremath{\ensuremath{\mathit{a}}}$ containing $ \ensuremath{\ensuremath{\mathit{n}}}$ integers in the set $ \{0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}}-1\}$ in $ O(\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}}+\ensuremath{\ensuremath{\ensuremath{\mathit{k}}}})$ time.

The counting-sort algorithm has the nice property of being stable; it preserves the relative order of equal elements. If two elements $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{\mathit{i}}]}$ and $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{\mathit{j}}]}$ have the same value, and $ \ensuremath{\ensuremath{\ensuremath{\mathit{i}}}}<\ensuremath{\ensuremath{\ensuremath{\mathit{j}}}}$ then $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{\mathit{i}}]}$ will appear before $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{\mathit{j}}]}$ in $ \ensuremath{\ensuremath{\mathit{b}}}$. This will be useful in the next section.

11.2.2 Radix-Sort

Counting-sort is very efficient for sorting an array of integers when the length, $ \ensuremath{\ensuremath{\mathit{n}}}$, of the array is not much smaller than the maximum value, $ \ensuremath{\ensuremath{\ensuremath{\mathit{k}}}}-1$, that appears in the array. The radix-sort algorithm, which we now describe, uses several passes of counting-sort to allow for a much greater range of maximum values.

Radix-sort sorts $ \ensuremath{\ensuremath{\mathit{w}}}$-bit integers by using $ \ensuremath{\ensuremath{\ensuremath{\mathit{w}}}}/\ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}$ passes of counting-sort to sort these integers $ \ensuremath{\ensuremath{\mathit{d}}}$ bits at a time.11.2 More precisely, radix sort first sorts the integers by their least significant $ \ensuremath{\ensuremath{\mathit{d}}}$ bits, then their next significant $ \ensuremath{\ensuremath{\mathit{d}}}$ bits, and so on until, in the last pass, the integers are sorted by their most significant $ \ensuremath{\ensuremath{\mathit{d}}}$ bits.
\begin{leftbar}
\begin{flushleft}
\hspace*{1em} \ensuremath{\mathrm{radix\_sort}...
...bf{return}} \ensuremath{\ensuremath{\mathit{b}}}\\
\end{flushleft}\end{leftbar}
(In this code, the expression $ \ensuremath{(\ensuremath{\mathit{a}}[\ensuremath{\mathit{i}}]\ensuremath{\gg}\...
...remath{\mathit{d}}\cdot \ensuremath{\mathit{p}})} \wedge (\ensuremath{2^{d}-1})$ extracts the integer whose binary representation is given by bits $ (\ensuremath{\ensuremath{\ensuremath{\mathit{p}}}}+1)\ensuremath{\ensuremath{\...
...math{\ensuremath{\mathit{p}}}}\ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}$ of $ \ensuremath{\ensuremath{\mathit{a}}[\ensuremath{\mathit{i}}]}$.) An example of the steps of this algorithm is shown in Figure 11.8.

Figure 11.8: Using radixsort to sort $ \ensuremath{\ensuremath{\ensuremath{\mathit{w}}}}=8$-bit integers by using 4 passes of counting sort on $ \ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}=2$-bit integers.
\includegraphics[width=\textwidth ]{figs-python/radixsort}

This remarkable algorithm sorts correctly because counting-sort is a stable sorting algorithm. If $ \ensuremath{\ensuremath{\ensuremath{\mathit{x}}}} < \ensuremath{\ensuremath{\ensuremath{\mathit{y}}}}$ are two elements of $ \ensuremath{\ensuremath{\mathit{a}}}$, and the most significant bit at which $ \ensuremath{\ensuremath{\mathit{x}}}$ differs from $ \ensuremath{\ensuremath{\mathit{y}}}$ has index $ r$, then $ \ensuremath{\ensuremath{\mathit{x}}}$ will be placed before $ \ensuremath{\ensuremath{\mathit{y}}}$ during pass $ \lfloor r/\ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}\rfloor$ and subsequent passes will not change the relative order of $ \ensuremath{\ensuremath{\mathit{x}}}$ and $ \ensuremath{\ensuremath{\mathit{y}}}$.

Radix-sort performs $ \ensuremath{\ensuremath{\mathit{w}}/\ensuremath{\mathit{d}}}$ passes of counting-sort. Each pass requires $ O(\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}}+2^{\ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}})$ time. Therefore, the performance of radix-sort is given by the following theorem.

Theorem 11..8   For any integer $ \ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}>0$, the $ \ensuremath{\mathrm{radix\_sort}(\ensuremath{\mathit{a}},\ensuremath{\mathit{k}})}$ method can sort an array $ \ensuremath{\ensuremath{\mathit{a}}}$ containing $ \ensuremath{\ensuremath{\mathit{n}}}$ $ \ensuremath{\ensuremath{\mathit{w}}}$-bit integers in $ O((\ensuremath{\ensuremath{\ensuremath{\mathit{w}}}}/\ensuremath{\ensuremath{\...
...nsuremath{\mathit{n}}}}+2^{\ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}}))$ time.

If we think, instead, of the elements of the array being in the range $ \{0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}}^c-1\}$, and take $ \ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}=\lceil\log\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}}\rceil$ we obtain the following version of Theorem 11.8.

Corollary 11..1   The $ \ensuremath{\mathrm{radix\_sort}(\ensuremath{\mathit{a}},\ensuremath{\mathit{k}})}$ method can sort an array $ \ensuremath{\ensuremath{\mathit{a}}}$ containing $ \ensuremath{\ensuremath{\mathit{n}}}$ integer values in the range $ \{0,\ldots,\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}}^c-1\}$ in $ O(c\ensuremath{\ensuremath{\ensuremath{\mathit{n}}}})$ time.



Footnotes

... time.11.2
We assume that $ \ensuremath{\ensuremath{\mathit{d}}}$ divides $ \ensuremath{\ensuremath{\mathit{w}}}$, otherwise we can always increase $ \ensuremath{\ensuremath{\mathit{w}}}$ to $ \ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}\lceil
\ensuremath{\ensuremat...
...nsuremath{\mathit{w}}}}/\ensuremath{\ensuremath{\ensuremath{\mathit{d}}}}\rceil$.
opendatastructures.org