Hubbry Logo
Sorting algorithmSorting algorithmMain
Open search
Sorting algorithm
Community hub
Sorting algorithm
logo
8 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Sorting algorithm
Sorting algorithm
from Wikipedia
Merge sort

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:

  1. The output is in monotonic order (each element is no smaller/larger than the previous element, according to the required order).
  2. 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]
An example of stable sort on playing cards. When the cards are sorted by rank with a stable sort, the two 5s must remain in the same order in the sorted output that they were originally in. When they are sorted with a non-stable sort, the 5s may end up in the opposite order in the sorted output.

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]

Comparison sorts
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 (balanced) 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]
Non-comparison sorts
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. [disputeddiscuss]
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]
[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]
A Shellsort, different from bubble sort in that it moves elements to numerous swapping positions.

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]
A bubble sort, a sorting algorithm that continuously steps through a list, swapping items until they appear in the correct order.

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.

[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]

References

[edit]

Further reading

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
A sorting algorithm is a computational method in that rearranges the elements of a collection, such as an or , into a specified order, most commonly non-decreasing (ascending) or non-increasing (descending) based on a comparison function. These algorithms form a of , enabling efficient organization of information for subsequent operations like searching or analysis. Sorting algorithms are ubiquitous in , underpinning applications in for query optimization, search engines for ranking results, and scientific simulations for handling large datasets. Their development dates back to early computing history, with foundational techniques emerging in the mid-20th century, and they continue to evolve to address modern challenges like parallel processing and . In practical systems, hybrid algorithms like —used in languages such as Python and —combine multiple strategies to achieve optimal performance across diverse inputs. Algorithms are typically classified into comparison-based sorts, which rely on pairwise comparisons between elements, and non-comparison sorts, which exploit properties of the data like integer keys. Prominent comparison-based examples include quicksort, which achieves average-case O(n log n) time complexity through partitioning, and mergesort, a stable divide-and-conquer approach with guaranteed O(n log n) performance. Non-comparison methods, such as radix sort, can run in linear O(n) time for fixed-size keys but require additional space. Performance evaluation of sorting algorithms focuses on time complexity in best, average, and worst cases; ; stability (preserving relative order of equal elements); and adaptability to partially sorted data. In the comparison model, any sorting algorithm requires at least Ω(n log n) comparisons in the worst case, establishing a theoretical lower bound derived from analysis. These metrics guide selection in real-world scenarios, balancing efficiency with constraints like memory usage or hardware parallelism.

Fundamentals

Definition and Purpose

A sorting algorithm is a procedure that rearranges the elements of a list or into a specified order, typically ascending or descending based on some criterion. The input is generally an unsorted collection of elements, such as an 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. This process ensures that the relative positions of elements satisfy the ordering relation, often numerical for values like integers or lexicographical for strings. The primary purpose of sorting algorithms is to facilitate efficient organization and retrieval in computing systems, serving as a foundational operation for numerous higher-level tasks. By arranging 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. It also supports organization by grouping similar elements or prioritizing items, which is essential for processing large datasets in a structured manner. 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. 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. 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.

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 nn, typically assuming an array of nn distinct elements under random or adversarial ordering. Time complexity is expressed using to describe the number of basic operations (such as comparisons or swaps) in the worst case (maximum over all inputs), 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 Ω(nlogn)\Omega(n \log n) comparisons exists in the worst case, derived from the information-theoretic argument that distinguishing among n!n! possible permutations requires at least log2(n!)\log_2(n!) bits of information, approximated by Stirling's formula as Ω(nlogn)\Omega(n \log n). Space complexity measures the auxiliary memory used beyond the input array, often categorized as in-place (requiring O(1)O(1) extra space) or out-of-place (needing Θ(n)\Theta(n) or more). In-place algorithms modify the input directly, minimizing overhead, while others may use temporary s 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 O(n+k)O(n + k) time where kk is the number of inversions; and in-place capability, emphasizing low auxiliary . 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 Ω(nlogn)\Omega(n \log n) bound, while empirical studies validate these under specific conditions, often showing deviations for small nn or non-uniform distributions.

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 , 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. 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 for the 1890 U.S. . 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. 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. 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 developed an early routine as part of programming efforts for the , 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 , initial implementations appeared on commercial systems like the , 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 via punched cards, and the absence of formal analysis on efficiency or scalability, setting the stage for theoretical developments in the mid-century.

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. This volume became a cornerstone for understanding sorting efficiency, influencing subsequent research by emphasizing mathematical rigor over empirical testing alone. 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 where the algorithm's comparisons form a with at least n! leaves (one for each ), was formalized in the context of modern computing during the 1950s and 1960s, with Knuth providing detailed proofs and implications in his 1973 analysis. The result underscored that no could asymptotically outperform this barrier, guiding the design of optimal algorithms. The 1970s and saw practical refinements to , 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. Further advancements in the , 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. By the late , 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. This innovation was quickly adopted in standard libraries, such as the C++ Standard Template Library, for its balance of simplicity and reliability. 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, 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.

