11package src .algorithms .sorting .quickSort .threeWayPartitioning ;
22
33/**
4- * Here, we are implementing QuickSort with three-way partitioning where we sort the array in increasing (or more
5- * precisely, non-decreasing) order.
4+ * Here, we are implementing Paranoid QuickSort with three-way partitioning where we sort the array in increasing (or
5+ * more precisely, non-decreasing) order.
66 *
77 * Three-way partitioning is used in QuickSort to tackle the scenario where there are many duplicate elements in the
88 * array being sorted.
1717 *
1818 * Complexity Analysis:
1919 * Time:
20- * - Worst case (poor choice of pivot) : O(n^2 )
20+ * - Worst case: O(nlogn )
2121 * - Average case: O(nlogn)
2222 * - Best case: O(nlogn)
2323 *
2424 * By isolating the elements equal to the pivot into their correct positions during the partitioning step, three-way
2525 * partitioning efficiently handles duplicates, preventing the presence of many duplicates in the array from causing
2626 * the time complexity of QuickSort to degrade to O(n^2).
2727 *
28- * In the worst case where the pivot selected is consistently the smallest or biggest element in the array, the
29- * partitioning of the array around the pivot will be extremely unbalanced, leading to a recurrence relation of:
30- * T(n) = T(n-1) + O(n) => O(n^2). However, the likelihood of this happening is extremely low since pivot selection is
31- * randomised.
32- *
3328 * Space:
3429 * - O(1) since sorting is done in-place
3530 */
@@ -56,9 +51,13 @@ public static void sort(int[] arr) {
5651 public static void quickSort (int [] arr , int start , int end ) {
5752 if (start < end ) {
5853 int [] newIdx = partition (arr , start , end );
59- if (newIdx != null ) {
60- quickSort (arr , start , newIdx [0 ]);
61- quickSort (arr , newIdx [1 ], end );
54+ if (isGoodPivot (newIdx [0 ], newIdx [1 ], start , end )) {
55+ if (newIdx != null ) {
56+ quickSort (arr , start , newIdx [0 ]);
57+ quickSort (arr , newIdx [1 ], end );
58+ }
59+ } else {
60+ quickSort (arr , start , end );
6261 }
6362 }
6463 }
@@ -144,4 +143,43 @@ private static int random(int start, int end) {
144143 return (int ) (Math .random () * (end - start + 1 )) + start ;
145144 }
146145
146+ /**
147+ * Checks if the pivot is a good pivot for the QuickSort algorithm.
148+ * A good pivot helps avoid worst-case behavior in QuickSort.
149+ *
150+ * Since we have three-way partitioning, we cannot use 1/10, 9/10 split of the array as our good pivot condition.
151+ * Note that our goal here is to ensure the sizes of the sub-arrays QuickSort is to recurse on are roughly the same
152+ * to ensure that our partitioning is not too imbalanced. The pivot condition we chose is: the larger sub-array can
153+ * be at most 9 times the size of the smaller sub-array.
154+ *
155+ * If n < 10, such a pivot condition would be meaningless, therefore always return true. This would cause
156+ * the worst case recurrence relation to be T(n) = T(n-1) + O(n) => O(n^2) for small sub-arrays, but the overall
157+ * asymptotic time complexity of Paranoid QuickSort is still O(nlogn).
158+ *
159+ * For an all-duplicates array, all pivots will be considered good pivots, therefore return true.
160+ *
161+ * @param firstPIdx The ending index of the < portion of the sub-array.
162+ * @param secondPIdx The starting index of the > portion of the sub-array.
163+ * @param start The starting index of the current sub-array.
164+ * @param end The ending index of the current sub-array.
165+ * @return True if the given index is a good pivot, false otherwise.
166+ */
167+ public static boolean isGoodPivot (int firstPIdx , int secondPIdx , int start , int end ) {
168+ int n = end - start + 1 ;
169+ if (firstPIdx >= start || secondPIdx <= end ) {
170+ if (end - secondPIdx + 1 > 0 ) { // avoid division by zero
171+ double ratio = (double ) (firstPIdx - start + 1 ) / (end - secondPIdx + 1 );
172+ if (n >= 10 ) {
173+ return ratio >= 1.0 / 9.0 && ratio <= 9 ;
174+ } else {
175+ return true ;
176+ }
177+ } else { // ratio is infinite, imbalanced partition => bad pivot
178+ return false ;
179+ }
180+ } else { // all duplicates array
181+ return true ;
182+ }
183+ }
184+
147185}
0 commit comments