Recent from talks
Nothing was collected or created yet.
Sorting algorithm
View on Wikipedia
In computer science, a sorting algorithm is an algorithm that puts elements of a list into an order. The most frequently used orders are numerical order and lexicographical order, and either ascending or descending. Efficient sorting is important for optimizing the efficiency of other algorithms (such as search and merge algorithms) that require input data to be in sorted lists. Sorting is also often useful for canonicalizing data and for producing human-readable output.
Formally, the output of any sorting algorithm must satisfy two conditions:
- The output is in monotonic order (each element is no smaller/larger than the previous element, according to the required order).
- The output is a permutation (a reordering, yet retaining all of the original elements) of the input.
Although some algorithms are designed for sequential access, the highest-performing algorithms assume data is stored in a data structure which allows random access.
History and concepts
[edit]From the beginning of computing, the sorting problem has attracted a great deal of research, perhaps due to the complexity of solving it efficiently despite its simple, familiar statement. Among the authors of early sorting algorithms around 1951 was Betty Holberton, who worked on ENIAC and UNIVAC.[1][2] Bubble sort was analyzed as early as 1956.[3] Asymptotically optimal algorithms have been known since the mid-20th century – new algorithms are still being invented, with the widely used Timsort dating to 2002, and the library sort being first published in 2006.
Comparison sorting algorithms have a fundamental requirement of Ω(n log n) comparisons (some input sequences will require a multiple of n log n comparisons, where n is the number of elements in the array to be sorted). Algorithms not based on comparisons, such as counting sort, can have better performance.
Sorting algorithms are prevalent in introductory computer science classes, where the abundance of algorithms for the problem provides a gentle introduction to a variety of core algorithm concepts, such as big O notation, divide-and-conquer algorithms, data structures such as heaps and binary trees, randomized algorithms, best, worst and average case analysis, time–space tradeoffs, and upper and lower bounds.
Sorting small arrays optimally (in the fewest comparisons and swaps) or fast (i.e. taking into account machine-specific details) is still an open research problem, with solutions only known for very small arrays (<20 elements). Similarly optimal (by various definitions) sorting on a parallel machine is an open research topic.
Classification
[edit]Sorting algorithms can be classified by:
- Computational complexity
- Best, worst and average case behavior in terms of the size of the list. For typical serial sorting algorithms, good behavior is O(n log n), with parallel sort in O(log2 n), and bad behavior is O(n2). Ideal behavior for a serial sort is O(n), but this is not possible in the average case. Optimal parallel sorting is O(log n).
- Swaps for "in-place" algorithms.
- Memory usage (and use of other computer resources). In particular, some sorting algorithms are "in-place". Strictly, an in-place sort needs only O(1) memory beyond the items being sorted; sometimes O(log n) additional memory is considered "in-place".
- Recursion: Some algorithms are either typically recursive or typically non-recursive, while others may typically be both (e.g., merge sort).
- Stability: stable sorting algorithms maintain the relative order of records with equal keys (i.e., values).
- Whether or not they are a comparison sort. A comparison sort examines the data only by comparing two elements with a comparison operator.
- General method: insertion, exchange, selection, merging, etc. Exchange sorts include bubble sort and quicksort. Selection sorts include cycle sort and heapsort.
- Whether the algorithm is serial or parallel. The remainder of this discussion almost exclusively concentrates on serial algorithms and assumes serial operation.
- Adaptability: Whether or not the presortedness of the input affects the running time. Algorithms that take this into account are known to be adaptive.
- Online: An algorithm such as Insertion Sort that is online can sort a constant stream of input.
Stability
[edit]
Stable sort algorithms sort equal elements in the same order that they appear in the input. For example, in the card sorting example to the right, the cards are being sorted by their rank, and their suit is being ignored. This allows the possibility of multiple different correctly sorted versions of the original list. Stable sorting algorithms choose one of these, according to the following rule: if two items compare as equal (like the two 5 cards), then their relative order will be preserved, i.e. if one comes before the other in the input, it will come before the other in the output.
Stability is important to preserve order over multiple sorts on the same data set. For example, say that student records consisting of name and class section are sorted dynamically, first by name, then by class section. If a stable sorting algorithm is used in both cases, the sort-by-class-section operation will not change the name order; with an unstable sort, it could be that sorting by section shuffles the name order, resulting in a nonalphabetical list of students.
More formally, the data being sorted can be represented as a record or tuple of values, and the part of the data that is used for sorting is called the key. In the card example, cards are represented as a record (rank, suit), and the key is the rank. A sorting algorithm is stable if whenever there are two records R and S with the same key, and R appears before S in the original list, then R will always appear before S in the sorted list.
When equal elements are indistinguishable, such as with integers, or more generally, any data where the entire element is the key, stability is not an issue. Stability is also not an issue if all keys are different.
Unstable sorting algorithms can be specially implemented to be stable. One way of doing this is to artificially extend the key comparison so that comparisons between two objects with otherwise equal keys are decided using the order of the entries in the original input list as a tie-breaker. Remembering this order, however, may require additional time and space.
One application for stable sorting algorithms is sorting a list using a primary and secondary key. For example, suppose we wish to sort a hand of cards such that the suits are in the order clubs (♣), diamonds (♦), hearts (♥), spades (♠), and within each suit, the cards are sorted by rank. This can be done by first sorting the cards by rank (using any sort), and then doing a stable sort by suit:
Within each suit, the stable sort preserves the ordering by rank that was already done. This idea can be extended to any number of keys and is utilised by radix sort. The same effect can be achieved with an unstable sort by using a lexicographic key comparison, which, e.g., compares first by suit, and then compares by rank if the suits are the same.
Comparison of algorithms
[edit]This analysis assumes that the length of each key is constant and that all comparisons, swaps and other operations can proceed in constant time.
Legend:
- n is the number of records to be sorted.
- Comparison column has the following ranking classifications: "Best", "Average" and "Worst" if the time complexity is given for each case.
- "Memory" denotes the amount of additional storage required by the algorithm.
- The run times and the memory requirements listed are inside big O notation, hence the base of the logarithms does not matter.
- The notation log2 n means (log n)2.
Comparison sorts
[edit]Below is a table of comparison sorts. Mathematical analysis demonstrates a comparison sort cannot perform better than O(n log n) on average.[4]
| Name | Best | Average | Worst | Memory | Stable | In-place | Method | Other notes |
|---|---|---|---|---|---|---|---|---|
| Heapsort | 1 | No | Yes | Selection | An optimized version of selection sort. Performs selection sort by constructing and maintaining a max heap to find the maximum in time. | |||
| Introsort | No | Yes | Partitioning & Selection | Used in several STL implementations. Performs a combination of Quicksort, Heapsort, and Insertion sort. | ||||
| Merge sort | n | Yes | No | Merging | Highly parallelizable (up to O(log n) using the Three Hungarians' Algorithm).[5] | |||
| In-Place Merge Sort | Yes | Yes | Merging | Variation of Mergesort which uses an in-place stable merge algorithm, such as rotate merge or symmerge. | ||||
| Tournament sort | n | Yes | No | Selection | An optimization of Selection Sort, which uses a tournament tree to select the min/max. | |||
| Tree sort | n | Yes | No | Insertion | When using a self-balancing binary search tree. | |||
| Block sort | n | 1 | Yes | Yes | Insertion & Merging | Combine a block-based in-place merge algorithm[6] with a bottom-up merge sort. | ||
| Smoothsort | n | 1 | No | Yes | Selection | Adaptive variant of heapsort based on the Leonardo sequence instead of a binary heap. | ||
| Timsort | n | n | Yes | No | Insertion & Merging | Makes comparisons when the data is already sorted or reverse sorted. | ||
| Patience sorting | n | n | No | No | Insertion & Selection | Finds all the longest increasing subsequences in O(n log n). | ||
| Cubesort | n | n | Yes | No | Insertion | Makes comparisons when the data is already sorted or reverse sorted. | ||
| Quicksort | No | Yes | Partitioning | Quicksort can be done in-place with O(log n) stack space.[7][8] | ||||
| Fluxsort | n | n | Yes | No | Partitioning & Merging | An adaptive branchless stable introsort. | ||
| Crumsort | n | No | Yes | Partitioning & Merging | An in-place, but unstable variant of Fluxsort. | |||
| Library sort | n | No | No | Insertion | Similar to a gapped insertion sort. | |||
| Shellsort | (Ciura) | (Ciura) | (Ciura) (Pratt) |
1 | No | Yes | Insertion | Small code size. Complexity may vary depending on gap sequence. Pratt's sequence has a worst-case of . The (Extended) Ciura sequence averages empirically. |
| Comb sort | 1 | No | Yes | Exchanging | Faster than bubble sort on average. | |||
| Insertion sort | n | 1 | Yes | Yes | Insertion | O(n + d), in the worst case over sequences that have d inversions. | ||
| Bubble sort | n | 1 | Yes | Yes | Exchanging | Tiny code size. | ||
| Cocktail shaker sort | n | 1 | Yes | Yes | Exchanging | A bi-directional variant of Bubblesort. | ||
| Gnome sort | n | 1 | Yes | Yes | Exchanging | Tiny code size. | ||
| Odd–even sort | n | 1 | Yes | Yes | Exchanging | Can be run on parallel processors easily. | ||
| Strand sort | n | n | Yes | No | Selection | |||
| Selection sort | 1 | No | Yes | Selection | Tiny code size. Noted for its simplicity and small number of element moves. Makes exactly swaps. | |||
| Exchange sort | 1 | No | Yes | Exchanging | Tiny code size. | |||
| Cycle sort | 1 | No | Yes | Selection | In-place with theoretically optimal number of writes. |
Non-comparison sorts
[edit]The following table describes integer sorting algorithms and other sorting algorithms that are not comparison sorts. These algorithms are not limited to Ω(n log n) unless meet unit-cost random-access machine model as described below.[9]
- Complexities below assume n items to be sorted, with keys of size k, digit size d, and r the range of numbers to be sorted.
- Many of them are based on the assumption that the key size is large enough that all entries have unique key values, and hence that n ≪ 2k, where ≪ means "much less than".
- In the unit-cost random-access machine model, algorithms with running time of , such as radix sort, still take time proportional to Θ(n log n), because n is limited to be not more than , and a larger number of elements to sort would require a bigger k in order to store them in the memory.[10]
| Name | Best | Average | Worst | Memory | Stable | n ≪ 2k | Notes |
|---|---|---|---|---|---|---|---|
| Pigeonhole sort | — | Yes | Yes | Cannot sort non-integers. | |||
| Bucket sort (uniform keys) | — | Yes | No | Assumes uniform distribution of elements from the domain in the array.[11]
Also cannot sort non-integers. | |||
| Bucket sort (integer keys) | — | Yes | Yes | If r is , then average time complexity is .[12] | |||
| Counting sort | — | Yes | Yes | If r is , then average time complexity is .[11] | |||
| LSD Radix Sort | Yes | No | recursion levels, 2d for count array.[11][12]
Unlike most distribution sorts, this can sort non-integers. | ||||
| MSD Radix Sort | — | Yes | No | Stable version uses an external array of size n to hold all of the bins.
Same as the LSD variant, it can sort non-integers. | |||
| MSD Radix Sort (in-place) | — | No | No | d=1 for in-place, recursion levels, no count array. | |||
| Spreadsort | n | No | No | Asymptotic are based on the assumption that n ≪ 2k, but the algorithm does not require this. | |||
| Burstsort | — | No | No | Has better constant factor than radix sort for sorting strings. Though relies somewhat on specifics of commonly encountered strings. | |||
| Flashsort | n | n | No | No | Requires uniform distribution of elements from the domain in the array to run in linear time. If distribution is extremely skewed then it can go quadratic if underlying sort is quadratic (it is usually an insertion sort). In-place version is not stable. | ||
| Postman sort | — | — | No | A variation of bucket sort, which works very similarly to MSD Radix Sort. Specific to post service needs. | |||
| Recombinant sort | No | No | Hashing, Counting, Dynamic Programming, Multidimensional data |
Samplesort can be used to parallelize any of the non-comparison sorts, by efficiently distributing data into several buckets and then passing down sorting to several processors, with no need to merge as buckets are already sorted between each other.
Others
[edit]Some algorithms are slow compared to those discussed above, such as the bogosort with unbounded run time and the stooge sort which has O(n2.7) run time. These sorts are usually described for educational purposes to demonstrate how the run time of algorithms is estimated. The following table describes some sorting algorithms that are impractical for real-life use in traditional software contexts due to extremely poor performance or specialized hardware requirements.
| Name | Best | Average | Worst | Memory | Stable | Comparison | Other notes |
|---|---|---|---|---|---|---|---|
| Bead sort | n | S | S | — | No | Works only with positive integers. Requires specialized hardware for it to run in guaranteed time. There is a possibility for software implementation, but running time will be , where S is the sum of all integers to be sorted; in the case of small integers, it can be considered to be linear. | |
| Merge-insertion sort | comparisons |
comparisons |
comparisons |
Varies | No | Yes | Makes very few comparisons worst case compared to other sorting algorithms.
Mostly of theoretical interest due to implementational complexity and suboptimal data moves. |
| "I Can't Believe It Can Sort"[13] | 1 | No | Yes | Notable primarily for appearing to be an erroneous implementation of either Insertion Sort or Exchange Sort. | |||
| Spaghetti (Poll) sort | n | n | n | Yes | Polling | This is a linear-time, analog algorithm for sorting a sequence of items, requiring O(n) stack space, and the sort is stable. This requires n parallel processors. See spaghetti sort § Analysis. | |
| Sorting network | Varies | Varies | Varies | Varies | Varies (stable sorting networks require more comparisons) | Yes | Order of comparisons are set in advance based on a fixed network size. |
| Bitonic sorter | parallel | parallel | non-parallel | 1 | No | Yes | An effective variation of Sorting networks. [disputed – discuss] |
| Bogosort | n | Unbounded | 1 | No | Yes | Random shuffling. Used for example purposes only, as even the expected best-case runtime is awful.[14]
Worst case is unbounded when using randomization, but a deterministic version guarantees worst case. | |
| LinearSort | n | n | Yes | No | Parody sorting algorithm to show the risk of overly relying on Big O notation - runs a merge sort and then sleeps until a fixed constant amount of time has elapsed from the function call, thus (wastefully) guaranteeing a fixed runtime below the hardcoded minimum. | ||
| Stooge sort | n | No | Yes | Slower than most of the sorting algorithms (even naive ones) with a time complexity of O(nlog 3 / log 1.5 ) = O(n2.7095...) Can be made stable, and is also a sorting network. | |||
| Slowsort | n | No | Yes | A multiply and surrender algorithm, antonymous with divide-and-conquer algorithm. | |||
| Franceschini's method[15] | 1 | Yes | Yes | Makes O(n) data moves in the worst case. Possesses ideal comparison sort asymptotic bounds but is only of theoretical interest. |
Theoretical computer scientists have invented other sorting algorithms that provide better than O(n log n) time complexity assuming certain constraints, including:
- Thorup's algorithm[16], a randomized integer sorting algorithm, taking O(n log log n) time and O(n) space.[16]
- AHNR algorithm,[17] an integer sorting algorithm which runs in time deterministically, and also has a randomized version which runs in linear time when words are large enough, specifically (where w is the word size).
- A randomized integer sorting algorithm taking expected time and O(n) space.[18]
Popular sorting algorithms
[edit]While there are a large number of sorting algorithms, in practical implementations a few algorithms predominate. Insertion sort is widely used for small data sets, while for large data sets an asymptotically efficient sort is used, primarily heapsort, merge sort, or quicksort. Efficient implementations generally use a hybrid algorithm, combining an asymptotically efficient algorithm for the overall sort with insertion sort for small lists at the bottom of a recursion. Highly tuned implementations use more sophisticated variants, such as Timsort (merge sort, insertion sort, and additional logic), used in Android, Java, and Python, and introsort (quicksort and heapsort), used (in variant forms) in some C++ sort implementations and in .NET.
For more restricted data, such as numbers in a fixed interval, distribution sorts such as counting sort or radix sort are widely used. Bubble sort and variants are rarely used in practice, but are commonly found in teaching and theoretical discussions.
When physically sorting objects (such as alphabetizing papers, tests or books) people intuitively generally use insertion sorts for small sets. For larger sets, people often first bucket, such as by initial letter, and multiple bucketing allows practical sorting of very large sets. Often space is relatively cheap, such as by spreading objects out on the floor or over a large area, but operations are expensive, particularly moving an object a large distance – locality of reference is important. Merge sorts are also practical for physical objects, particularly as two hands can be used, one for each list to merge, while other algorithms, such as heapsort or quicksort, are poorly suited for human use. Other algorithms, such as library sort, a variant of insertion sort that leaves spaces, are also practical for physical use.
Simple sorts
[edit]Two of the simplest sorts are insertion sort and selection sort, both of which are efficient on small data, due to low overhead, but not efficient on large data. Insertion sort is generally faster than selection sort in practice, due to fewer comparisons and good performance on almost-sorted data, and thus is preferred in practice, but selection sort uses fewer writes, and thus is used when write performance is a limiting factor.
Insertion sort
[edit]Insertion sort is a simple sorting algorithm that is relatively efficient for small lists and mostly sorted lists, and is often used as part of more sophisticated algorithms. It works by taking elements from the list one by one and inserting them in their correct position into a new sorted list similar to how one puts money in their wallet.[19] In arrays, the new list and the remaining elements can share the array's space, but insertion is expensive, requiring shifting all following elements over by one. Shellsort is a variant of insertion sort that is more efficient for larger lists.
Selection sort
[edit]Selection sort is an in-place comparison sort. It has O(n2) complexity, making it inefficient on large lists, and generally performs worse than the similar insertion sort. Selection sort is noted for its simplicity and also has performance advantages over more complicated algorithms in certain situations.
The algorithm finds the minimum value, swaps it with the value in the first position, and repeats these steps for the remainder of the list.[20] It does no more than n swaps and thus is useful where swapping is very expensive.
Efficient sorts
[edit]Practical general sorting algorithms are almost always based on an algorithm with average time complexity (and generally worst-case complexity) O(n log n), of which the most common are heapsort, merge sort, and quicksort. Each has advantages and drawbacks, with the most significant being that simple implementation of merge sort uses O(n) additional space, and simple implementation of quicksort has O(n2) worst-case complexity. These problems can be solved or ameliorated at the cost of a more complex algorithm.
While these algorithms are asymptotically efficient on random data, for practical efficiency on real-world data various modifications are used. First, the overhead of these algorithms becomes significant on smaller data, so often a hybrid algorithm is used, commonly switching to insertion sort once the data is small enough. Second, the algorithms often perform poorly on already sorted data or almost sorted data – these are common in real-world data and can be sorted in O(n) time by appropriate algorithms. Finally, they may also be unstable, and stability is often a desirable property in a sort. Thus more sophisticated algorithms are often employed, such as Timsort (based on merge sort) or introsort (based on quicksort, falling back to heapsort).
Merge sort
[edit]Merge sort takes advantage of the ease of merging already sorted lists into a new sorted list. It starts by comparing every two elements (i.e., 1 with 2, then 3 with 4...) and swapping them if the first should come after the second. It then merges each of the resulting lists of two into lists of four, then merges those lists of four, and so on; until at last two lists are merged into the final sorted list.[21] Of the algorithms described here, this is the first that scales well to very large lists, because its worst-case running time is O(n log n). It is also easily applied to lists, not only arrays, as it only requires sequential access, not random access. However, it has additional O(n) space complexity and involves a large number of copies in simple implementations.
Merge sort has seen a relatively recent surge in popularity for practical implementations, due to its use in the sophisticated algorithm Timsort, which is used for the standard sort routine in the programming languages Python[22] and Java (as of JDK7[23]). Merge sort itself is the standard routine in Perl,[24] among others, and has been used in Java at least since 2000 in JDK1.3.[25]
Heapsort
[edit]Heapsort is a much more efficient version of selection sort. It also works by determining the largest (or smallest) element of the list, placing that at the end (or beginning) of the list, then continuing with the rest of the list, but accomplishes this task efficiently by using a data structure called a heap, a special type of binary tree.[26] Once the data list has been made into a heap, the root node is guaranteed to be the largest (or smallest) element. When it is removed and placed at the end of the list, the heap is rearranged so the largest element remaining moves to the root. Using the heap, finding the next largest element takes O(log n) time, instead of O(n) for a linear scan as in simple selection sort. This allows Heapsort to run in O(n log n) time, and this is also the worst-case complexity.
Recombinant sort
[edit]Recombinant sort is a non-comparison-based sorting algorithm developed by Peeyush Kumar et al in 2020. The algorithm combines bucket sort, counting sort, radix sort, hashing, and dynamic programming techniques. It employs an n-dimensional Cartesian space mapping approach consisting of two primary phases: a Hashing cycle that maps elements to a multidimensional array using a special hash function, and an Extraction cycle that retrieves elements in sorted order. Recombinant Sort achieves O(n) time complexity for best, average, and worst cases, and can process both numerical and string data types, including mixed decimal and non-decimal numbers.[27]
Quicksort
[edit]Quicksort is a divide-and-conquer algorithm which relies on a partition operation: to partition an array, an element called a pivot is selected.[28][29] All elements smaller than the pivot are moved before it and all greater elements are moved after it. This can be done efficiently in linear time and in-place. The lesser and greater sublists are then recursively sorted. This yields an average time complexity of O(n log n), with low overhead, and thus this is a popular algorithm. Efficient implementations of quicksort (with in-place partitioning) are typically unstable sorts and somewhat complex but are among the fastest sorting algorithms in practice. Together with its modest O(log n) space usage, quicksort is one of the most popular sorting algorithms and is available in many standard programming libraries.
The important caveat about quicksort is that its worst-case performance is O(n2); while this is rare, in naive implementations (choosing the first or last element as pivot) this occurs for sorted data, which is a common case. The most complex issue in quicksort is thus choosing a good pivot element, as consistently poor choices of pivots can result in drastically slower O(n2) performance, but good choice of pivots yields O(n log n) performance, which is asymptotically optimal. For example, if at each step the median is chosen as the pivot then the algorithm works in O(n log n). Finding the median, such as by the median of medians selection algorithm is however an O(n) operation on unsorted lists and therefore exacts significant overhead with sorting. In practice choosing a random pivot almost certainly yields O(n log n) performance.
If a guarantee of O(n log n) performance is important, there is a simple modification to achieve that. The idea, due to Musser, is to set a limit on the maximum depth of recursion.[30] If that limit is exceeded, then sorting is continued using the heapsort algorithm. Musser proposed that the limit should be , which is approximately twice the maximum recursion depth one would expect on average with a randomly ordered array.
Shellsort
[edit]
Shellsort was invented by Donald Shell in 1959.[31] It improves upon insertion sort by moving out of order elements more than one position at a time. The concept behind Shellsort is that insertion sort performs in time, where k is the greatest distance between two out-of-place elements. This means that generally, they perform in O(n2), but for data that is mostly sorted, with only a few elements out of place, they perform faster. So, by first sorting elements far away, and progressively shrinking the gap between the elements to sort, the final sort computes much faster. One implementation can be described as arranging the data sequence in a two-dimensional array and then sorting the columns of the array using insertion sort.
The worst-case time complexity of Shellsort is an open problem and depends on the gap sequence used, with known complexities ranging from O(n2) to O(n4/3) and Θ(n log2 n). This, combined with the fact that Shellsort is in-place, only needs a relatively small amount of code, and does not require use of the call stack, makes it is useful in situations where memory is at a premium, such as in embedded systems and operating system kernels.
Bubble sort and variants
[edit]Bubble sort, and variants such as the Comb sort and cocktail sort, are simple, highly inefficient sorting algorithms. They are frequently seen in introductory texts due to ease of analysis, but they are rarely used in practice.
Bubble sort
[edit]
Bubble sort is a simple sorting algorithm. The algorithm starts at the beginning of the data set. It compares the first two elements, and if the first is greater than the second, it swaps them. It continues doing this for each pair of adjacent elements to the end of the data set. It then starts again with the first two elements, repeating until no swaps have occurred on the last pass.[32] This algorithm's average time and worst-case performance is O(n2), so it is rarely used to sort large, unordered data sets. Bubble sort can be used to sort a small number of items (where its asymptotic inefficiency is not a high penalty). Bubble sort can also be used efficiently on a list of any length that is nearly sorted (that is, the elements are not significantly out of place). For example, if any number of elements are out of place by only one position (e.g. 0123546789 and 1032547698), bubble sort's exchange will get them in order on the first pass, the second pass will find all elements in order, so the sort will take only 2n time.
Comb sort
[edit]Comb sort is a relatively simple sorting algorithm based on bubble sort and originally designed by Włodzimierz Dobosiewicz in 1980.[33] It was later rediscovered and popularized by Stephen Lacey and Richard Box with a Byte Magazine article published in April 1991. The basic idea is to eliminate turtles, or small values near the end of the list, since in a bubble sort these slow the sorting down tremendously. (Rabbits, large values around the beginning of the list, do not pose a problem in bubble sort) It accomplishes this by initially swapping elements that are a certain distance from one another in the array, rather than only swapping elements if they are adjacent to one another, and then shrinking the chosen distance until it is operating as a normal bubble sort. Thus, if Shellsort can be thought of as a generalized version of insertion sort that swaps elements spaced a certain distance away from one another, comb sort can be thought of as the same generalization applied to bubble sort.
Exchange sort
[edit]Exchange sort is sometimes confused with bubble sort, although the algorithms are in fact distinct.[34][35] Exchange sort works by comparing the first element with all elements above it, swapping where needed, thereby guaranteeing that the first element is correct for the final sort order; it then proceeds to do the same for the second element, and so on. It lacks the advantage that bubble sort has of detecting in one pass if the list is already sorted, but it can be faster than bubble sort by a constant factor (one less pass over the data to be sorted; half as many total comparisons) in worst-case situations. Like any simple O(n2) sort it can be reasonably fast over very small data sets, though in general insertion sort will be faster.
Distribution sorts
[edit]Distribution sort refers to any sorting algorithm where data is distributed from their input to multiple intermediate structures which are then gathered and placed on the output. For example, both bucket sort and flashsort are distribution-based sorting algorithms. Distribution sorting algorithms can be used on a single processor, or they can be a distributed algorithm, where individual subsets are separately sorted on different processors, then combined. This allows external sorting of data too large to fit into a single computer's memory.
Counting sort
[edit]Counting sort is applicable when each input is known to belong to a particular set, S, of possibilities. The algorithm runs in O(|S| + n) time and O(|S|) memory where n is the length of the input. It works by creating an integer array of size |S| and using the ith bin to count the occurrences of the ith member of S in the input. Each input is then counted by incrementing the value of its corresponding bin. Afterward, the counting array is looped through to arrange all of the inputs in order. This sorting algorithm often cannot be used because S needs to be reasonably small for the algorithm to be efficient, but it is extremely fast and demonstrates great asymptotic behavior as n increases. It also can be modified to provide stable behavior.
Bucket sort
[edit]Bucket sort is a divide-and-conquer sorting algorithm that generalizes counting sort by partitioning an array into a finite number of buckets. Each bucket is then sorted individually, either using a different sorting algorithm or by recursively applying the bucket sorting algorithm.
A bucket sort works best when the elements of the data set are evenly distributed across all buckets.
Radix sort
[edit]Radix sort is an algorithm that sorts numbers by processing individual digits. n numbers consisting of k digits each are sorted in O(n · k) time. Radix sort can process digits of each number either starting from the least significant digit (LSD) or starting from the most significant digit (MSD). The LSD algorithm first sorts the list by the least significant digit while preserving their relative order using a stable sort. Then it sorts them by the next digit, and so on from the least significant to the most significant, ending up with a sorted list. While the LSD radix sort requires the use of a stable sort, the MSD radix sort algorithm does not (unless stable sorting is desired). In-place MSD radix sort is not stable. It is common for the counting sort algorithm to be used internally by the radix sort. A hybrid sorting approach, such as using insertion sort for small bins, improves performance of radix sort significantly.
Memory usage patterns and index sorting
[edit]When the size of the array to be sorted approaches or exceeds the available primary memory, so that (much slower) disk or swap space must be employed, the memory usage pattern of a sorting algorithm becomes important, and an algorithm that might have been fairly efficient when the array fit easily in RAM may become impractical. In this scenario, the total number of comparisons becomes (relatively) less important, and the number of times sections of memory must be copied or swapped to and from the disk can dominate the performance characteristics of an algorithm. Thus, the number of passes and the localization of comparisons can be more important than the raw number of comparisons, since comparisons of nearby elements to one another happen at system bus speed (or, with caching, even at CPU speed), which, compared to disk speed, is virtually instantaneous.
For example, the popular recursive quicksort algorithm provides quite reasonable performance with adequate RAM, but due to the recursive way that it copies portions of the array it becomes much less practical when the array does not fit in RAM, because it may cause a number of slow copy or move operations to and from disk. In that scenario, another algorithm may be preferable even if it requires more total comparisons.
One way to work around this problem, which works well when complex records (such as in a relational database) are being sorted by a relatively small key field, is to create an index into the array and then sort the index, rather than the entire array. (A sorted version of the entire array can then be produced with one pass, reading from the index, but often even that is unnecessary, as having the sorted index is adequate.) Because the index is much smaller than the entire array, it may fit easily in memory where the entire array would not, effectively eliminating the disk-swapping problem. This procedure is sometimes called "tag sort".[36]
Another technique for overcoming the memory-size problem is using external sorting, for example, one of the ways is to combine two algorithms in a way that takes advantage of the strength of each to improve overall performance. For instance, the array might be subdivided into chunks of a size that will fit in RAM, the contents of each chunk sorted using an efficient algorithm (such as quicksort), and the results merged using a k-way merge similar to that used in merge sort. This is faster than performing either merge sort or quicksort over the entire list.[37][38]
Techniques can also be combined. For sorting very large sets of data that vastly exceed system memory, even the index may need to be sorted using an algorithm or combination of algorithms designed to perform reasonably with virtual memory, i.e., to reduce the amount of swapping required.
Related algorithms
[edit]Related problems include approximate sorting (sorting a sequence to within a certain amount of the correct order), partial sorting (sorting only the k smallest elements of a list, or finding the k smallest elements, but unordered) and selection (computing the kth smallest element). These can be solved inefficiently by a total sort, but more efficient algorithms exist, often derived by generalizing a sorting algorithm. The most notable example is quickselect, which is related to quicksort. Conversely, some sorting algorithms can be derived by repeated application of a selection algorithm; quicksort and quickselect can be seen as the same pivoting move, differing only in whether one recurses on both sides (quicksort, divide-and-conquer) or one side (quickselect, decrease-and-conquer).
A kind of opposite of a sorting algorithm is a shuffling algorithm. These are fundamentally different because they require a source of random numbers. Shuffling can also be implemented by a sorting algorithm, namely by a random sort: assigning a random number to each element of the list and then sorting based on the random numbers. This is generally not done in practice, however, and there is a well-known simple and efficient algorithm for shuffling: the Fisher–Yates shuffle.
Sorting algorithms are ineffective for finding an order in many situations. Usually, when elements have no reliable comparison function (crowdsourced preferences like voting systems), comparisons are very costly (sports), or when it would be impossible to pairwise compare all elements for all criteria (search engines). In these cases, the problem is usually referred to as ranking and the goal is to find the "best" result for some criteria according to probabilities inferred from comparisons or rankings. A common example is in chess, where players are ranked with the Elo rating system, and rankings are determined by a tournament system instead of a sorting algorithm.
There are sorting algorithms for a "noisy" (potentially incorrect) comparator and sorting algorithms for a pair of "fast and dirty" (i.e. "noisy") and "clean" comparators. This can be useful when the full comparison function is costly.[39]
See also
[edit]- Collation – Assembly of written information into a standard order
- K-sorted sequence
- Schwartzian transform – Programming idiom for efficiently sorting a list by a computed key
- Search algorithm – Any algorithm which solves the search problem
- Quantum sort – Sorting algorithms for quantum computers
References
[edit]- ^ "Meet the 'Refrigerator Ladies' Who Programmed the ENIAC". Mental Floss. 2013-10-13. Archived from the original on 2018-10-08. Retrieved 2016-06-16.
- ^ Lohr, Steve (Dec 17, 2001). "Frances E. Holberton, 84, Early Computer Programmer". NYTimes. Archived from the original on 16 December 2014. Retrieved 16 December 2014.
- ^ Demuth, Howard B. (1956). Electronic Data Sorting (PhD thesis). Stanford University. ProQuest 301940891.
- ^ Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2009), "8", Introduction To Algorithms (3rd ed.), Cambridge, MA: The MIT Press, p. 167, ISBN 978-0-262-03293-3
- ^ Ajtai, M.; Komlós, J.; Szemerédi, E. (1983). An O(n log n) sorting network. STOC '83. Proceedings of the fifteenth annual ACM symposium on Theory of computing. pp. 1–9. doi:10.1145/800061.808726. ISBN 0-89791-099-0.
- ^ Kim, P. S.; Kutzner, A. (2008). Ratio Based Stable In-Place Merging. TAMC 2008. Theory and Applications of Models of Computation. LNCS. Vol. 4978. pp. 246–257. CiteSeerX 10.1.1.330.2641. doi:10.1007/978-3-540-79228-4_22. ISBN 978-3-540-79227-7.
- ^ Sedgewick, Robert (1 September 1998). Algorithms In C: Fundamentals, Data Structures, Sorting, Searching, Parts 1-4 (3 ed.). Pearson Education. ISBN 978-81-317-1291-7. Retrieved 27 November 2012.
- ^ Sedgewick, R. (1978). "Implementing Quicksort programs". Comm. ACM. 21 (10): 847–857. doi:10.1145/359619.359631. S2CID 10020756.
- ^ Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2001), "8", Introduction To Algorithms (2nd ed.), Cambridge, MA: The MIT Press, p. 165, ISBN 0-262-03293-7
- ^ Nilsson, Stefan (2000). "The Fastest Sorting Algorithm?". Dr. Dobb's. Archived from the original on 2019-06-08. Retrieved 2015-11-23.
- ^ a b c Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2001) [1990]. Introduction to Algorithms (2nd ed.). MIT Press and McGraw-Hill. ISBN 0-262-03293-7.
- ^ a b Goodrich, Michael T.; Tamassia, Roberto (2002). "4.5 Bucket-Sort and Radix-Sort". Algorithm Design: Foundations, Analysis, and Internet Examples. John Wiley & Sons. pp. 241–243. ISBN 978-0-471-38365-9.
- ^ Fung, Stanley P. Y. (3 October 2021). "Is this the simplest (and most surprising) sorting algorithm ever?". arXiv:2110.01111 [cs.DS].
- ^ Gruber, H.; Holzer, M.; Ruepp, O. (2007), "Sorting the slow way: an analysis of perversely awful randomized sorting algorithms", 4th International Conference on Fun with Algorithms, Castiglioncello, Italy, 2007 (PDF), Lecture Notes in Computer Science, vol. 4475, Springer-Verlag, pp. 183–197, doi:10.1007/978-3-540-72914-3_17, ISBN 978-3-540-72913-6, archived (PDF) from the original on 2020-09-29, retrieved 2020-06-27.
- ^ Franceschini, G. (June 2007). "Sorting Stably, in Place, with O(n log n) Comparisons and O(n) Moves". Theory of Computing Systems. 40 (4): 327–353. doi:10.1007/s00224-006-1311-1.
- ^ a b Thorup, M. (February 2002). "Randomized Sorting in O(n log log n) Time and Linear Space Using Addition, Shift, and Bit-wise Boolean Operations". Journal of Algorithms. 42 (2): 205–230. doi:10.1006/jagm.2002.1211. S2CID 9700543.
- ^ Andersson, Arne; Hagerup, Torben; Nilsson, Stefan; Raman, Rajeev (1995). "Sorting in linear time?". Proceedings of the twenty-seventh annual ACM symposium on Theory of computing. ACM. pp. 427–436.
- ^ Han, Yijie; Thorup, M. (2002). Integer sorting in O(n√(log log n)) expected time and linear space. The 43rd Annual IEEE Symposium on Foundations of Computer Science. pp. 135–144. doi:10.1109/SFCS.2002.1181890. ISBN 0-7695-1822-2.
- ^ Wirth, Niklaus (1986). Algorithms & Data Structures. Upper Saddle River, NJ: Prentice-Hall. pp. 76–77. ISBN 978-0130220059.
- ^ Wirth 1986, pp. 79–80
- ^ Wirth 1986, pp. 101–102
- ^ "Tim Peters's original description of timsort". python.org. Archived from the original on 22 January 2018. Retrieved 14 April 2018.
- ^ "OpenJDK's TimSort.java". java.net. Archived from the original on 14 August 2011. Retrieved 14 April 2018.
- ^ "sort – perldoc.perl.org". perldoc.perl.org. Archived from the original on 14 April 2018. Retrieved 14 April 2018.
- ^ Merge sort in Java 1.3, Sun. Archived 2009-03-04 at the Wayback Machine
- ^ Wirth 1986, pp. 87–89
- ^ Kumar, Peeyush; Gangal, Ayushe; Kumari, Sunita (2020), "Recombinant Sort: N-Dimensional Cartesian Spaced Algorithm Designed from Synergetic Combination of Hashing, Bucket, Counting and Radix Sort", Ingénierie des Systèmes D Information, 25 (5): 655–668, arXiv:2107.01391, doi:10.18280/isi.250513
- ^ Wirth 1986, p. 93
- ^ Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2009), Introduction to Algorithms (3rd ed.), Cambridge, MA: The MIT Press, pp. 171–172, ISBN 978-0262033848
- ^ Musser, David R. (1997), "Introspective Sorting and Selection Algorithms", Software: Practice and Experience, 27 (8): 983–993, doi:10.1002/(SICI)1097-024X(199708)27:8<983::AID-SPE117>3.0.CO;2-#
- ^ Shell, D. L. (1959). "A High-Speed Sorting Procedure" (PDF). Communications of the ACM. 2 (7): 30–32. doi:10.1145/368370.368387. S2CID 28572656. Archived from the original (PDF) on 2017-08-30. Retrieved 2020-03-23.
- ^ Wirth 1986, pp. 81–82
- ^ Brejová, B. (15 September 2001). "Analyzing variants of Shellsort". Inf. Process. Lett. 79 (5): 223–227. doi:10.1016/S0020-0190(00)00223-4.
- ^ "Exchange Sort Algorithm". CodingUnit Programming Tutorials. Archived from the original on 2021-07-10. Retrieved 2021-07-10.
- ^ "Exchange Sort". JavaBitsNotebook.com. Archived from the original on 2021-07-10. Retrieved 2021-07-10.
- ^ "tag sort Definition from PC Magazine Encyclopedia". Pcmag.com. Archived from the original on 6 October 2012. Retrieved 14 April 2018.
- ^ Donald Knuth, The Art of Computer Programming, Volume 3: Sorting and Searching, Second Edition. Addison-Wesley, 1998, ISBN 0-201-89685-0, Section 5.4: External Sorting, pp. 248–379.
- ^ Ellis Horowitz and Sartaj Sahni, Fundamentals of Data Structures, H. Freeman & Co., ISBN 0-7167-8042-9.
- ^ Bai, Xingjian; Coester, Christian (2023). Sorting with Predictions. NeurIPS. p. 5.
Further reading
[edit]- Knuth, Donald E. (1998), Sorting and Searching, The Art of Computer Programming, vol. 3 (2nd ed.), Boston: Addison-Wesley, ISBN 0-201-89685-0
- Sedgewick, Robert (1980), "Efficient Sorting by Computer: An Introduction", Computational Probability, New York: Academic Press, pp. 101–130, ISBN 0-12-394680-8
External links
[edit]- Sorting Algorithm Animations at the Wayback Machine (archived 3 March 2015).
- Sequential and parallel sorting algorithms – Explanations and analyses of many sorting algorithms.
- Dictionary of Algorithms, Data Structures, and Problems – Dictionary of algorithms, techniques, common functions, and problems.
- Slightly Skeptical View on Sorting Algorithms – Discusses several classic algorithms and promotes alternatives to the quicksort algorithm.
- 15 Sorting Algorithms in 6 Minutes (Youtube) – Visualization and "audibilization" of 15 Sorting Algorithms in 6 Minutes.
- A036604 sequence in OEIS database titled "Sorting numbers: minimal number of comparisons needed to sort n elements" – Performed by Ford–Johnson algorithm.
- XiSort – External merge sort with symbolic key transformation – A variant of merge sort applied to large datasets using symbolic techniques.
- XiSort reference implementation – C/C++ Library of the xisort algorithm in reference
- Sorting Algorithms Used on Famous Paintings (Youtube) – Visualization of Sorting Algorithms on Many Famous Paintings.
- A Comparison of Sorting Algorithms – Runs a series of tests of 9 of the main sorting algorithms using Python timeit and Google Colab.
Sorting algorithm
View on GrokipediaFundamentals
Definition and Purpose
A sorting algorithm is a procedure that rearranges the elements of a list or array into a specified order, typically ascending or descending based on some comparison criterion.[9] The input is generally an unsorted collection of elements, such as an array of comparable items, and the output is the same collection reordered such that each element is positioned relative to its peers according to the defined ordering.[10] This process ensures that the relative positions of elements satisfy the ordering relation, often numerical for values like integers or lexicographical for strings.[11] The primary purpose of sorting algorithms is to facilitate efficient data organization and retrieval in computing systems, serving as a foundational operation for numerous higher-level tasks.[12] By arranging data in a predictable order, sorting enables optimized searching techniques, such as binary search, which requires a sorted input to achieve logarithmic-time performance rather than linear scanning.[13] It also supports data organization by grouping similar elements or prioritizing items, which is essential for processing large datasets in a structured manner.[14] Sorting algorithms commonly operate on diverse data types, including primitive values like numbers (integers or floats), strings, and complex objects sorted by designated keys such as names or identifiers.[15] In real-world applications, they underpin database queries by organizing records for fast retrieval via indexed keys, manage file systems through ordering files by attributes like name or modification date, and aid UI rendering by sequencing elements for display based on relevance or position.[14] These uses highlight sorting's role in enhancing overall system efficiency, though the choice of algorithm impacts performance metrics like time complexity, which are evaluated separately.[16]Performance Metrics
Performance metrics for sorting algorithms primarily revolve around time and space complexity, which quantify efficiency in terms of computational resources required as a function of input size , typically assuming an array of distinct elements under random or adversarial ordering. Time complexity is expressed using Big O notation to describe the number of basic operations (such as comparisons or swaps) in the worst case (maximum over all inputs), average case (expected over random inputs), and best case (minimum over all inputs). For comparison-based sorting algorithms, which rely solely on pairwise comparisons between elements, a fundamental lower bound of comparisons exists in the worst case, derived from the information-theoretic argument that distinguishing among possible permutations requires at least bits of information, approximated by Stirling's formula as . [17] [18] Space complexity measures the auxiliary memory used beyond the input array, often categorized as in-place (requiring extra space) or out-of-place (needing or more). In-place algorithms modify the input directly, minimizing memory overhead, while others may use temporary arrays for merging or partitioning. Other key metrics include stability, which ensures that equal elements retain their relative order from the input (detailed further in classification properties); adaptivity, where algorithms exploit partial order in the input to achieve better-than-worst-case performance, such as time where is the number of inversions[1]; and in-place capability, emphasizing low auxiliary space. [19] [20] Analysis of these metrics can be theoretical, providing asymptotic bounds independent of hardware via models like the RAM model, or empirical, involving timed executions on real datasets to reveal practical constants and hardware dependencies, such as cache effects not captured in Big O. Theoretical analysis establishes universal guarantees, like the bound, while empirical studies validate these under specific conditions, often showing deviations for small or non-uniform distributions. [21]Historical Development
Early Algorithms
Before the advent of electronic computers, sorting large datasets relied on manual and mechanical methods that emphasized physical organization of information. In the 19th century, libraries commonly employed card-based systems where librarians handwrote bibliographic details on index cards, typically measuring 3 by 5 inches, and sorted them into drawers by author, title, or subject to facilitate access to growing collections.[22] This process, known as manual card sorting, required extensive human labor to alphabetize or categorize cards, often involving repeated physical rearrangements to maintain order as new materials were added. Such techniques, while effective for smaller scales, became increasingly cumbersome for expansive archives, highlighting the need for more efficient tools. The transition to mechanical aids began in the late 19th century with Herman Hollerith's invention of the tabulating machine for the 1890 U.S. Census. Hollerith's system used punched cards to encode demographic data, which were then processed by electrically operated tabulators and sorters that tallied and grouped information by reading holes punched in specific columns.[23] The sorters, featuring spring-loaded pins and rods, allowed operators to mechanically select and stack cards based on predefined criteria, reducing the census tabulation time from an estimated decade to just months.[24] Despite these advances, the process still demanded significant manual intervention, including card punching and machine setup, and lacked standardized theoretical underpinnings for optimization. The emergence of electronic computing in the mid-20th century marked the first algorithmic approaches to sorting. In 1945, mathematician John von Neumann developed an early merge sort routine as part of programming efforts for the ENIAC, the pioneering electronic computer completed that year, to handle numerical data ordering for scientific computations. This method involved dividing data into subsets, sorting them recursively, and merging results, providing a foundational blueprint for automated sorting on stored-program machines. By the 1950s, initial implementations appeared on commercial systems like the UNIVAC I, where engineers adapted merge-based techniques to process business and scientific data tapes, though programming remained labor-intensive due to machine-specific wiring and limited memory. These early efforts were constrained by high levels of manual preparation, such as data entry via punched cards, and the absence of formal analysis on efficiency or scalability, setting the stage for theoretical developments in the mid-century.[25]Key Milestones
In the 1960s and early 1970s, significant theoretical advancements formalized the analysis of sorting algorithms, with Donald Knuth's seminal work providing rigorous complexity evaluations. In The Art of Computer Programming, Volume 3: Sorting and Searching (1973), Knuth systematically analyzed various sorting methods, establishing average- and worst-case time complexities for algorithms like insertion sort and mergesort, and highlighting their practical trade-offs.[26] This volume became a cornerstone for understanding sorting efficiency, influencing subsequent research by emphasizing mathematical rigor over empirical testing alone.[26] A key theoretical milestone from this era was the establishment of the information-theoretic lower bound for comparison-based sorting algorithms, proving that any such method requires at least Ω(n log n) comparisons in the worst case. This bound, derived from the decision tree model where the algorithm's comparisons form a binary tree with at least n! leaves (one for each permutation), was formalized in the context of modern computing during the 1950s and 1960s, with Knuth providing detailed proofs and implications in his 1973 analysis.[26] The result underscored that no comparison sort could asymptotically outperform this barrier, guiding the design of optimal algorithms. The 1970s and 1980s saw practical refinements to quicksort, originally invented by C. A. R. Hoare in 1960 and published in 1961, focusing on optimizations to mitigate its worst-case O(n²) performance. Robert Sedgewick's 1975 study introduced median-of-three pivoting and other partitioning improvements, reducing average-case comparisons and enhancing robustness against poor inputs, as demonstrated through extensive simulations on real hardware.[27] Further advancements in the 1980s, such as Jon Bentley's adaptations for small subarrays and early termination heuristics, solidified quicksort's dominance in general-purpose libraries due to its in-place efficiency and cache-friendly behavior.[28] By the late 1990s, hybrid approaches addressed quicksort's remaining vulnerabilities, with introsort emerging as a breakthrough in guaranteed performance. Developed by David Musser in 1997, introsort combines quicksort's average-case speed with heapsort's O(n log n) worst-case guarantee by monitoring recursion depth and switching strategies when it exceeds logarithmic limits, achieving optimal asymptotic behavior while preserving practical speed.[29] This innovation was quickly adopted in standard libraries, such as the C++ Standard Template Library, for its balance of simplicity and reliability.[29] In the 2000s, practical impacts extended to adaptive hybrids tailored for real-world data, exemplified by Timsort's invention and widespread adoption. Created by Tim Peters in 2002 for Python's list sorting, Timsort merges natural runs in the input with a modified mergesort, achieving O(n log n) worst-case time while excelling on partially sorted data common in applications like spreadsheets. Its efficiency led to inclusion in Java's Arrays.sort from JDK 7 (2011) and other platforms, demonstrating how domain-specific optimizations could outperform general theoretical bounds in production environments. From the 2020s onward, quantum computing has inspired theoretical proposals for sorting, though they remain impractical due to hardware limitations. Algorithms like quantum mergesort, building on earlier ideas from Dürr and Høyer (2002), promise O(n log n) complexity using quantum parallelism, but recent analyses (2020–2025) confirm they require fault-tolerant qubits far beyond current noisy intermediate-scale systems, with no scalable implementations achieved. These efforts highlight sorting's role in benchmarking quantum advantage, yet classical hybrids continue to dominate practical use.[30]Classification
Comparison-Based Sorts
Comparison-based sorting algorithms determine the relative order of elements in a collection solely through pairwise comparisons, typically using relational operators such as less than (<), greater than (>), or equal (=) to decide how to rearrange them. These comparisons reveal whether one element precedes, follows, or matches another in the desired ordering, without relying on any additional properties of the data beyond the ability to compare pairs. This mechanism forms the foundation of the algorithm's decision-making process, where each comparison outcome guides the next step in rearranging the elements until the entire sequence is sorted.[31][32] The theoretical analysis of comparison-based sorts often employs a decision tree model to capture their behavior. In this model, each internal node represents a comparison between two elements, with branches extending from the node for each possible outcome—commonly modeled as binary (e.g., less than or not) for simplicity, though ternary outcomes are possible with equality. The paths from the root to the leaves traverse a sequence of comparisons, and each leaf corresponds to a specific permutation of the input elements, indicating the sorted order. For n distinct elements, there are exactly n! possible permutations, so the decision tree must have at least n! leaves to distinguish all possible input orderings.[17][18] The height of this decision tree represents the worst-case number of comparisons required by the sorting algorithm. Since a binary decision tree with height h can have at most leaves, to accommodate at least n! leaves requires . Using Stirling's approximation, , which simplifies to . Therefore, the minimum height is , establishing that any comparison-based sorting algorithm must perform at least comparisons in the worst case. This information-theoretic lower bound arises because each comparison provides at most 1 bit of information, and distinguishing among n! permutations requires at least bits. The proof assumes distinct elements and a total order, but it holds generally for the worst-case analysis of such algorithms.[33][18][17] A key advantage of comparison-based sorts is their generality: they apply to any data type that supports a comparison operation defining a total order, without needing prior knowledge of the elements' range, distribution, or structure. This makes them suitable for sorting arbitrary comparable objects, such as numbers, strings, or custom types with user-defined comparators. Comparison-based sorting algorithms are often classified into categories like exchange sorts, which identify and swap misplaced pairs of elements, and selection sorts, which iteratively find and position the next element in order. These categories encompass a wide range of implementations, all bound by the lower limit.[34][35]Non-Comparison-Based Sorts
Non-comparison-based sorting algorithms operate by exploiting specific properties of the input data, such as discrete values or fixed representations, rather than relying on pairwise comparisons between elements. These methods typically employ techniques like counting occurrences of values, distributing elements into buckets based on ranges, or extracting and sorting digits individually to determine the order. For instance, counting mechanisms tally the frequency of each possible key value in an auxiliary array, which is then used to place elements in their correct positions without direct comparisons.[36] Bucketing involves partitioning the input into evenly spaced intervals or "buckets" and collecting elements from these buckets in sequence, assuming a uniform distribution across the range.[37] Digit extraction, often applied to integers or fixed-length keys, processes the data digit by digit, sorting each position separately using a stable subroutine like counting.[38] These algorithms make strong assumptions about the input to achieve efficiency. They generally require elements to be integers within a fixed range, such as from 0 to k where k is not excessively large relative to the input size n, or to follow a uniform distribution that allows predictable bucketing.[36] For digit-based methods, keys must have a bounded number of digits or bits, enabling iterative passes without exponential growth in processing time.[39] Without these constraints, such as when dealing with arbitrary real numbers or unbounded keys, the algorithms become impractical due to excessive space or time requirements.[38] A primary advantage of non-comparison-based sorts is their potential to achieve linear O(n + k) time complexity, where k represents the range or number of buckets/digits, surpassing the typical Ω(n log n) performance of general comparison sorts under favorable conditions.[36] This makes them particularly efficient for large datasets with known bounds, such as sorting integers in databases or fixed-length strings in indexing systems. However, their limitations are significant: they are not adaptable to arbitrary data types or distributions, often requiring additional preprocessing or failing entirely on unsuited inputs, and they can consume substantial auxiliary space proportional to the key range.[37] For example, if k >> n, the space overhead dominates, rendering the sort inefficient.[38] Hybrid approaches enhance versatility by integrating non-comparison methods with comparison-based ones, such as using counting or bucketing as a preprocessing step to partition data before applying a general sort like quicksort on subgroups.[39] This combination leverages the linear-time strengths of non-comparative techniques on restricted subsets while falling back to robust comparison sorts for the rest, improving overall performance in mixed scenarios. Radix sort exemplifies this by repeatedly applying a stable counting sort across digits.[36]Stability and Other Properties
A sorting algorithm is stable if it preserves the relative order of elements with equal keys from the input to the output, ensuring that equivalent elements maintain their original sequence.[40] This property is crucial in applications involving multi-key sorting, where data is sorted by multiple criteria sequentially; for instance, sorting transaction records first by amount and then by date using a stable algorithm ensures that records with equal amounts remain in their date-ordered positions, avoiding unintended rearrangements.[14] Stability facilitates such hierarchical sorting without requiring additional keys to track original positions.[41] Beyond stability, sorting algorithms exhibit other important properties that influence their suitability for specific scenarios. An in-place algorithm operates using only a constant amount of extra memory beyond the input array, typically O(1) additional space, making it memory-efficient for large datasets.[2] Adaptive algorithms perform better on partially sorted inputs by detecting and leveraging existing order, such as runs of sorted elements, to reduce comparisons or swaps compared to fully random data.[40] Online algorithms process elements as they arrive incrementally, without needing the entire input upfront, which is useful in streaming or real-time data environments.[40] These properties often involve trade-offs; for example, achieving stability frequently requires auxiliary space to track relative orders, whereas in-place sorts may sacrifice stability to minimize memory usage.[10] Adaptive and online behaviors can enhance efficiency on non-random inputs but may not improve worst-case performance.[42] To measure stability, one standard approach is to augment input elements with their original indices to create unique tuples (value, index), sort using the value as the key while ignoring the index for comparisons, and then verify that indices for equal values remain non-decreasing in the output.[43] This test confirms whether the algorithm preserves relative ordering without altering the sorting logic.[44]Simple Sorting Algorithms
Insertion Sort
Insertion sort is a simple comparison-based sorting algorithm that builds the final sorted array one item at a time by iteratively taking an element from the input data and inserting it into its correct position in the already-sorted portion of the array.[45] It resembles the process of sorting a hand of playing cards, where each new card is inserted into the appropriate spot among the previously sorted cards.[46] The algorithm maintains two subarrays: a sorted subarray starting from the beginning and an unsorted subarray from which elements are successively inserted into the sorted portion.[45] The following pseudocode illustrates the insertion sort procedure for an array of elements (1-based indexing, as in the standard description):INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
i = j - 1
while i > 0 and A[i] > key
A[i + 1] = A[i]
i = i - 1
A[i + 1] = key
INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
i = j - 1
while i > 0 and A[i] > key
A[i + 1] = A[i]
i = i - 1
A[i + 1] = key
- Initialize: Sorted subarray is {{grok:render&&&type=render_inline_citation&&&citation_id=3&&&citation_type=wikipedia}}; unsorted is .
- Insert 1: Compare with 3 (greater), shift 3 right, place 1 at start → .
- Insert 4: Compare with 3 (smaller? No, 3 < 4), no shift → .
- Insert 1: Compare with 4 (>1, shift), 3 (>1, shift), 1 (not >1, stop), place 1 after first 1 → .
- Insert 5: Compare with 4 (<5), no shift → .
BINARY-INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
// Binary search to find position low in A[1..j-1]
low = 1; high = j-1
while low <= high
mid = floor((low + high)/2)
if A[mid] <= key
low = mid + 1
else
high = mid - 1
// Shift A[low..j-1] right by one
for i = j down to low+1
A[i] = A[i-1]
A[low] = key
BINARY-INSERTION-SORT(A)
for j = 2 to A.length
key = A[j]
// Binary search to find position low in A[1..j-1]
low = 1; high = j-1
while low <= high
mid = floor((low + high)/2)
if A[mid] <= key
low = mid + 1
else
high = mid - 1
// Shift A[low..j-1] right by one
for i = j down to low+1
A[i] = A[i-1]
A[low] = key
Selection Sort
Selection sort is a straightforward in-place comparison-based sorting algorithm that constructs the sorted array incrementally by repeatedly selecting the smallest (or largest, for descending order) element from the unsorted portion and placing it at the end of the sorted portion. The algorithm conceptually divides the input array into a sorted subarray, which initially holds the first element, and an unsorted subarray comprising the remaining elements. In each pass, it scans the unsorted subarray to identify the minimum element and swaps it with the leftmost element of the unsorted subarray, effectively extending the sorted subarray by one position until the entire array is sorted. This process ensures that after k iterations, the first k elements are in sorted order. The pseudocode for selection sort, assuming 1-based indexing, is as follows:SELECTION-SORT(A)
n ← length[A]
for i ← 1 to n - 1
min ← i
for j ← i + 1 to n
if A[j] < A[min]
min ← j
exchange A[i] with A[min]
SELECTION-SORT(A)
n ← length[A]
for i ← 1 to n - 1
min ← i
for j ← i + 1 to n
if A[j] < A[min]
min ← j
exchange A[i] with A[min]
- Iteration 1 (i=1): Minimum is 11 at index 5; swap with 64 → [11, 25, 12, 22, 64]
- Iteration 2 (i=2): Minimum is 12 at index 3; swap with 25 → [11, 12, 25, 22, 64]
- Iteration 3 (i=3): Minimum is 22 at index 5; swap with 25 → [11, 12, 22, 25, 64]
- Iteration 4 (i=4): Minimum is 25 at index 4; no swap → [11, 12, 22, 25, 64]
Bubble Sort
Bubble sort is a simple comparison-based sorting algorithm that repeatedly iterates through the list to be sorted, comparing each pair of adjacent elements and swapping them if they are in the wrong order.[50] This process "bubbles" the largest unsorted element to its correct position at the end of the list in each pass, with subsequent passes handling progressively smaller unsorted sublists.[51] Although inefficient for large datasets, bubble sort serves as an educational tool for understanding basic sorting principles due to its straightforward implementation.[52] The standard pseudocode for bubble sort is as follows:for i from 0 to n-2
for j from 0 to n-i-2
if A[j] > A[j+1]
swap A[j] and A[j+1]
end
for i from 0 to n-2
for j from 0 to n-i-2
if A[j] > A[j+1]
swap A[j] and A[j+1]
end
A of length n and sorts in non-decreasing order.[51]
In terms of time complexity, bubble sort requires Θ(n²) comparisons and swaps in the worst and average cases, as it performs a fixed number of passes regardless of the input arrangement.[51] The best-case time complexity is Θ(n) when the list is already sorted, achievable with an optimization that tracks swaps and terminates early if none occur in a pass.[50] Space complexity is O(1), making it an in-place algorithm that requires only a constant amount of extra space for temporary variables during swaps.[52] Bubble sort can be implemented in a stable manner, preserving the relative order of equal elements since swaps only occur for strictly greater comparisons.[51]
Common optimizations include reducing the inner loop's range after each pass by the number of elements already sorted at the end, which cuts unnecessary comparisons by about 50% in the worst case.[50] Another variant, known as cocktail shaker sort, improves efficiency on certain inputs by alternating the direction of passes—bubbling large elements to the end in one direction and small elements to the beginning in the reverse—thus potentially reducing the number of passes needed.[53]
To illustrate, consider tracing bubble sort on the array [8, 5, 1]:
- Initial: [8, 5, 1]
- Pass 1: Compare 8 and 5 (swap) → [5, 8, 1]; compare 8 and 1 (swap) → [5, 1, 8]
- Pass 2: Compare 5 and 1 (swap) → [1, 5, 8]; compare 5 and 8 (no swap) → [1, 5, 8]
Efficient Comparison-Based Sorts
Merge Sort
Merge sort is a divide-and-conquer sorting algorithm that recursively divides an array into halves, sorts each half, and then merges the sorted halves into a single sorted array.[55] It was invented by John von Neumann in 1945 as part of early computer design considerations for the EDVAC machine.[56] This approach ensures a consistent performance regardless of the input data distribution, making it particularly reliable for large datasets. The algorithm operates by first dividing the input array into two roughly equal subarrays until each subarray contains a single element, which is inherently sorted. It then merges these subarrays pairwise, starting from the smallest ones, to produce progressively larger sorted subarrays. The merge step is crucial, as it combines two sorted arrays into one while preserving the relative order of equal elements, ensuring stability. Merge sort is stable, meaning that if two elements are equal, their original order is maintained in the sorted output.[56] Here is the standard recursive pseudocode for merge sort:MERGE_SORT(A, low, high)
if low < high
mid = floor((low + high) / 2)
MERGE_SORT(A, low, mid)
MERGE_SORT(A, mid + 1, high)
MERGE(A, low, mid, high)
MERGE_SORT(A, low, high)
if low < high
mid = floor((low + high) / 2)
MERGE_SORT(A, low, mid)
MERGE_SORT(A, mid + 1, high)
MERGE(A, low, mid, high)
MERGE procedure takes two sorted subarrays—A[low..mid] and A[mid+1..high]—and merges them into a single sorted subarray A[low..high]. It uses two pointers, one for each subarray, advancing the pointer of the subarray with the smaller current element and copying that element to the output. If one subarray is exhausted, the remaining elements from the other are appended. This process requires a temporary array of size O(n) to hold the merged result before copying back to the original array.
In terms of time complexity, merge sort achieves O(n log n) in the best, average, and worst cases due to the balanced recursion tree, where the divide step takes constant time, the conquer step doubles the subproblem size, and the merge step across all levels totals O(n log n).[56] It requires O(n) extra space for the temporary arrays, making it not in-place, though optimizations can reduce constants.
To illustrate, consider sorting the array [38, 27, 43, 3, 9, 82, 10]. The recursion divides it into [38, 27, 43, 3] and [9, 82, 10], further into [38, 27] and [43, 3], then [57] and [58]; [59] and [60]; and so on, down to single elements. Merging builds up: [27, 38] from [57] and [58]; [3, 43] from [59] and [60]; then [3, 27, 38, 43] from those; similarly for the right half to [9, 10, 82]; finally merging to [3, 9, 10, 27, 38, 43, 82]. This can be visualized as a binary tree where leaves are single elements, internal nodes represent merges, and the root is the full sorted array.
An alternative bottom-up variant avoids recursion by starting with subarrays of size 1 and iteratively merging adjacent pairs into larger sorted subarrays, doubling the size each pass until the entire array is sorted. This iterative approach can be more space-efficient in practice for very large arrays and is often used in external sorting scenarios where data does not fit in memory.[56]
Quicksort
Quicksort is a divide-and-conquer comparison-based sorting algorithm invented by British computer scientist C. A. R. Hoare while working at Elliott Brothers (London) Ltd.[61] Published in 1961 as Algorithm 64, it selects a pivot element from the array and partitions the remaining elements into two subarrays: one containing elements less than or equal to the pivot and the other containing elements greater than or equal to the pivot (with equals possibly on either side).[61] The subarrays are then recursively sorted, achieving efficient performance on average by reducing the problem size at each step.[27] The algorithm's core is the partitioning step, which rearranges the array such that, after the pointers cross, all elements to the left of the returned index are less than or equal to the pivot value and all to the right are greater than or equal to the pivot value, though the pivot element itself may be anywhere in the array and not yet in its final sorted position. Hoare's original implementation uses a two-pointer technique starting from the ends of the subarray, swapping elements that are out of order relative to the pivot until the pointers cross.[61] This in-place partitioning minimizes space usage, making quicksort suitable for large datasets in random-access memory.[62] Here is pseudocode for quicksort using an adapted version of Hoare's original partitioning scheme:procedure partition([array](/page/Array) A, low, high):
pivot := A[low] // Typically the first element, but can vary
i := low - 1
j := high + 1
while true:
i := i + 1
while A[i] < pivot:
i := i + 1
j := j - 1
while A[j] > pivot:
j := j - 1
if i >= j:
return j
swap A[i] and A[j]
procedure quicksort([array](/page/Array) A, low, high):
if low < high:
pivot_index := partition(A, low, high)
quicksort(A, low, pivot_index)
quicksort(A, pivot_index + 1, high)
procedure partition([array](/page/Array) A, low, high):
pivot := A[low] // Typically the first element, but can vary
i := low - 1
j := high + 1
while true:
i := i + 1
while A[i] < pivot:
i := i + 1
j := j - 1
while A[j] > pivot:
j := j - 1
if i >= j:
return j
swap A[i] and A[j]
procedure quicksort([array](/page/Array) A, low, high):
if low < high:
pivot_index := partition(A, low, high)
quicksort(A, low, pivot_index)
quicksort(A, pivot_index + 1, high)
- Initial array: [4, 2, 7, 1, 3, 6, 5]
- Partition: After swaps, get [3, 2, 1, 7, 4, 6, 5] (returned index 2; left subarray 0-2: all ≤4, right 3-6: all ≥4, with pivot 4 now at index 4)
- Recurse on left [3, 2, 1]: Pivot 3 → partition to [2, 1, 3]
- Recurse on right [7, 4, 6, 5]: Pivot 7 → partition to [4, 6, 5, 7] (further recursion sorts to [4, 5, 6, 7])
- Final sorted: [1, 2, 3, 4, 5, 6, 7]
Heapsort
Heapsort is a comparison-based sorting algorithm that utilizes a binary heap data structure to achieve an efficient sorting process. Invented by J. W. J. Williams in 1964, it constructs a max-heap from the input array and then iteratively extracts the maximum element, placing it at the end of the array while maintaining the heap property on the reduced heap.[63] This approach ensures a worst-case time complexity of O(n log n), making it suitable for scenarios requiring guaranteed performance bounds without the variability seen in algorithms like quicksort. The algorithm operates in two main phases: heap construction and extraction. First, it builds a max-heap, where the parent node in the binary tree representation is greater than or equal to its children, ensuring the root holds the maximum value. This construction starts from the middle of the array and calls a heapify procedure on each subtree to enforce the heap property. Once the max-heap is built, the sorting phase swaps the root (maximum) with the last unsorted element, reduces the heap size, and heapifies the root to restore the property. This process repeats until the heap is empty, resulting in a sorted array in non-decreasing order. The heapify operation is central to heapsort, as it maintains the max-heap property for a subtree rooted at a given node. It compares the node with its left and right children, identifies the largest among them, and if a child is larger than the parent, swaps the parent with that child and recursively heapifies the affected subtree. This sifting-down process ensures the heap invariant is preserved efficiently. The following pseudocode outlines the key procedures, assuming a 1-based array indexing for the heap representation where parent of index i is floor(i/2), left child is 2i, and right child is 2i+1: MAX-HEAPIFY(A, i)l = LEFT(i)
r = RIGHT(i)
if l ≤ heap-size[A] and A[l] > A[i]
then largest = l
else largest = i
if r ≤ heap-size[A] and A[r] > A[largest]
then largest = r
if largest ≠ i
then exchange A[i] ↔ A[largest]
MAX-HEAPIFY(A, largest)
l = LEFT(i)
r = RIGHT(i)
if l ≤ heap-size[A] and A[l] > A[i]
then largest = l
else largest = i
if r ≤ heap-size[A] and A[r] > A[largest]
then largest = r
if largest ≠ i
then exchange A[i] ↔ A[largest]
MAX-HEAPIFY(A, largest)
heap-size[A] = length[A]
for i = floor(length[A]/2) downto 1
MAX-HEAPIFY(A, i)
heap-size[A] = length[A]
for i = floor(length[A]/2) downto 1
MAX-HEAPIFY(A, i)
BUILD-MAX-HEAP(A)
for i = length[A] downto 2
exchange A[1] ↔ A[i]
heap-size[A] = heap-size[A] - 1
MAX-HEAPIFY(A, 1)
BUILD-MAX-HEAP(A)
for i = length[A] downto 2
exchange A[1] ↔ A[i]
heap-size[A] = heap-size[A] - 1
MAX-HEAPIFY(A, 1)
Shellsort
Shellsort is a comparison-based sorting algorithm that extends the insertion sort by sorting elements that are far apart before addressing closer ones, using a sequence of decreasing gaps to improve efficiency. Invented by Donald L. Shell in 1959, it applies insertion sort to multiple interleaved subarrays defined by these gaps, starting with a large gap and reducing it until the gap reaches 1, at which point it performs a standard insertion sort.[64] This approach allows Shellsort to achieve better average-case performance than simple insertion sort by reducing the total number of element shifts required, as distant inversions are resolved early in the process.[64] The algorithm's pseudocode can be described as follows, where the gaps are generated from a predefined sequence (e.g., starting with and halving until ):function [shellSort](/page/Shellsort)(array A of size n):
for h in gap_sequence(n):
for i from h to n-1:
temp = A[i]
j = i
while j >= h and A[j - h] > temp:
A[j] = A[j - h]
j = j - h
A[j] = temp
function [shellSort](/page/Shellsort)(array A of size n):
for h in gap_sequence(n):
for i from h to n-1:
temp = A[i]
j = i
while j >= h and A[j - h] > temp:
A[j] = A[j - h]
j = j - h
A[j] = temp
Non-Comparison-Based Sorts
Counting Sort
Counting sort is a non-comparison-based sorting algorithm that efficiently sorts a collection of integers within a known small range by counting the frequency of each distinct value and using these counts to place elements in their correct positions.[68] Developed by Harold H. Seward in 1954 as part of his work on information sorting for electronic digital computers, the algorithm operates in linear time relative to the input size and the range of values, making it particularly suitable for scenarios where the keys are limited to a small set of possible integers.[68] The process begins by creating an auxiliary array to store the count of each possible value in the input. For an input array of elements with values ranging from 0 to , initialize a count array of size to zeros. Then, iterate through and increment for each element . To produce the sorted output, modify to store cumulative counts: for each index from 1 to , set . Finally, create an output array of size , and starting from the end of , place each element at position and decrement , ensuring stability by processing in reverse order.[69]procedure countingSort(A, B, k)
C = [array](/page/Array) of k zeros
for i = 0 to n-1
C[A[i]] += 1
for i = 1 to k-1
C[i] += C[i-1]
for i = n-1 downto 0
B[C[A[i]] - 1] = A[i]
C[A[i]] -= 1
procedure countingSort(A, B, k)
C = [array](/page/Array) of k zeros
for i = 0 to n-1
C[A[i]] += 1
for i = 1 to k-1
C[i] += C[i-1]
for i = n-1 downto 0
B[C[A[i]] - 1] = A[i]
C[A[i]] -= 1
Radix Sort
Radix sort is a non-comparison-based sorting algorithm that processes elements by distributing them based on individual digits or characters, typically using a stable subroutine like counting sort for each pass. It achieves linear time complexity for fixed-length keys by iteratively sorting from the least significant digit (LSD) or most significant digit (MSD), preserving the order from previous passes due to the stability of the subroutine.[72] The algorithm traces its origins to Herman Hollerith's 1889 patent for an electric tabulating system, which described an MSD approach for sorting punched cards in census data processing.[73] A formal LSD variant was later detailed by Edward H. Friend in his 1956 paper on sorting for electronic computers. In the LSD radix sort, elements are sorted starting from the rightmost digit (least significant) and proceeding leftward to the most significant digit. Each iteration applies a stable sort to the current digit position, ensuring that elements with equal values in that digit retain their relative order from prior sorts. The pseudocode for LSD radix sort on an array of elements with digits in base is as follows:for position = 0 to d-1:
stable_sort(A, position) // sort by digit at position (0 = least significant)
for position = 0 to d-1:
stable_sort(A, position) // sort by digit at position (0 = least significant)
- Units digit pass: Distributes to [170, 090, 802, 002, 024, 045, 075, 066], resulting in [170, 90, 802, 2, 24, 45, 75, 66].
- Tens digit pass (stable): Distributes to preserve units order, yielding [802, 2, 24, 45, 66, 170, 75, 90].
- Hundreds digit pass (stable): Distributes to [2, 24, 45, 66, 75, 90, 170, 802].
Bucket Sort
Bucket sort is a distribution-based sorting algorithm designed for inputs consisting of real numbers uniformly distributed in the interval [0, 1). The algorithm partitions the n input elements into n initially empty buckets, where each bucket corresponds to a subinterval of length 1/n within [0, 1). Each element is assigned to its bucket by computing the hash value , which determines the bucket index. After all elements are distributed into linked lists within their respective buckets, each non-empty bucket is sorted individually using a comparison-based algorithm such as insertion sort. Finally, the sorted contents of the buckets are concatenated in order to produce the fully sorted array. The following pseudocode illustrates the bucket sort procedure:BUCKET-SORT(A)
n ← length[A]
b ← n // Number of buckets equals input size
create empty lists bucket[0..b-1]
for i ← 1 to n
do hash ← ⌊n · A[i]⌋
insert A[i] at the end of bucket[hash]
for i ← 0 to b-1
do sort bucket[i] using [insertion sort](/page/Insertion_sort)
return the concatenation of bucket[0], bucket[1], ..., bucket[b-1]
BUCKET-SORT(A)
n ← length[A]
b ← n // Number of buckets equals input size
create empty lists bucket[0..b-1]
for i ← 1 to n
do hash ← ⌊n · A[i]⌋
insert A[i] at the end of bucket[hash]
for i ← 0 to b-1
do sort bucket[i] using [insertion sort](/page/Insertion_sort)
return the concatenation of bucket[0], bucket[1], ..., bucket[b-1]
- 0.42 hashes to ⌊5·0.42⌋ = 2, so bucket[70] = [0.42]
- 0.32 hashes to ⌊5·0.32⌋ = 1, so bucket[71] = [0.32]
- 0.33 hashes to ⌊5·0.33⌋ = 1, so bucket[71] = [0.32, 0.33]
- 0.78 hashes to ⌊5·0.78⌋ = 3, so bucket[60] = [0.78]
- 0.23 hashes to ⌊5·0.23⌋ = 1, so bucket[71] = [0.32, 0.33, 0.23]
Analysis and Comparisons
Time and Space Complexity
The time complexity of sorting algorithms is typically analyzed in terms of best-case, average-case, and worst-case performance, measured in Big-O notation relative to input size . For comparison-based algorithms, a fundamental lower bound exists: any such sorting method requires at least comparisons in the worst case, as established by the decision tree model, where the tree's height must be at least to distinguish among possible permutations, and Stirling's approximation yields .[18] The following table summarizes the time complexities for the key algorithms discussed, drawing from standard analyses in algorithm textbooks; note that non-comparison sorts depend on additional parameters like range size or digit count .| Algorithm | Best Case | Average Case | Worst Case |
|---|---|---|---|
| Merge Sort | |||
| Quicksort | |||
| Heapsort | |||
| Shellsort | (approx., sequence-dependent) | (sequence-dependent) | |
| Counting Sort | |||
| Radix Sort | |||
| Bucket Sort |
| Algorithm | Auxiliary Space |
|---|---|
| Merge Sort | |
| Quicksort | (average), (worst) |
| Heapsort | |
| Shellsort | |
| Counting Sort | |
| Radix Sort | |
| Bucket Sort |
Practical Considerations
In practice, many standard library implementations of sorting algorithms employ hybrids to balance average-case speed, worst-case guarantees, and simplicity. The C++ Standard Template Library'sstd::sort function utilizes Introsort, a hybrid that begins with quicksort using median-of-3 pivoting, switches to heapsort when recursion depth exceeds approximately , and applies insertion sort to small subarrays (typically fewer than 16 elements) for optimal performance.[29] Similarly, Python's sorted() function and list.sort() method implement Timsort, a stable hybrid of merge sort and insertion sort that excels on real-world data by minimizing unnecessary operations.[77]
Shellsort often outperforms its theoretical bound in practice for medium-sized arrays (up to several thousand elements) due to its cache-friendly access patterns, where diminishing gap sequences promote better data locality and fewer cache misses compared to naive insertion sort.[78] This makes it a lightweight choice for scenarios where implementation simplicity and in-place operation are prioritized over asymptotic guarantees, though it lags behind hybrids like Introsort for larger inputs.
When stability is required—such as preserving the relative order of equal elements in multi-key sorts—merge sort is preferred over heapsort, as the latter is inherently unstable and may disrupt ties during heap adjustments.[79] Merge sort's merging phase naturally maintains order, making it suitable for applications like sorting database records or graphical elements where secondary keys matter, despite its extra space usage.[80]
Timsort's adaptivity enhances its efficiency by detecting and exploiting "natural runs"—maximal monotonic subsequences—in the input data, such as already-sorted segments, which it identifies through a single left-to-right pass before merging.[81] This run detection allows Timsort to achieve near-linear time on partially ordered data common in practice, like log files or user inputs, outperforming non-adaptive sorts in such cases.[77]
For parallel environments up to 2025, libraries like NVIDIA's Thrust provide high-performance sorting via STL-like interfaces that execute on multicore CPUs or GPUs, offering 5x to 100x speedups over sequential CPU sorts for large datasets while supporting the same hybrid strategies.[82] However, CPU-focused usage emphasizes multicore scalability without requiring GPU hardware, ideal for general-purpose computing.
Effective benchmarking of sorting algorithms requires testing specific implementations on representative hardware, standardizing the environment (e.g., compiler flags and input distributions), and repeating trials multiple times to account for variability, while using hardware performance counters to measure cache misses and branch predictions for deeper insights.[83]
Advanced Topics
External Sorting
External sorting is employed when the volume of data surpasses the available main memory, transforming the input/output (I/O) operations on secondary storage—such as hard disk drives or solid-state drives—into the dominant performance bottleneck, far outweighing computational overhead.[84] This scenario arises frequently in database systems, big data processing, and file management, where datasets can span gigabytes to petabytes, necessitating algorithms that minimize disk accesses to achieve practical efficiency.[85] The canonical technique for external sorting is external merge sort, which partitions the input file into smaller segments that fit within memory, sorts each segment using an in-memory algorithm, and stores the resulting sorted runs on disk.[85] Subsequent phases involve k-way merging of these runs, employing input buffers to read multiple runs simultaneously and an output buffer to write merged results, thereby amortizing the cost of random disk seeks through sequential access patterns.[84] This method mirrors the divide-and-conquer strategy of internal merge sort but adapts it to handle persistent storage constraints. In terms of complexity, external merge sort maintains the O(n log n) comparison bound of its internal counterpart, where n is the number of elements, but the true measure of efficiency lies in I/O operations, which polyphase merge optimizes by strategically varying run lengths across merge passes to reduce the total number of disk traversals—often achieving near-minimal passes for balanced trees of runs.[85] For run formation, replacement selection serves as a prominent variant, utilizing a heap-based priority queue to stream input records and output the smallest available element, replacing it with the next input; this yields initial runs averaging twice the memory size for uniformly random data, halving the subsequent merge workload compared to naive load-sort-store approaches.[86] Introduced in seminal work on tape sorting, replacement selection excels in streaming contexts by overlapping I/O and computation, though it performs equivalently to memory-sized runs on worst-case ordered inputs.[87] A practical example involves sorting a terabyte-scale file: the data is initially divided into runs of several gigabytes each via replacement selection, requiring log_B(n/M) merge passes where B is the block size and M the memory capacity, progressively consolidating runs until a single sorted output emerges, with each pass scanning the dataset sequentially to leverage disk bandwidth.[84] In distributed systems, tools like Hadoop MapReduce facilitate external sorting at even larger scales by assigning map tasks to sort local data partitions in memory or via spills to disk, followed by a shuffle-sort phase where reducers merge sorted inputs from multiple mappers using external merge techniques, enabling petabyte-level sorting across clusters.[88]Parallel Sorting
Parallel sorting algorithms leverage multiple processors or cores to distribute the workload of sorting large datasets, achieving significant speedups on multi-core CPUs, distributed systems, and accelerators like GPUs. These methods adapt sequential sorting paradigms to parallel architectures, focusing on dividing data across processing units while minimizing inter-processor communication. Seminal work in this area includes randomized parallel sorting algorithms that achieve near-optimal performance in models like the PRAM (Parallel Random Access Machine).[89] One prominent approach is parallel merge sort, which exploits the divide-and-conquer structure of merge sort by recursively partitioning the input array across multiple threads or processes and sorting subarrays in parallel before merging results. In this method, the array is divided into contiguous segments assigned to different cores, with the merge phase often parallelized using techniques like multi-way merging to reduce contention. For instance, on a multi-core system, initial subarray sorts can proceed concurrently, followed by a threaded merge that combines sorted halves using shared memory for efficiency. This approach is particularly effective for shared-memory systems, where synchronization primitives ensure correct merging without excessive overhead.[90][91] Another key approach is sample sort, a parallel variant inspired by quicksort that uses random sampling to select pivots and partition data into roughly equal buckets across processors, ensuring balanced load distribution. The algorithm begins by drawing a regular sample from the input to determine splitters, sorts local subsets independently, and then performs a collective permutation to route elements to their target processors for final local sorting. This method excels in distributed environments by reducing data movement compared to traditional quicksort, as it guarantees balanced partitions with high probability.[92][93] In terms of time complexity, ideal parallel sorting achieves work with processors, where the total operations scale linearly with the number of processors while maintaining logarithmic depth per processor. However, real-world implementations often incur additional costs from communication and synchronization, leading to practical complexities like in distributed settings due to all-to-all communication patterns. Cole's parallel merge sort, for example, runs in time using processors on a CREW PRAM model, performing comparisons.[90][94] For implementation, libraries like OpenMP facilitate parallel quicksort on shared-memory multi-core systems by using directives to spawn threads for partitioning and sorting subarrays recursively, achieving speedups of up to 3-4x on 4-core processors for large inputs. In distributed-memory environments, the Message Passing Interface (MPI) supports algorithms like parallel sample sort through collective operations such as all-to-all personalized communication, enabling sorting across clusters with initial data distribution of elements per process. These libraries handle low-level details, allowing focus on algorithmic parallelism.[95][96] Key challenges in parallel sorting include load balancing, where uneven partition sizes can leave some processors idle, and synchronization, which introduces overhead from barriers or locks during merge or permutation steps. Poor load balancing exacerbates with non-uniform data distributions, while excessive synchronization can serialize execution, negating parallelism gains; techniques like oversampling in sample sort mitigate these by ensuring equitable data splits. Communication overhead in distributed systems further complicates scalability, as data redistribution can dominate for small .[97] As of 2025, GPU-based parallel sorting has advanced with algorithms like bitonic sort, which constructs sorting networks through compare-exchange operations in a highly parallel manner suited to SIMD architectures. Bitonic sort divides the input into bitonic sequences—monotonically increasing then decreasing—and merges them in logarithmic stages, achieving time with parallel units on GPUs, often outperforming CPU sorts for datasets exceeding billions of elements due to massive thread counts. Hybrid GPU approaches, combining bitonic merging with radix-like partitioning, have demonstrated sorting rates of billions of keys per second on modern hardware like NVIDIA H100 or Blackwell GPUs.[98][99]Index Sorting and Memory Patterns
Index sorting, also known as indirect sorting, involves creating and sorting an auxiliary array of indices or pointers that reference the original data elements, rather than moving the data itself during the sorting process.[100] This technique minimizes data movement by only rearranging the indices, which is particularly beneficial when the original data consists of large or immutable structures that are expensive to copy or relocate.[100] A prominent example is theargsort function in NumPy, which returns the indices that would sort an array in ascending order without altering the original array.
Common use cases for index sorting include scenarios with immutable data, such as strings or complex objects in programming languages, and large datasets where copying would incur significant overhead.[101] In database systems, index sorting is employed to order query results by sorting indices associated with table rows, avoiding the need to shuffle entire records and thus reducing I/O costs.[102]
The time complexity of index sorting mirrors that of the underlying sorting algorithm applied to the index array—for instance, O(n log n) for quicksort or mergesort variants—while the space complexity increases by O(n) for the index array itself.[100] However, it achieves reduced write operations to the original data, as only reads are needed during comparisons, leading to fewer cache invalidations and lower memory bandwidth usage compared to direct sorting methods.[103]
A simple example of indirect sorting using a quicksort-like approach on indices can be illustrated in pseudocode:
function indirect_quicksort(indices, data, low, high):
if low < high:
pivot_index = partition(indices, data, low, high)
indirect_quicksort(indices, data, low, pivot_index - 1)
indirect_quicksort(indices, data, pivot_index + 1, high)
function partition(indices, data, low, high):
pivot = indices[high]
i = low - 1
for j from low to high - 1:
if data[indices[j]] <= data[pivot]:
i += 1
swap indices[i] and indices[j]
swap indices[i + 1] and indices[high]
return i + 1
function indirect_quicksort(indices, data, low, high):
if low < high:
pivot_index = partition(indices, data, low, high)
indirect_quicksort(indices, data, low, pivot_index - 1)
indirect_quicksort(indices, data, pivot_index + 1, high)
function partition(indices, data, low, high):
pivot = indices[high]
i = low - 1
for j from low to high - 1:
if data[indices[j]] <= data[pivot]:
i += 1
swap indices[i] and indices[j]
swap indices[i + 1] and indices[high]
return i + 1
partition function compares elements via their indices in the data array without modifying data, only swapping index values.[103]
Memory access patterns in sorting algorithms significantly influence practical performance due to cache hierarchies, where poor locality leads to increased misses and stalls. Quicksort generally exhibits better cache locality than mergesort because its in-place partitioning accesses contiguous memory regions sequentially during swaps and recursions on subarrays, resulting in approximately 1.6 cache misses per key for large inputs in untuned implementations.[104] In contrast, mergesort's merging phase often jumps between non-adjacent memory blocks, incurring higher misses—around 10 per key—though tiled variants can mitigate this by aligning merges with cache lines.[104]
Cache-oblivious sorting algorithms address varying memory hierarchies without tuning to specific cache parameters, achieving optimal I/O complexity of O((n/B) log_{M/B} (n/B)) under the tall-cache assumption (M ≥ B^2), where n is input size, B is block size, and M is cache size.[105] Seminal examples include funnelsort, which recursively merges sorted runs using buffer trees to enhance locality and minimize block transfers, as developed by Brodal and Fagerberg.[105] These algorithms prioritize sequential access patterns to exploit hardware caches automatically, outperforming cache-aware sorts on unknown architectures.[105]
Index sorting techniques often leverage in-place properties of base algorithms to further limit auxiliary space beyond the index array.[100]References
- They are either comparison based or non-comparison based. Their relative advantages and disadvantages have been studied extensively. A comparison based sorting ...Missing: mechanisms | Show results with:mechanisms
- A complete study is presented of the best general purpose method for sorting by computer: C. A. R. Hoare's Quicksort algorithm. Special.
- May 3, 2021 · In this section, we shall study two elementary sorting methods (selection sort and insertion sort) and a variation of one of them (shellsort).Missing: categories | Show results with:categories
- Sorting algorithms represent foundational knowledge that every computer scientist and IT professional should at least know at a basic level. And it turns out to ...Missing: authoritative sources
- Sorting is the process of rearranging a sequence of objects so as to put them in some logical order. Sorting plays a major role in commercial data processing ...
