| | 1 | | namespace MoreStructures.Lists.Sorting.QuickSort; |
| | 2 | |
|
| | 3 | | /// <summary> |
| | 4 | | /// An <see cref="IInputMutatingSort"/> implementation based on the quicksort algorithm. |
| | 5 | | /// </summary> |
| | 6 | | /// <remarks> |
| | 7 | | /// <para id="advantages"> |
| | 8 | | /// ADVANTAGES AND DISADVANTAGES |
| | 9 | | /// <br/> |
| | 10 | | /// - Like <see cref="MergeSort"/>, it belongs to the category of linearithmic comparison-based sorting algorithm |
| | 11 | | /// (when setup correctly, see the complexity analysis section below). |
| | 12 | | /// <br/> |
| | 13 | | /// - Compared to <see cref="MergeSort"/>, it is <b>in-place</b>, having O(1) Space Complexity. |
| | 14 | | /// <br/> |
| | 15 | | /// - However, it is <b>not stable</b> and it's <b>not as easy parallelizable</b> as <see cref="MergeSort"/>. |
| | 16 | | /// <br/> |
| | 17 | | /// - While it is not optimal in the exact number of comparisons (it does in average 39% more comparisons than |
| | 18 | | /// <see cref="MergeSort"/>, which is optimal on the number of comparisons), it does way less swapping and moving |
| | 19 | | /// operations, resulting in a visibly better performance, especially when cost of moving items is high. |
| | 20 | | /// <br/> |
| | 21 | | /// - For that reason is tipically the <b>algorithm of choice when sorting primitive values or value types</b>, |
| | 22 | | /// such as struct instances, where cost of comparison is low and cost of swapping can be high, depending on the |
| | 23 | | /// size of the struct. |
| | 24 | | /// <br/> |
| | 25 | | /// - When sorting reference types, such as class instances, <see cref="MergeSort"/> is sometimes preferred, since |
| | 26 | | /// swapping items in the list means swapping their references, which are of fixed and small size, and instead |
| | 27 | | /// the cost of comparison can be quite high, depending on the <see cref="IComparable{T}"/> or on the |
| | 28 | | /// <see cref="IComparer{T}"/> implementation used. |
| | 29 | | /// <br/> |
| | 30 | | /// - Compared to most other comparison-based algorithms, a disadvantage of quicksort is that, for it to have |
| | 31 | | /// consistently good performances, it has to be randomized. In such setup, it is <b>not deterministic</b>. |
| | 32 | | /// </para> |
| | 33 | | /// <para id="algorithm"> |
| | 34 | | /// ALGORITHM |
| | 35 | | /// <br/> |
| | 36 | | /// - First, it shuffles the input to sort, by using the <see cref="IShuffleStrategy"/> provided in the |
| | 37 | | /// constructor. |
| | 38 | | /// <br/> |
| | 39 | | /// - Then, it partitions the shuffled input into three segments: left, middle and right. |
| | 40 | | /// <br/> |
| | 41 | | /// - Partitioning is done via the <see cref="IThreeWayPartitionStrategy"/> provided in the constructor. The way in |
| | 42 | | /// which partitions are done, and in particular whether pivot values only appear in the middle segment, only in |
| | 43 | | /// left and/or right segments or in all three segments, depends on the specific strategy used. |
| | 44 | | /// <br/> |
| | 45 | | /// - In either case, the left contains items non-bigger than the pivot (possibly including pivot values), the |
| | 46 | | /// middle items equal to the pivot (and no other values) and the right items non-smaller than the pivot |
| | 47 | | /// (possibly including pivot values). |
| | 48 | | /// <br/> |
| | 49 | | /// - Items in the middle segments have already been placed by the algorithm in their final position. Therefore, |
| | 50 | | /// the middle segment doesn't have to be sorted any further. Left and rights segments, instead, are not sorted. |
| | 51 | | /// <br/> |
| | 52 | | /// - So the algorithm calls recursively the quicksort on the left and right segments, to sort them. |
| | 53 | | /// <br/> |
| | 54 | | /// - When the recursion terminates, the entire list is sorted. |
| | 55 | | /// </para> |
| | 56 | | /// <para id="complexity"> |
| | 57 | | /// COMPLEXITY |
| | 58 | | /// <br/> |
| | 59 | | /// - Worst-case Time Complexity is O(n^2) and Space Complexity is O(1), since sorting happens entirely in place, |
| | 60 | | /// by repeated swapping. |
| | 61 | | /// <br/> |
| | 62 | | /// - Expected Time Complexity heavily depends on the choice of <see cref="IShuffleStrategy"/> and |
| | 63 | | /// <see cref="IThreeWayPartitionStrategy"/>, which in turns depends on the choice of |
| | 64 | | /// <see cref="IPivotSelectionStrategy"/>. |
| | 65 | | /// <br/> |
| | 66 | | /// - For example, using <see cref="IdentityShuffleStrategy"/> and <see cref="StartIndexPivotSelectionStrategy"/> |
| | 67 | | /// with inputs already sorted in ascending order, makes quicksort selects the worst pivot at every iteration, |
| | 68 | | /// partitioning each window of the input list in the most unbalanced way (left segment empty and right segment |
| | 69 | | /// containing all items but the pivot), and making quicksort behaving quadratically. |
| | 70 | | /// <br/> |
| | 71 | | /// - The same happens when using <see cref="IdentityShuffleStrategy"/> and |
| | 72 | | /// <see cref="EndIndexPivotSelectionStrategy"/>, with inputs already sorted in descending order. |
| | 73 | | /// <br/> |
| | 74 | | /// - A tipical setup, yielding good runtime results, is to use a RandomizedShuffleStrategy and a "smart" 3-way |
| | 75 | | /// <see cref="IThreeWayPartitionStrategy"/>, placing all pivot values in the middle segment. That variation of |
| | 76 | | /// the quicksort has expected worst-case Time Complexity linearithmic, i.e. has O(n * log(n)) Time Complexity |
| | 77 | | /// with high probability, and behaves almost linearly with many real case input configurations. |
| | 78 | | /// </para> |
| | 79 | | /// </remarks> |
| | 80 | | public class RecursiveQuickSort : IInputMutatingSort |
| | 81 | | { |
| 138 | 82 | | private IShuffleStrategy ShuffleStrategy { get; } |
| 521 | 83 | | private IThreeWayPartitionStrategy PartitionStrategy { get; } |
| | 84 | |
|
| | 85 | | /// <summary> |
| | 86 | | /// Builds a sorter running quicksort with the provided <paramref name="partitionStrategy"/>. |
| | 87 | | /// </summary> |
| | 88 | | /// <param name="shuffleStrategy"> |
| | 89 | | /// The strategy used to shuffle the input list, before running the actual quicksort algorithm. |
| | 90 | | /// </param> |
| | 91 | | /// <param name="partitionStrategy"> |
| | 92 | | /// The strategy used to partition the sublist of the provided list within the provided start and end indices. |
| | 93 | | /// </param> |
| 48 | 94 | | public RecursiveQuickSort(IShuffleStrategy shuffleStrategy, IThreeWayPartitionStrategy partitionStrategy) |
| 48 | 95 | | { |
| 48 | 96 | | ShuffleStrategy = shuffleStrategy; |
| 48 | 97 | | PartitionStrategy = partitionStrategy; |
| 48 | 98 | | } |
| | 99 | |
|
| | 100 | | /// <inheritdoc path="//*[not(self::summary or self::remarks)]"/> |
| | 101 | | /// <summary> |
| | 102 | | /// <inheritdoc/> |
| | 103 | | /// <br/> |
| | 104 | | /// Uses the quicksort algorithm with the default comparer for <typeparamref name="T"/>, given by |
| | 105 | | /// <see cref="Comparer{T}.Default"/>. |
| | 106 | | /// </summary> |
| | 107 | | /// <remarks> |
| | 108 | | /// <inheritdoc cref="RecursiveQuickSort"/> |
| | 109 | | /// </remarks> |
| | 110 | | public void Sort<T>(IList<T> list) where T : IComparable<T> => |
| 114 | 111 | | Sort(list, Comparer<T>.Default); |
| | 112 | |
|
| | 113 | | /// <inheritdoc path="//*[not(self::summary or self::remarks)]"/> |
| | 114 | | /// <summary> |
| | 115 | | /// <inheritdoc/> |
| | 116 | | /// <br/> |
| | 117 | | /// Uses the quicksort algorithm with the specified <paramref name="comparer"/>. |
| | 118 | | /// </summary> |
| | 119 | | /// <remarks> |
| | 120 | | /// <inheritdoc cref="RecursiveQuickSort"/> |
| | 121 | | /// </remarks> |
| | 122 | | public void Sort<T>(IList<T> list, IComparer<T> comparer) |
| 138 | 123 | | { |
| 138 | 124 | | ShuffleStrategy.Shuffle(list, 0, list.Count - 1); |
| 138 | 125 | | RecursiveSort(list, comparer, 0, list.Count - 1); |
| 138 | 126 | | } |
| | 127 | |
|
| | 128 | | private void RecursiveSort<T>(IList<T> list, IComparer<T> comparer, int start, int end) |
| 1180 | 129 | | { |
| 1839 | 130 | | if (end - start <= 0) return; |
| | 131 | |
|
| 521 | 132 | | var (pivotLowIndex, pivotHighIndex) = PartitionStrategy.Partition(list, comparer, start, end); |
| 521 | 133 | | RecursiveSort(list, comparer, start, pivotLowIndex - 1); |
| 521 | 134 | | RecursiveSort(list, comparer, pivotHighIndex + 1, end); |
| 1180 | 135 | | } |
| | 136 | | } |