Classification

Comparison-Based Sorts

Comparison-based sorting algorithms determine the relative order of elements in a collection solely through pairwise s, 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. The theoretical analysis of comparison-based sorts often employs a to capture their behavior. In this model, each internal node represents a 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 to the leaves traverse a sequence of comparisons, and each leaf corresponds to a specific 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. 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 2h2^h leaves, to accommodate at least n! leaves requires hlog2(n!)h \geq \lceil \log_2 (n!) \rceil. Using , log2(n!)nlog2nnlog2e+O(log2n)\log_2 (n!) \approx n \log_2 n - n \log_2 e + O(\log_2 n), which simplifies to Θ(nlogn)\Theta(n \log n). Therefore, the minimum height is Ω(nlogn)\Omega(n \log n), establishing that any comparison-based sorting algorithm must perform at least Ω(nlogn)\Omega(n \log n) 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 log2(n!)\log_2 (n!) bits. The proof assumes distinct elements and a , but it holds generally for the worst-case analysis of such algorithms. 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 Ω(nlogn)\Omega(n \log n) lower limit.

Non-Comparison-Based Sorts

Non-comparison-based sorting algorithms operate by exploiting specific properties of the input , such as discrete values or fixed representations, rather than relying on pairwise comparisons between elements. These methods typically employ techniques like occurrences of values, distributing elements into buckets based on ranges, or extracting and sorting digits individually to determine the order. For instance, mechanisms tally the frequency of each possible key value in an auxiliary , which is then used to place elements in their correct positions without direct comparisons. 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. Digit extraction, often applied to integers or fixed-length keys, processes the data digit by digit, sorting each position separately using a subroutine like . 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. For digit-based methods, keys must have a bounded number of digits or bits, enabling iterative passes without in processing time. 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. 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. 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. For example, if k >> n, the space overhead dominates, rendering the sort inefficient. Hybrid approaches enhance versatility by integrating non-comparison methods with comparison-based ones, such as using or bucketing as a preprocessing step to partition data before applying a general sort like on subgroups. 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. exemplifies this by repeatedly applying a across digits.

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. 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. Stability facilitates such hierarchical sorting without requiring additional keys to track original positions. Beyond stability, sorting algorithms exhibit other important properties that influence their suitability for specific scenarios. An operates using only a constant amount of extra beyond the input , typically O(1) additional space, making it memory-efficient for large datasets. 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. Online algorithms process elements as they arrive incrementally, without needing the entire input upfront, which is useful in streaming or environments. These properties often involve trade-offs; for example, achieving stability frequently requires auxiliary to track relative orders, whereas in-place sorts may sacrifice stability to minimize memory usage. Adaptive and behaviors can enhance on non-random inputs but may not improve worst-case . 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. This test confirms whether the algorithm preserves relative ordering without altering the sorting logic.

