| About   «  8.14. Radix Sort   ::   Contents   ::   8.16. Lower Bounds for Sorting  »

8.15. An Empirical Comparison of Sorting Algorithms

8.15.1. An Empirical Comparison of Sorting Algorithms

Which sorting algorithm is fastest? Asymptotic complexity analysis lets us distinguish between \(\Theta(n^2)\) and \(\Theta(n \log n)\) algorithms, but it does not help distinguish between algorithms with the same asymptotic complexity. Nor does asymptotic analysis say anything about which algorithm is best for sorting small lists. For answers to these questions, we can turn to empirical testing.

Table 8.15.1 shows timing results for actual implementations of the sorting algorithms presented in this chapter. The algorithms compared include Insertion Sort, Bubble Sort, Selection Sort, Shellsort, Quicksort, Mergesort, Heapsort, Radix Sort, all run on simple int[] arrays.

Two versions of Insertion Sort are timed, the standard algorithm and the optimized version using that shifts values down the array instead of simple swaps. Bubblesort shows times for the standard algorithm and one that monitors the last position swapped in an attempt to optimize performance. Mergesort compares both the basic array-based implementation and an optimized version (which includes calls to Insertion Sort for lists of length below a threshold value of 14). For Quicksort, two versions are compared: the basic implementation and an optimized version that does not partition sublists below length 14 (with Insertion Sort performed at the end). Times for three versions of Radixsort are shown: the array-based version (one using divide and mod, the other using shift and mask) and the linked-list version (using divide and mod).

Except for the rightmost columns, the input to each algorithm is a random array of integers. This affects the timing for some of the sorting algorithms. For example, Selection Sort is not being used to best advantage because the record size is small (cheap swaps), so it does not get the best possible showing. The Radix Sort implementation certainly takes advantage of knowing that integer keys, and does not look at more bits than necessary.

The various sorting algorithms are shown for lists of sizes 10, 100, 1000, 10,000, 100,000, and 1,000,000. (Note that the \(O(n^2)\) sorts are not times on input arrays of size 1,000,000 due to their exessive time requirements). The final two columns of each table show the performance for the algorithms on inputs of size 10,000 where the numbers are in ascending (sorted) and descending (reverse sorted) order, respectively. These columns demonstrate best-case performance for some algorithms and worst-case performance for others. They also show that for some algorithms, the order of input has little effect.

These figures show a number of interesting results. As expected, the \(O(n^2)\) sorts are quite poor performers for large arrays. Insertion Sort is by far the best of this group. Shellsort is clearly superior to any of these \(O(n^2)\) sorts for lists of even 100 records. Optimized Quicksort is generally the best overall algorithm aside from Radix sort. Even for small arrays, optimized Quicksort performs well because it does one partition step before calling Insertion Sort. Compared to the other \(O(n \log n)\) sorts, unoptimized Heapsort is quite slow due to the overhead of the class structure. When all of this is stripped away and the algorithm is implemented to manipulate an array directly, it is still somewhat slower than mergesort. In general, optimizing the various algorithms makes a noticeable improvement for larger array sizes.

Overall, Radix Sort is a surprisingly strong performer. This is true for both the array version and the linked-list version. However, its requirements for being able to properly manipulate the digits of its key might limit the range of record types (and thus, the applications) that the sort could support.

It is certainly important to consider the fact that the sorting times above are for simple arrays of int values. This affects the relative time required for key value access and comparison, and swap times. Table 8.15.1 shows timing results for for the same set of algorithms written to support records of Key-Value Pair objects (where the keys and values are Integer objects). A few algorithms perform relatively better or worse. The most noticeable change is that the optimized versions of Quicksort and Mergesort have effectively identical runtimes.

Here are a few multiple choice questions that ask you to compare the sorting algorithms that we learned about in this chapter.

   «  8.14. Radix Sort   ::   Contents   ::   8.16. Lower Bounds for Sorting  »

Close Window