1
+ package src .algorithms .sorting .quickSort .threeWayPartitioning ;
2
+
3
+ /**
4
+ * Here, we are implementing QuickSort with three-way partitioning where we sort the array in increasing (or more
5
+ * precisely, non-decreasing) order.
6
+ *
7
+ * Three-way partitioning is used in QuickSort to tackle the scenario where there are many duplicate elements in the
8
+ * array being sorted.
9
+ *
10
+ * The idea behind three-way partitioning is to divide the array into three sections: elements less than the pivot,
11
+ * elements equal to the pivot, and elements greater than the pivot. By doing so, we can avoid unnecessary comparisons
12
+ * and swaps with duplicate elements, making the sorting process more efficient.
13
+ *
14
+ * Complexity Analysis:
15
+ * Time:
16
+ * - Worst case (poor choice of pivot): O(n^2)
17
+ * - Average case: O(nlogn)
18
+ * - Best case: O(nlogn)
19
+ *
20
+ * By isolating the elements equal to the pivot into their correct positions during the partitioning step, three-way
21
+ * partitioning efficiently handles duplicates, preventing the presence of many duplicates in the array from causing
22
+ * the time complexity of QuickSort to degrade to O(n^2).
23
+ *
24
+ * In the worst case where the pivot selected is consistently the smallest or biggest element in the array, the
25
+ * partitioning of the array around the pivot will be extremely unbalanced, leading to a recurrence relation of:
26
+ * T(n) = T(n-1) + O(n) => O(n^2). However, the likelihood of this happening is extremely low since pivot selection is
27
+ * randomised.
28
+ *
29
+ * Space:
30
+ * - O(1) since sorting is done in-place
31
+ */
32
+
33
+ public class QuickSort {
34
+ /**
35
+ * Sorts the given array in-place in non-decreasing order.
36
+ *
37
+ * @param arr array to be sorted.
38
+ */
39
+ public static void sort (int [] arr ) {
40
+ int n = arr .length ;
41
+ quickSort (arr , 0 , n - 1 );
42
+ }
43
+
44
+ /**
45
+ * Recursively sorts the sub-array from index 'start' to index 'end' in non-decreasing order
46
+ * using the QuickSort algorithm.
47
+ *
48
+ * @param arr the array containing the sub-array to be sorted.
49
+ * @param start the starting index (inclusive) of the sub-array to be sorted.
50
+ * @param end the ending index (inclusive) of the sub-array to be sorted.
51
+ */
52
+ public static void quickSort (int [] arr , int start , int end ) {
53
+ if (start < end ) {
54
+ int [] newIdx = partition (arr , start , end );
55
+ if (newIdx != null ) {
56
+ quickSort (arr , start , newIdx [0 ]);
57
+ quickSort (arr , newIdx [1 ], end );
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Partitions the sub-array from index 'start' to index 'end' around a randomly selected pivot element.
64
+ * The elements less than or equal to the pivot are placed on the left side, and the elements greater than
65
+ * the pivot are placed on the right side.
66
+ *
67
+ * @param arr the array containing the sub-array to be partitioned.
68
+ * @param start the starting index (inclusive) of the sub-array to be partitioned.
69
+ * @param end the ending index (inclusive) of the sub-array to be partitioned.
70
+ * @return An integer array containing the indices that represent the boundaries
71
+ * of the three partitions: [ending idx of the < portion of the array, starting idx of the > portion of the array]
72
+ * if length of arr > 1, else null
73
+ */
74
+ private static int [] partition (int [] arr , int start , int end ) {
75
+ // ___<___ ___=___ ___IP___ ___>___
76
+ // ^ ^ ^ ^
77
+
78
+ if (arr .length > 1 ) {
79
+ int pivotIdx = random (start , end ); // pick a pivot
80
+ int pivot = arr [pivotIdx ];
81
+
82
+ swap (arr , pivotIdx , start ); // swap the pivot to the start of the array, arr[start] is now our = portion
83
+ // of the array
84
+
85
+ int eqStart = start ;
86
+ int eqEnd = start ;
87
+ int ipStart = start + 1 ;
88
+ int ipEnd = end ;
89
+
90
+ while (ipStart <= ipEnd ) {
91
+ int curr = arr [ipStart ];
92
+ // case 1: < pivot
93
+ if (curr < pivot ) {
94
+ swap (arr , ipStart , eqStart ); // do a swap with eqStart
95
+
96
+ // increment eqStart, ipStart, and eqEnd
97
+ eqStart ++;
98
+ ipStart ++;
99
+ eqEnd ++;
100
+ // case 2: = pivot
101
+ } else if (curr == pivot ) {
102
+ // simply increment eqEnd and ipStart
103
+ eqEnd ++;
104
+ ipStart ++;
105
+ // case 3: > pivot
106
+ } else {
107
+ swap (arr , ipStart , ipEnd ); // do a swap with ipEnd
108
+ // decrement ipEnd
109
+ ipEnd --;
110
+ }
111
+ }
112
+ int [] result = {eqStart - 1 , eqEnd }; //return
113
+ return result ;
114
+ } else {
115
+ return null ;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Swaps the elements at indices 'i' and 'j' in the given array.
121
+ *
122
+ * @param arr the array in which the elements should be swapped.
123
+ * @param i the index of the first element to be swapped.
124
+ * @param j the index of the second element to be swapped.
125
+ */
126
+ private static void swap (int [] arr , int i , int j ) {
127
+ int temp = arr [i ];
128
+ arr [i ] = arr [j ];
129
+ arr [j ] = temp ;
130
+ }
131
+
132
+ /**
133
+ * Generates a random integer within the range [start, end].
134
+ *
135
+ * @param start the lower bound of the random integer (inclusive).
136
+ * @param end the upper bound of the random integer (inclusive).
137
+ * @return a random integer within the specified range.
138
+ */
139
+ private static int random (int start , int end ) {
140
+ return (int ) (Math .random () * (end - start + 1 )) + start ;
141
+ }
142
+
143
+ }
0 commit comments