Simple Sorting Algorithms

Insertion Sort

Insertion sort is a simple comparison-based sorting algorithm that builds the final sorted one item at a time by iteratively taking an element from the input and inserting it into its correct position in the already-sorted portion of the . 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. 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. The following pseudocode illustrates the insertion sort procedure for an array AA of nn 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

This code assumes the array is sorted in non-decreasing order; the inner shifts larger elements one position to the right to make space for the key, which is then placed in the correct spot. The of is O(n2)O(n^2) in both the worst and average cases due to the nested loops, where each insertion may require up to j1j-1 comparisons and shifts for the jj-th element. However, it achieves O(n)O(n) time in the best case when the input is already sorted, as no shifts are needed beyond the initial comparisons. is adaptive to nearly sorted data and requires O(1)O(1) extra space, operating in-place by modifying the original array. It is also , preserving the relative order of equal elements. To illustrate, consider sorting the array [3,1,4,1,5][3, 1, 4, 1, 5] (0-based indexing for clarity):
  • Initialize: Sorted subarray is {{grok:render&&&type=render_inline_citation&&&citation_id=3&&&citation_type=wikipedia}}; unsorted is [1,4,1,5][1, 4, 1, 5].
  • Insert 1: Compare with 3 (greater), shift 3 right, place 1 at start → [1,3,4,1,5][1, 3, 4, 1, 5].
  • Insert 4: Compare with 3 (smaller? No, 3 < 4), no shift → [1,3,4,1,5][1, 3, 4, 1, 5].
  • Insert 1: Compare with 4 (>1, shift), 3 (>1, shift), 1 (not >1, stop), place 1 after first 1 → [1,1,3,4,5][1, 1, 3, 4, 5].
  • Insert 5: Compare with 4 (<5), no shift → [1,1,3,4,5][1, 1, 3, 4, 5].
This process requires 6 comparisons and 3 shifts in total. A common variant, binary insertion sort, optimizes the search for the insertion point by using on the sorted subarray, reducing the number of comparisons from O(n2)O(n^2) to O(nlogn)O(n \log n). However, the time complexity remains O(n2)O(n^2) overall because shifting elements to create space still requires linear time per insertion. The pseudocode modifies the inner loop to replace linear search with before shifting:

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

This variant is useful when comparisons are expensive relative to shifts.

Selection Sort

Selection sort is a straightforward in-place comparison-based 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]

This implementation performs a linear scan over the unsorted portion in each of the outer loop's n-1 iterations to locate the minimum. The time complexity of selection sort is Θ(n²) for all cases—worst, average, and best—since it always executes exactly n(n-1)/2 comparisons and up to n-1 swaps, regardless of the input arrangement. It requires O(1) extra space, confirming its in-place nature, but it is unstable because swaps can alter the relative order of equal elements. To illustrate, consider sorting the array A = [64, 25, 12, 22, 11]:
  • 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]
The sorted array is now [11, 12, 22, 25, 64]. Selection sort is advantageous in scenarios where swapping elements is costly compared to comparisons, such as when elements are large records or involve expensive copy operations, as it limits swaps to at most n-1 while other quadratic algorithms like may require up to n(n-1)/2. Compared to , it conducts more comparisons but fewer data movements overall.

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. 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. Although inefficient for large datasets, bubble sort serves as an educational tool for understanding basic sorting principles due to its straightforward implementation. 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

This implementation assumes an array A of length n and sorts in non-decreasing order. In terms of , 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. The best-case is Θ(n) when the list is already sorted, achievable with an optimization that tracks swaps and terminates early if none occur in a pass. is O(1), making it an that requires only a constant amount of extra space for temporary variables during swaps. Bubble sort can be implemented in a manner, preserving the relative order of equal elements since swaps only occur for strictly greater comparisons. 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. Another variant, known as , 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. To illustrate, consider tracing bubble sort on the [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]
The is now sorted after two passes, with the third pass unnecessary if early termination is used.

