Skip to content

Commit 3e1ec96

Browse files
committed
Changed Hoare's to lecture implementation
1 parent 9972919 commit 3e1ec96

File tree

6 files changed

+113
-59
lines changed

6 files changed

+113
-59
lines changed

assets/Hoares.jpeg

-56.7 KB
Binary file not shown.

assets/LectureHoares.jpeg

112 KB
Loading

assets/UsualHoares.jpeg

97 KB
Loading
Lines changed: 24 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,15 @@
11
package src.algorithms.sorting.quickSort.hoares;
22

3-
import java.lang.Math;
4-
53
/**
64
* Here, we are implementing Hoares's QuickSort where we sort the array in increasing (or more precisely,
7-
* non-decreasing) order.
8-
*
9-
* Brief Description:
10-
* Hoare's QuickSort operates by selecting a pivot element from the input array and rearranging the elements such that
11-
* all elements in A[start, returnIdx] are <= pivot and all elements in A[returnIdx + 1, end] are >= pivot, where
12-
* returnIdx is the index returned by the sub-routine partition.
13-
*
14-
* After partitioning, the algorithm recursively applies the same process to the left and right sub-arrays, effectively
15-
* sorting the entire array.
16-
*
17-
* The Hoare's partition scheme works by initializing two pointers that start at two ends. The two pointers move toward
18-
* each other until an inversion is found. This inversion happens when the left pointer is at an element >= pivot, and
19-
* the right pointer is at an element <= pivot. When an inversion is found, the two values are swapped and the pointers
20-
* continue moving towards each other.
5+
* non-decreasing) order. We will follow lecture implementation here, which differs slightly from the usual
6+
* implementation of Hoare's. See more under notes in README.
217
*
228
* Implementation Invariant:
23-
* All elements in A[start, returnIdx] are <= pivot and all elements in A[returnIdx + 1, end] are >= pivot.
9+
* The pivot is in the correct position, with elements to its left being <= it, and elements to its right being > it.
10+
* (We edited the psuedocode a bit to keep the duplicates to the left of the pivot.)
2411
*
25-
* Note:
26-
* - Hoare's partition scheme does not necessarily put the pivot in its correct position. It merely partitions the
27-
* array into <= pivot and >= pivot portions.
28-
* - This is in contrast to Lomuto's partition scheme. Hoare's uses two pointers, while Lomuto's uses one. Hoare's
29-
* partition scheme is generally more efficient as it requires less swaps. See more at
30-
* https://www.geeksforgeeks.org/hoares-vs-lomuto-partition-scheme-quicksort/.
12+
* This implementation picks the element at the index 0 as the pivot.
3113
*/
3214

