| | 1 | | namespace MoreStructures.Lists.Sorting.QuickSort; |
| | 2 | |
|
| | 3 | | /// <summary> |
| | 4 | | /// A <see cref="IThreeWayPartitionStrategy"/> implementing the Lomuto partition scheme into three segments, the |
| | 5 | | /// middle containing only and all the items equals to the pivot. |
| | 6 | | /// </summary> |
| | 7 | | /// <remarks> |
| | 8 | | /// <para id="advantages"> |
| | 9 | | /// ADVANTAGES |
| | 10 | | /// <br/> |
| | 11 | | /// - Compared to <see cref="LomutoTwoWayPartitionStrategy"/> it has the advantage of not making quicksort |
| | 12 | | /// quadratic when many duplicates are present, since it places all the duplicates in the middle segment, |
| | 13 | | /// which is not recursed over. |
| | 14 | | /// <br/> |
| | 15 | | /// - This advantage comes, however, at the cost of its complexity, since two pointers and three non-empty segments |
| | 16 | | /// have to be setup and kept coherent throughout the entire execution of the partitioning algorithm. |
| | 17 | | /// <br/> |
| | 18 | | /// - Other advantages and disadvantages over other partitioning schemes, such as |
| | 19 | | /// HoareTwoWayPartitionStrategy are described in the documentation of |
| | 20 | | /// <see cref="LomutoTwoWayPartitionStrategy"/>. |
| | 21 | | /// </para> |
| | 22 | | /// <para id="algorithm"> |
| | 23 | | /// ALGORITHM |
| | 24 | | /// <br/> |
| | 25 | | /// - The algorithm closely resembles the one defined in <see cref="LomutoTwoWayPartitionStrategy"/>: |
| | 26 | | /// pivot selection, using the provided <see cref="PivotSelectionStrategy"/> and swapping with L[end] happen in |
| | 27 | | /// exactly the same way. |
| | 28 | | /// <br/> |
| | 29 | | /// - Instead of a single pointer, two pointers i1 and i2 are defined: to the last index of the left segment, and |
| | 30 | | /// to the first index of the right segment, set to start - 1 and start respectively. |
| | 31 | | /// <br/> |
| | 32 | | /// - A running pointer j goes from the start index to the index before the end (the end index contains the pivot). |
| | 33 | | /// <br/> |
| | 34 | | /// - Each item L[j] smaller than the pivot is added after the end of the left segment, which is increased by 1. |
| | 35 | | /// <br/> |
| | 36 | | /// - In order to make room for L[j] in the left segment, the middle segment is "shifted" by one index on the |
| | 37 | | /// right, more precisely by moving the first item of the middle segment after its end, and placing the first |
| | 38 | | /// item of the right segment in position j. |
| | 39 | | /// <br/> |
| | 40 | | /// - Each item L[j] equal to the pivot is added after the end of the middle segment, which is increased by 1. |
| | 41 | | /// <br/> |
| | 42 | | /// - In this case there is no need to touch the left segment, and it's enough to swap L[i2] with L[j], and then |
| | 43 | | /// increase i2. |
| | 44 | | /// <br/> |
| | 45 | | /// - By the time j will have run through all indices except the last, all items except the last will have been |
| | 46 | | /// partitioned into a left, middle and right segments. |
| | 47 | | /// <br/> |
| | 48 | | /// - By incrementing i one more time and swapping L[i] with L[end], the pivot goes into the right position, and |
| | 49 | | /// the three segments are in place: the left going from start to i1, the middle from i1 + 1 to i2 (at least an |
| | 50 | | /// item, possibly more than one) and the right going from i2 + 1 to end. |
| | 51 | | /// <br/> |
| | 52 | | /// - So, <c>(i1 + 1, i2)</c> is returned as result. |
| | 53 | | /// </para> |
| | 54 | | /// <para id="complexity"> |
| | 55 | | /// COMPLEXITY |
| | 56 | | /// <br/> |
| | 57 | | /// - Pivot selection complexity depends on the specific <see cref="IPivotSelectionStrategy"/> strategy |
| | 58 | | /// used, and is not included in this analysis (i.e. it is assumed to be O(1)). |
| | 59 | | /// <br/> |
| | 60 | | /// - Pivot swapping, before and after the main loop, are both constant-time operations. |
| | 61 | | /// <br/> |
| | 62 | | /// - The main loop goes through n - 1 items, where n is the number of items in the window. |
| | 63 | | /// <br/> |
| | 64 | | /// - At each iteration, at most two swap and three increments are performed (j and possibly i1 and i2). |
| | 65 | | /// <br/> |
| | 66 | | /// - Therefore Time Complexity is O(n) and Space Complexity is O(1), since all swaps happen in-place on L. |
| | 67 | | /// </para> |
| | 68 | | /// </remarks> |
| | 69 | | public class LomutoThreeWayPartitionStrategy : IThreeWayPartitionStrategy |
| | 70 | | { |
| | 71 | | /// <summary> |
| | 72 | | /// The strategy to select the pivot. |
| | 73 | | /// </summary> |
| 818 | 74 | | public IPivotSelectionStrategy PivotSelectionStrategy { get; } |
| | 75 | |
|
| | 76 | | /// <summary> |
| | 77 | | /// Builds a Lomuto 3-way partitioner with the provided <paramref name="pivotSelectionStrategy"/>. |
| | 78 | | /// </summary> |
| | 79 | | /// <param name="pivotSelectionStrategy"> |
| | 80 | | /// <inheritdoc cref="PivotSelectionStrategy"/> |
| | 81 | | /// </param> |
| 27 | 82 | | public LomutoThreeWayPartitionStrategy(IPivotSelectionStrategy pivotSelectionStrategy) |
| 27 | 83 | | { |
| 27 | 84 | | PivotSelectionStrategy = pivotSelectionStrategy; |
| 27 | 85 | | } |
| | 86 | |
|
| | 87 | | /// <inheritdoc path="//*[not(self::remarks)]"/> |
| | 88 | | /// <remarks> |
| | 89 | | /// <inheritdoc/> |
| | 90 | | /// <inheritdoc cref="LomutoThreeWayPartitionStrategy"/> |
| | 91 | | /// </remarks> |
| | 92 | | public (int, int) Partition<T>(IList<T> list, IComparer<T> comparer, int start, int end) |
| 818 | 93 | | { |
| 818 | 94 | | var pivotIndex = PivotSelectionStrategy.Select(list, comparer, start, end); |
| 818 | 95 | | (list[end], list[pivotIndex]) = (list[pivotIndex], list[end]); |
| | 96 | |
|
| 818 | 97 | | var pivot = list[end]; |
| 818 | 98 | | var i1 = start - 1; |
| 818 | 99 | | var i2 = start; |
| 5644 | 100 | | for (var j = start; j < end; j++) |
| 2004 | 101 | | { |
| 2004 | 102 | | var comparisonResult = comparer.Compare(list[j], pivot); |
| 2004 | 103 | | if (comparisonResult < 0) |
| 848 | 104 | | { |
| 848 | 105 | | i1++; |
| 848 | 106 | | (list[i2], list[j]) = (list[j], list[i2]); |
| 848 | 107 | | (list[i1], list[i2]) = (list[i2], list[i1]); |
| 848 | 108 | | i2++; |
| 848 | 109 | | } |
| 1156 | 110 | | else if (comparisonResult == 0) |
| 96 | 111 | | { |
| 96 | 112 | | (list[i2], list[j]) = (list[j], list[i2]); |
| 96 | 113 | | i2++; |
| 96 | 114 | | } |
| 2004 | 115 | | } |
| | 116 | |
|
| 818 | 117 | | (list[i2], list[end]) = (list[end], list[i2]); |
| 818 | 118 | | return (i1 + 1, i2); |
| 818 | 119 | | } |
| | 120 | | } |