Efficient Comparison-Based Sorts

is a divide-and-conquer sorting algorithm that recursively divides an into halves, sorts each half, and then merges the sorted halves into a single sorted . It was invented by in 1945 as part of early computer design considerations for the machine. 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. 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)

The 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). 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 and ; and ; and so on, down to single elements. Merging builds up: [27, 38] from and ; [3, 43] from and ; 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.

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. 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). The subarrays are then recursively sorted, achieving efficient performance on average by reducing the problem size at each step. 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. This in-place partitioning minimizes space usage, making quicksort suitable for large datasets in random-access memory. 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)

This formulation returns the crossing point j after partitioning, with the left subarray (low to pivot_index) and right subarray (pivot_index + 1 to high) handled recursively. The pivot ends up in one of the subarrays and will be placed in its correct position during further recursions. Quicksort exhibits O(n²) time complexity in the worst case, occurring when the pivot consistently splits the array unevenly (e.g., always the smallest or largest element, as in a presorted array with the first element as pivot), leading to unbalanced recursion. In the average case, assuming random pivots or uniform input distribution, the expected time complexity is O(n log n), as each partition roughly halves the problem size, matching the information-theoretic lower bound for comparison sorts. The space complexity is O(log n) on average due to the recursion stack, though it is in-place with constant auxiliary space beyond the stack; however, it is unstable because equal elements may be reordered during swaps. To mitigate the worst-case scenario, various pivot selection strategies have been developed. Choosing the first or last element as pivot is simple but vulnerable to sorted or reverse-sorted inputs. Randomized pivot selection, where an element is picked uniformly at random, ensures the expected time remains O(n log n) with high probability, as poor splits become exponentially unlikely. The median-of-three strategy improves practical performance by selecting the median of the first, middle, and last elements as the pivot, reducing the chance of extremely unbalanced partitions by about 14% in comparisons while slightly increasing swaps; empirical studies show it speeds up quicksort by roughly 5-10% on typical data. For illustration, consider sorting the array [4, 2, 7, 1, 3, 6, 5] using the first element (4) as pivot:
  • 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]
This example demonstrates two levels of recursion, with balanced splits yielding logarithmic depth. Introsort addresses quicksort's worst-case quadratic time by hybridizing it with heapsort: it monitors recursion depth and switches to the O(n log n) worst-case heapsort if the depth exceeds approximately 2 log₂ n, guaranteeing O(n log n) overall while retaining average-case speed. Introduced by David Musser in 1997, introsort is widely used in standard libraries, such as C++'s std::sort, for its robustness.

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. 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)

BUILD-MAX-HEAP(A)

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)

(A)

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)

This implementation is in-place, requiring only constant extra space beyond the input . In terms of complexity, building the max-heap takes O(n) time, as the heapify calls on lower levels contribute proportionally less work, summing to a linear total. Each of the n extractions involves an O(log n) heapify, leading to an overall time complexity of O(n log n) in the worst, average, and best cases. Heapsort is not stable, as equal elements may change relative order during swaps, and it performs Θ(n log n) comparisons regardless of input distribution. To illustrate, consider sorting the array A = [4, 1, 3, 2, 16, 9, 10, 14, 12, 8, 7, 20, 18, 17] (1-based indices 1 to 14). After BUILD-MAX-HEAP, the heap is [20, 18, 17, 14, 12, 16, 10, 4, 8, 2, 7, 1, 3, 9]. Swap root 20 with last element 9, yielding [9, 18, 17, 14, 12, 16, 10, 4, 8, 2, 7, 1, 3, 20], then heapify root to get [18, 14, 17, 4, 12, 16, 10, 9, 8, 2, 7, 1, 3, 20]. Repeat: swap 18 with 3 → [3, 14, 17, 4, 12, 16, 10, 9, 8, 2, 7, 1, 18, 20], heapify to [17, 14, 3, 4, 12, 16, 10, 9, 8, 2, 7, 1, 18, 20]. Continuing this process eventually sorts the array as [1, 2, 3, 4, 7, 8, 9, 10, 12, 14, 16, 17, 18, 20]. Each step reduces the heap size and positions the next largest element correctly. A variant of uses a min-heap instead of a max-heap to sort in ascending order by placing the minimum element at the beginning of the array rather than the end. This requires reversing the comparison operators in the heapify procedure (e.g., checking for smaller children) and adjusting the extraction to swap with the first position while heapifying from the root. The time and space complexities remain O(n log n) and O(1), respectively.