3315
public class QuickSort {
@@ -51,45 +33,46 @@ public static void sort(int[] arr) {
5133
public static void quickSort(int[] arr, int start, int end) {
5234
if (start < end) {
5335
int pIdx = partition(arr, start, end);
54-
quickSort(arr, start, pIdx);
36+
quickSort(arr, start, pIdx - 1);
5537
quickSort(arr, pIdx + 1, end);
5638
}
5739
}
5840

5941
/**
6042
* Partitions the sub-array from index 'start' to index 'end' around a randomly selected pivot element.
61-
* After this sub-routine is complete, all elements in A[start, returnIdx] are <= pivot and all elements in
62-
* A[returnIdx + 1, end] are >= pivot.
43+
* The elements less than or equal to the pivot are placed on the left side, and the elements greater than
44+
* the pivot are placed on the right side.
6345
*
6446
* Given a sub-array of length m, the time complexity of the partition subroutine is O(m) as we need to iterate
6547
* through every element in the sub-array once.
6648
*
6749
* @param arr the array containing the sub-array to be partitioned.
6850
* @param start the starting index (inclusive) of the sub-array to be partitioned.
6951
* @param end the ending index (inclusive) of the sub-array to be partitioned.
70-
* @return the index at which the array is partitioned at
52+
* @return the index of the pivot element in its correct position after partitioning.
7153
*/
7254
private static int partition(int[] arr, int start, int end) {
73-
int pIdx = random(start, end);
74-
int pivot = arr[pIdx];
75-
int i = start - 1;
76-
int j = end + 1;
55+
int pivot = arr[start];
56+
int low = start + 1;
57+
int high = end;
7758

78-
while (true) {
79-
do {
80-
i++;
81-
} while (arr[i] < pivot);
59+
while (low <= high) {
60+
while (low <= high && arr[low] <= pivot) {// we use <= as opposed to < to pack duplicates to the left side
61+
// of the pivot
62+
low++;
63+
}
8264

83-
do {
84-
j--;
85-
} while (arr[j] > pivot);
65+
while (low <= high && arr[high] > pivot) {
66+
high--;
67+
}
8668

87-
if (i >= j) {
88-
return j;
69+
if (low < high) {
70+
swap(arr, low, high);
8971
}
90-
swap(arr, i, j);
9172
}
73+
swap(arr, start, low - 1);
9274

75+
return low - 1;
9376
}
9477

9578
/**
@@ -105,15 +88,4 @@ private static void swap(int[] arr, int i, int j) {
10588
arr[j] = temp;
10689
}
10790

108-
/**
109-
* Generates a random integer within the range [start, end].
110-
*
111-
* @param start the lower bound of the random integer (inclusive).
112-
* @param end the upper bound of the random integer (inclusive).
113-
* @return a random integer within the specified range.
114-
*/
115-
private static int random(int start, int end) {
116-
return (int) (Math.random() * (end - start + 1)) + start;
117-
}
118-
11991
}
Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,77 @@
1-
![Hoare's QuickSort with random pivot](../../../../../assets/Hoares.jpeg)
1+
# Hoare's QuickSort
2+
3+
Our description, analysis and implementation of Hoare's Quicksort here will follow that of lecture implementation.
4+
Note that the usual Hoare's QuickSort differs slightly from lecture implementation, see more under Notes.
5+
6+
This version of QuickSort assumes the **absence of duplicates** in our array.
7+
8+
QuickSort is a sorting algorithm based on the divide-and-conquer strategy. It works by selecting a pivot element from
9+
the array and rearranging the elements such that all elements less than the pivot are on its left, and
10+
all elements greater than the pivot are on its right. This effectively partitions the array into two parts. The same
11+
process is then applied recursively to the two partitions until the entire array is sorted.
12+
13+
Implementation Invariant:
14+
15+
The pivot is in the correct position, with elements to its left being < it, and elements to its right being > it.
16+
17+
![Lecture Hoare's QuickSort](../../../../../assets/LectureHoares.jpeg)
18+
Example Credits: Prof Seth/Lecture Slides
19+
20+
## Complexity Analysis
21+
Complexity Analysis: (this analysis is based on fixed index pivot selection)
22+
23+
Time:
24+
- Expected worst case (poor choice of pivot): O(n^2)
25+
- Expected average case: O(nlogn)
26+
- Expected best case (balanced pivot): O(nlogn)
27+
28+
In the best case of a balanced pivot, the partitioning process divides the array in half, which leads to log n
29+
levels of recursion. Given a sub-array of length m, the time complexity of the partition subroutine is O(m) as we
30+
need to iterate through every element in the sub-array once.
31+
Therefore, the recurrence relation is: T(n) = 2T(n/2) + O(n) => O(nlogn).
32+
33+
Even in the average case where the chosen pivot partitions the array by a fraction, there will still be log n levels
34+
of recursion. (e.g. T(n) = T(n/10) + T(9n/10) + O(n) => O(nlogn))
35+
36+
However, using a fixed pivot, such as always choosing the first element as the pivot, can lead to worst-case behavior,
37+
especially when the array is already sorted or has a specific pattern. This is because in such cases, the partitioning
38+
might create highly unbalanced sub-arrays, causing the algorithm to degrade to O(n^2) time complexity.
39+
40+
## Notes
41+
42+
### Presence of Duplicates
43+
The above analysis assumes the absence of duplicates in our array. We can change the implementation slightly to keep
44+
all elements = pivot to the left of the pivot element. In this case, if there are many duplicates in the array,
45+
e.g. {1, 1, 1, 1}, the 1st pivot will be placed in the 3rd idx, and 2nd pivot in 2nd idx, 3rd pivot in the 1st idx and
46+
4th pivot in the 0th idx. As we observe, the presence of many duplicates in the array leads to extremely unbalanced
47+
partitioning, leading to a O(n^2) time complexity.
48+
49+
### Usual Implementation of Hoare's QuickSort
50+
The usual implementation of Hoare's partition scheme does not necessarily put the pivot in its correct position. It
51+
merely partitions the array into <= pivot and >= pivot portions.
52+
53+
Brief Description:
54+
55+
Hoare's QuickSort operates by selecting a pivot element from the input array and rearranging the elements such
56+
that all elements in A[start, returnIdx] are <= pivot and all elements in A[returnIdx + 1, end] are >= pivot,
57+
where returnIdx is the index returned by the sub-routine partition.
58+
59+
After partitioning, the algorithm recursively applies the same process to the left and right sub-arrays, effectively
60+
sorting the entire array.
61+
62+
The Hoare's partition scheme works by initializing two pointers that start at two ends. The two pointers move toward
63+
each other until an inversion is found. This inversion happens when the left pointer is at an element >= pivot, and
64+
the right pointer is at an element <= pivot. When an inversion is found, the two values are swapped and the pointers
65+
continue moving towards each other.
66+
67+
![Usual Hoare's QuickSort with random pivot](../../../../../assets/UsualHoares.jpeg)
68+
69+
Implementation Invariant:
70+
71+
All elements in A[start, returnIdx] are <= pivot and all elements in A[returnIdx + 1, end] are >= pivot.
72+
73+
## Hoare's vs Lomuto's QuickSort
74+
Hoare's partition scheme is in contrast to Lomuto's partition scheme. Hoare's uses two pointers, while Lomuto's uses
75+
one. Hoare's partition scheme is generally more efficient as it requires less swaps. See more at
76+
https://www.geeksforgeeks.org/hoares-vs-lomuto-partition-scheme-quicksort/.
77+

test/algorithms/quickSort/hoares/QuickSortTest.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,43 +13,49 @@ public class QuickSortTest {
1313
@Test
1414
public void test_selectionSort_shouldReturnSortedArray() {
1515
int[] firstArray =
16-
new int[] {2, 3, 4, 1, 2, 5, 6, 7, 10, 15, 20, 13, 15, 1, 2, 15, 12, 20, 21, 120, 11, 5, 7, 85, 30};
16+
new int[] {22, 1, 6, 40, 32, 10, 18, 4, 50};
1717
int[] firstResult = Arrays.copyOf(firstArray, firstArray.length);
1818
QuickSort.sort(firstResult);
1919

2020
int[] secondArray
21-
= new int[] {9, 1, 2, 8, 7, 3, 4, 6, 5, 5, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
21+
= new int[] {2, 3, 4, 1, 2, 5, 6, 7, 10, 15, 20, 13, 15, 1, 2, 15, 12, 20, 21, 120, 11, 5, 7, 85, 30};
2222
int[] secondResult = Arrays.copyOf(secondArray, secondArray.length);
2323
QuickSort.sort(secondResult);
2424

25-
int[] thirdArray = new int[] {};
25+
int[] thirdArray = new int[] {9, 1, 2, 8, 7, 3, 4, 6, 5, 5, 9, 8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
2626
int[] thirdResult = Arrays.copyOf(thirdArray, thirdArray.length);
2727
QuickSort.sort(thirdResult);
2828

29-
int[] fourthArray = new int[] {1};
29+
int[] fourthArray = new int[] {};
3030
int[] fourthResult = Arrays.copyOf(fourthArray, fourthArray.length);
3131
QuickSort.sort(fourthResult);
3232

33-
int[] fifthArray = new int[] {5,1,1,2,0,0};
33+
int[] fifthArray = new int[] {1};
3434
int[] fifthResult = Arrays.copyOf(fifthArray, fifthArray.length);
3535
QuickSort.sort(fifthResult);
3636

37-
int[] sixthArray = new int[] {1,1,1,1,1,1,1,1,1,1,1,1,1,1};
37+
int[] sixthArray = new int[] {5,1,1,2,0,0};
3838
int[] sixthResult = Arrays.copyOf(sixthArray, sixthArray.length);
3939
QuickSort.sort(sixthResult);
4040

41+
int[] seventhArray = new int[] {1,1,1,1,1,1,1,1,1,1,1,1,1,1};
42+
int[] seventhResult = Arrays.copyOf(seventhArray, seventhArray.length);
43+
QuickSort.sort(seventhResult);
44+
4145
Arrays.sort(firstArray); // get expected result
4246
Arrays.sort(secondArray); // get expected result
4347
Arrays.sort(thirdArray); // get expected result
4448
Arrays.sort(fourthArray); // get expected result
4549
Arrays.sort(fifthArray); // get expected result
4650
Arrays.sort(sixthArray); // get expected result
51+
Arrays.sort(seventhArray); // get expected result
4752

4853
assertArrayEquals(firstResult, firstArray);
4954
assertArrayEquals(secondResult, secondArray);
5055
assertArrayEquals(thirdResult, thirdArray);
5156
assertArrayEquals(fourthResult, fourthArray);
5257
assertArrayEquals(fifthResult, fifthArray);
5358
assertArrayEquals(sixthResult, sixthArray);
59+
assertArrayEquals(seventhResult, seventhArray);
5460
}
5561
}

0 commit comments

Comments
 (0)