Shellsort

is a comparison-based sorting algorithm that extends the by sorting elements that are far apart before addressing closer ones, using a of decreasing gaps to improve . Invented by Donald L. Shell in 1959, it applies 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 . This approach allows to achieve better average-case performance than simple by reducing the total number of element shifts required, as distant inversions are resolved early in the process. The algorithm's can be described as follows, where the gaps are generated from a predefined sequence (e.g., starting with h=n/2h = \lfloor n/2 \rfloor and halving until h=1h = 1):

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

This structure performs on each subarray of elements spaced hh apart, ensuring that after all gaps, the array is fully sorted. operates in-place with O(1)O(1) extra space and is unstable, as it may reorder equal elements. Its worst-case is O(n2)O(n^2), but with appropriate gap sequences, it achieves an upper bound of O(nlog2n)O(n \log^2 n); empirical analyses indicate average-case performance closer to O(n1.3)O(n^{1.3}) to O(n1.5)O(n^{1.5}), making it faster than for large inputs by preprocessing the array to minimize remaining disorder. The choice of gap sequence significantly affects performance, with early proposals including Shell's original powers-of-2 decrements (h=n/2,n/4,,1h = n/2, n/4, \dots, 1). A widely analyzed sequence is Hibbard's increments (h=2k+1h = 2^k + 1, for kk decreasing from log2n\log_2 n to 0), which yield a of O(n3/2)O(n^{3/2}). For improved empirical results, Sedgewick's sequence—such as 1,5,19,41,109,1, 5, 19, 41, 109, \dots (generated by hk+1=3hk+(1)k+140h_{k+1} = 3h_k + (-1)^{k+1} \cdot 40, starting from small values)—provides near-optimal average-case behavior with low constant factors. To illustrate, consider sorting the array [8, 4, 2, 6, 1, 3, 7, 5] with gaps 4, 2, 1 (a simplified sequence for n=8). For gap 4, subarrays [8,1], [4,3], [2,7], [6,5], yielding [1,8], [3,4], [2,7], [5,6] and array [1,3,2,5,8,4,7,6]. For gap 2, sorting interleaved pairs results in [1,2,3,5,6,4,7,8]. Finally, gap 1 performs standard to produce [1,2,3,4,5,6,7,8]. This demonstrates how initial large gaps handle distant elements, reducing swaps in later phases compared to direct on the original array.

Non-Comparison-Based Sorts

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. Developed by Harold H. Seward in 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. The process begins by creating an auxiliary to store the of each possible value in the input. For an input AA of nn elements with values ranging from 0 to k1k-1, initialize a CC of kk to zeros. Then, iterate through AA and increment CC for each element xx. To produce the sorted output, modify CC to store cumulative counts: for each index ii from 1 to k1k-1, set C=C+C[i1]C = C + C[i-1]. Finally, create an output BB of nn, and starting from the end of AA, place each element xx at position B[C1]B[C - 1] and decrement CC, ensuring stability by processing in reverse order.

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

This assumes 0-based indexing and produces a sort in output BB. The of counting sort is O(n+k)O(n + k), where nn is the number of elements to sort and kk is the range of input values, as each step processes the input once and the count once. Space is also O(n+k)O(n + k), requiring additional arrays for counts and output, and the algorithm is not in-place unless modified to overwrite the input. Counting sort assumes the input consists of integers bounded by a known maximum value kk, typically non-negative and starting from 0 or 1; it does not handle floating-point numbers, negatives, or unbounded ranges without preprocessing like shifting or mapping. For example, consider sorting the array [3,1,3,2][3, 1, 3, 2] with k=4k = 4 (values from 0 to 3). The count array initializes as C=[0,0,0,0]C = [0, 0, 0, 0], then becomes [0,1,1,2][0, 1, 1, 2] after counting. Cumulative sums yield C=[0,1,2,4]C = [0, 1, 2, 4]. Processing backwards: place 2 at index 1 (C-1=1, decrement to 1), 3 at index 3 (C-1=3, decrement to 3), 3 at index 2 (C-1=2, decrement to 2), 1 at index 0 (C-1=0, decrement to 0), resulting in [1,2,3,3][1, 2, 3, 3]. A key limitation of counting sort is its inefficiency when kk is large relative to nn, as the time and space requirements grow with kk, potentially making it impractical for wide ranges like 32-bit integers without range reduction. It forms the foundational counting mechanism used in for handling larger integer ranges digit by digit.

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 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. The algorithm traces its origins to Herman Hollerith's 1889 for an electric tabulating system, which described an MSD approach for sorting punched cards in data processing. 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 for LSD radix sort on an array AA of nn elements with dd digits in base bb 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)

This process relies on a stable subroutine, such as , to handle the distribution for each digit. The is O(d(n+b))O(d(n + b)), where dd is the number of digits, nn is the number of elements, and bb is the base (e.g., 10 for or 256 for bytes); is O(n+b)O(n + b) dominated by the subroutine. is stable overall if the subroutine is stable, making it suitable for multi-key sorting. The MSD variant differs by starting from the leftmost (most significant) digit and using a partitioning or recursive approach, which can terminate early for subgroups with identical prefixes, offering efficiency for variable-length keys like strings. LSD radix sort assumes fixed-length keys and processes all digits uniformly, which suits integers but may waste effort on leading zeros. For example, consider sorting the integers [170, 45, 75, 90, 802, 24, 2, 66] in base 10, padded to three digits: [170, 045, 075, 090, 802, 024, 002, 066].
  • 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 (): Distributes to preserve units order, yielding [802, 2, 24, 45, 66, 170, 75, 90].
  • Hundreds digit pass (): Distributes to [2, 24, 45, 66, 75, 90, 170, 802].
This demonstrates how stability ensures correct final ordering. finds applications in sorting strings by character positions, universally unique identifiers (UUIDs) by digits, and large-scale integer sorting in databases or numerical simulations where keys have bounded digit lengths. It excels in scenarios with many elements but limited digit variety, such as IP addresses or postal codes, outperforming comparison sorts when dbd \cdot b is small relative to nlognn \log n.

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 xix_i is assigned to its bucket by computing the hash value nxi\lfloor n \cdot x_i \rfloor, 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]

This implementation assumes the use of linked lists for buckets to facilitate efficient insertions and concatenations. Under the uniform distribution assumption, the expected of bucket sort is O(n, as each receives approximately one element on average, rendering the s within buckets collectively O(n. In the worst case, all elements may hash to a single , leading to O(n^2 time due to the quadratic behavior of on that . The is O(n, accounting for the buckets and auxiliary lists. Bucket sort is when the internal sorting method preserves relative order, as does. As a non-comparison sort, it leverages the distributional properties rather than element comparisons. To illustrate, consider an input array A = [0.42, 0.32, 0.33, 0.78, 0.23] with n=5, yielding buckets indexed 0 to 4:
  • 0.42 hashes to ⌊5·0.42⌋ = 2, so bucket = [0.42]
  • 0.32 hashes to ⌊5·0.32⌋ = 1, so bucket = [0.32]
  • 0.33 hashes to ⌊5·0.33⌋ = 1, so bucket = [0.32, 0.33]
  • 0.78 hashes to ⌊5·0.78⌋ = 3, so bucket = [0.78]
  • 0.23 hashes to ⌊5·0.23⌋ = 1, so bucket = [0.32, 0.33, 0.23]
Insertion sort on bucket yields [0.23, 0.32, 0.33]; other buckets remain singletons. Concatenation produces [0.23, 0.32, 0.33, 0.42, 0.78]. In the standard formulation, bucket assignments rely on arithmetic means to define uniform subintervals, but variants adjust boundaries using medians instead to accommodate skewed distributions, potentially maintaining near-linear performance by balancing bucket loads more robustly.

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 nn. For comparison-based algorithms, a fundamental lower bound exists: any such sorting method requires at least Ω(nlogn)\Omega(n \log n) comparisons in the worst case, as established by the decision tree model, where the tree's height must be at least log2(n!)\log_2(n!) to distinguish among n!n! possible permutations, and Stirling's approximation yields log2(n!)nlog2n1.4427n\log_2(n!) \geq n \log_2 n - 1.4427n. 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 kk or digit count dd.
AlgorithmBest CaseAverage CaseWorst Case
O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)
O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(n2)O(n^2)
O(nlogn)O(n \log n)O(nlogn)O(n \log n)O(nlogn)O(n \log n)
O(nlogn)O(n \log n)O(n1.3)O(n^{1.3}) (approx., sequence-dependent)O(n2)O(n^2) (sequence-dependent)
O(n+k)O(n + k)O(n+k)O(n + k)O(n+k)O(n + k)
O(d(n+k))O(d(n + k))O(d(n+k))O(d(n + k))O(d(n+k))O(d(n + k))
O(n+k)O(n + k)O(n+k)O(n + k)O(n2)O(n^2)
Auxiliary space complexity measures additional memory beyond the input , which varies significantly across algorithms and impacts practicality on memory-constrained systems.
AlgorithmAuxiliary Space
O(n)O(n)
O(logn)O(\log n) (average), O(n)O(n) (worst)
O(1)O(1)
O(1)O(1)
O(k)O(k)
O(n+k)O(n + k)
O(n+k)O(n + k)
These complexities are influenced by factors such as input distribution—e.g., quicksort degrades to quadratic time on already-sorted inputs without —and hardware characteristics like cache misses, where algorithms with poor locality (e.g., certain recursive sorts) incur higher effective costs on modern processors. While asymptotic bounds provide theoretical guarantees, constant factors and implementation details often determine real-world performance; for instance, typically outperforms mergesort and in practice despite its worse worst-case bound, due to fewer operations per level and better cache utilization.

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's std::sort function utilizes Introsort, a hybrid that begins with quicksort using median-of-3 pivoting, switches to heapsort when recursion depth exceeds approximately log2n\log_2 n, and applies insertion sort to small subarrays (typically fewer than 16 elements) for optimal performance. 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. Shellsort often outperforms its theoretical O(n1.5)O(n^{1.5}) 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 . This makes it a choice for scenarios where implementation simplicity and in-place operation are prioritized over asymptotic guarantees, though it lags behind hybrids like for larger inputs. When stability is required—such as preserving the relative order of equal elements in multi-key sorts— is preferred over , as the latter is inherently unstable and may disrupt ties during heap adjustments. 's merging phase naturally maintains order, making it suitable for applications like sorting database records or graphical elements where secondary keys matter, despite its O(n)O(n) extra space usage. 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. 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. For parallel environments up to 2025, libraries like NVIDIA's 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. However, CPU-focused usage emphasizes multicore scalability without requiring GPU hardware, ideal for general-purpose . Effective of sorting algorithms requires testing specific implementations on representative hardware, standardizing the environment (e.g., flags and input distributions), and repeating trials multiple times to account for variability, while using hardware performance counters to measure cache misses and predictions for deeper insights.

Advanced Topics

External Sorting

External sorting is employed when the volume of data surpasses the available main memory, transforming the (I/O) operations on secondary storage—such as hard disk drives or solid-state drives—into the dominant performance bottleneck, far outweighing computational overhead. This scenario arises frequently in database systems, processing, and file management, where datasets can span gigabytes to petabytes, necessitating algorithms that minimize disk accesses to achieve practical efficiency. The canonical technique for external sorting is external merge sort, which partitions the input file into smaller segments that fit within , sorts each segment using an in-memory , and stores the resulting sorted runs on disk. 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 patterns. This method mirrors the divide-and-conquer strategy of internal but adapts it to handle persistent storage constraints. In terms of , 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. For run formation, replacement selection serves as a prominent variant, utilizing a heap-based to 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. Introduced in seminal work on tape sorting, replacement selection excels in streaming contexts by overlapping I/O and , though it performs equivalently to memory-sized runs on worst-case ordered inputs. 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 capacity, progressively consolidating runs until a single sorted output emerges, with each pass scanning the dataset sequentially to leverage disk bandwidth. In distributed systems, tools like Hadoop facilitate at even larger scales by assigning map tasks to sort local data partitions in 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.

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 sorting algorithms that achieve near-optimal performance in models like the PRAM (Parallel Random Access Machine). One prominent approach is parallel merge sort, which exploits the divide-and-conquer structure of 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 , initial subarray sorts can proceed concurrently, followed by a threaded merge that combines sorted halves using for efficiency. This approach is particularly effective for , where primitives ensure correct merging without excessive overhead. Another key approach is sample sort, a parallel variant inspired by 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 to route elements to their target processors for final local sorting. This method excels in distributed environments by reducing data movement compared to traditional , as it guarantees balanced partitions with high probability. In terms of , ideal parallel sorting achieves O(nlognp)O\left(\frac{n \log n}{p}\right) work with pp 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 , leading to practical complexities like O(nlognp+log2p)O\left(\frac{n \log n}{p} + \log^2 p\right) in distributed settings due to all-to-all communication patterns. Cole's parallel , for example, runs in O(logn)O(\log n) time using nn processors on a PRAM model, performing 154nlogn\frac{15}{4} n \log n comparisons. For implementation, libraries like facilitate parallel 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 . In distributed-memory environments, the (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 n/pn/p elements per process. These libraries handle low-level details, allowing focus on algorithmic parallelism. Key challenges in parallel sorting include load balancing, where uneven partition sizes can leave some processors idle, and , which introduces overhead from barriers or locks during merge or permutation steps. Poor load balancing exacerbates with non-uniform data distributions, while excessive can serialize execution, negating parallelism gains; techniques like in sample sort mitigate these by ensuring equitable data splits. Communication overhead in distributed systems further complicates , as data redistribution can dominate for small pp. 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 O(log2n)O(\log^2 n) time with nn 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.

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. 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. A prominent example is the argsort function in , 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. 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. 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. 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. A simple example of indirect sorting using a quicksort-like approach on indices can be illustrated in :

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

Here, the partition function compares elements via their indices in the data array without modifying data, only swapping index values. 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. 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. Cache-oblivious sorting algorithms address varying memory hierarchies without tuning to specific cache parameters, achieving optimal I/O 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. 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. These algorithms prioritize patterns to exploit hardware caches automatically, outperforming cache-aware sorts on unknown architectures. Index sorting techniques often leverage in-place properties of base algorithms to further limit auxiliary space beyond the index array.

References

  1. 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
  2. A complete study is presented of the best general purpose method for sorting by computer: C. A. R. Hoare's Quicksort algorithm. Special.
  3. 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
  4. 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
  5. 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 ...
Add your contribution
Related Hubs
User Avatar
No comments yet.