Skip to content

Commit 9b5238e

Browse files
committed
docs: refactor quicksort readmes
1 parent 7ba04f8 commit 9b5238e

File tree

8 files changed

+101
-85
lines changed

8 files changed

+101
-85
lines changed

src/main/java/algorithms/sorting/mergeSort/recursive/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Merge Sort
22

3-
### Brief Description:
3+
### Background
44
MergeSort is a divide-and-conquer sorting algorithm. The recursive implementation takes a top-down approach by
55
recursively dividing the array into two halves, sorting each half separately, and then merging the sorted halves
66
to produce the final sorted output.
@@ -9,11 +9,11 @@ to produce the final sorted output.
99

1010
Image Source: https://www.101computing.net/merge-sort-algorithm/
1111

12-
### Implementation Invariant (for the merging subroutine):
12+
### Implementation Invariant (for the merging subroutine)
1313
The sub-array temp[start, (k-1)] consists of the (𝑘−start) smallest elements of arr[start, mid] and
1414
arr[mid + 1, end], in sorted order.
1515

16-
### Complexity Analysis:
16+
### Complexity Analysis
1717
Time:
1818
- Worst case: O(nlogn)
1919
- Average case: O(nlogn)

src/main/java/algorithms/sorting/quickSort/hoares/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ Implementation Invariant:
7878

7979
All elements in A[start, returnIdx] are <= pivot and all elements in A[returnIdx + 1, end] are >= pivot.
8080

81-
## Hoare's vs Lomuto's QuickSort
81+
### Hoare's vs Lomuto's QuickSort
8282

8383
Hoare's partition scheme is in contrast to Lomuto's partition scheme. Hoare's uses two pointers, while Lomuto's uses
8484
one. Hoare's partition scheme is generally more efficient as it requires less swaps. See more at

src/main/java/algorithms/sorting/quickSort/lomuto/QuickSort.java

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,40 +3,6 @@
33
/**
44
* Here, we are implementing Lomuto's QuickSort where we sort the array in increasing (or more precisely,
55
* non-decreasing) order.
6-
* <p>
7-
* Basic Description:
8-
* QuickSort is a divide-and-conquer sorting algorithm. The basic idea behind Quicksort is to choose a pivot element,
9-
* places it in its correct position in the sorted array, and then recursively sorts the sub-arrays on either side of
10-
* the pivot. When we introduce randomization in pivot selection, every element has equal probability of being
11-
* selected as the pivot. This means the chance of an extreme element getting chosen as the pivot is decreased, so we
12-
* reduce the probability of encountering the worst-case scenario of imbalanced partitioning.
13-
* <p>
14-
* Implementation Invariant:
15-
* The pivot is in the correct position, with elements to its left being <= it, and elements to its right being > it.
16-
* <p>
17-
* We are implementing Lomuto's partition scheme here. This is opposed to Hoare's partition scheme, see more at
18-
* https://www.geeksforgeeks.org/hoares-vs-lomuto-partition-scheme-quicksort/.
19-
* <p>
20-
* Complexity Analysis:
21-
* Time:
22-
* - Expected worst case (poor choice of pivot): O(n^2)
23-
* - Expected average case: O(nlogn)
24-
* - Expected best case (balanced pivot): O(nlogn)
25-
* <p>
26-
* In the best case of a balanced pivot, the partitioning process divides the array in half, which leads to log n
27-
* levels of recursion. Given a sub-array of length m, the time complexity of the partition subroutine is O(m) as we
28-
* need to iterate through every element in the sub-array once.
29-
* Therefore, the recurrence relation is: T(n) = 2T(n/2) + O(n) => O(nlogn).
30-
* <p>
31-
* Even in the average case where the chosen pivot partitions the array by a fraction, there will still be log n levels
32-
* of recursion. (e.g. T(n) = T(n/10) + T(9n/10) + O(n) => O(nlogn))
33-
* <p>
34-
* However, if there are many duplicates in the array, e.g. {1, 1, 1, 1}, the 1st pivot will be placed in the 3rd idx,
35-
* and 2nd pivot in 2nd idx, 3rd pivot in the 1st idx and 4th pivot in the 0th idx. As we observe, the presence of many
36-
* duplicates in the array leads to extremely unbalanced partitioning, leading to a O(n^2) time complexity.
37-
* <p>
38-
* Space:
39-
* - O(1) excluding memory allocated to the call stack, since partitioning is done in-place
406
*/
417

428
public class QuickSort {

src/main/java/algorithms/sorting/quickSort/lomuto/README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# Lomuto's QuickSort
2+
3+
## Background
4+
QuickSort is a divide-and-conquer sorting algorithm. The basic idea behind Quicksort is to choose a pivot element,
5+
places it in its correct position in the sorted array, and then recursively sorts the sub-arrays on either side of
6+
the pivot. When we introduce randomization in pivot selection, every element has equal probability of being
7+
selected as the pivot. This means the chance of an extreme element getting chosen as the pivot is decreased, so we
8+
reduce the probability of encountering the worst-case scenario of imbalanced partitioning.
9+
110
This is how QuickSort works if we always pick the first element as the pivot with Lomuto's partitioning.
211

312
![QuickSort with first element as pivot](../../../../../../../docs/assets/images/QuickSortFirstPivot.png)
@@ -9,3 +18,34 @@ need to do is to swap the random pivot to the first element in the array, then p
918
then swap the pivot back to its correct position. Below is an illustration:
1019

1120
![Lomuto's QuickSort with random pivot](../../../../../../../docs/assets/images/Lomutos.jpeg)
21+
22+
## Implementation Invariant
23+
The pivot is in the correct position, with elements to its left being <= it, and elements to its right being > it.
24+
25+
## Complexity Analysis:
26+
Time:
27+
- Expected worst case (poor choice of pivot): O(n^2)
28+
- Expected average case: O(nlogn)
29+
- Expected best case (balanced pivot): O(nlogn)
30+
31+
In the best case of a balanced pivot, the partitioning process divides the array in half, which leads to log n
32+
levels of recursion. Given a sub-array of length m, the time complexity of the partition subroutine is O(m) as we
33+
need to iterate through every element in the sub-array once.
34+
Therefore, the recurrence relation is: T(n) = 2T(n/2) + O(n) => O(nlogn).
35+
36+
Even in the average case where the chosen pivot partitions the array by a fraction, there will still be log n levels
37+
of recursion. (e.g. T(n) = T(n/10) + T(9n/10) + O(n) => O(nlogn))
38+
39+
However, if there are many duplicates in the array, e.g. {1, 1, 1, 1}, the 1st pivot will be placed in the 3rd idx,
40+
and 2nd pivot in 2nd idx, 3rd pivot in the 1st idx and 4th pivot in the 0th idx. As we observe, the presence of many
41+
duplicates in the array leads to extremely unbalanced partitioning, leading to a O(n^2) time complexity.
42+
43+
Space:
44+
- O(1) excluding memory allocated to the call stack, since partitioning is done in-place
45+
46+
## Notes
47+
### Lomuto's vs Hoare's QuickSort
48+
49+
Lomuto's partition scheme is in contrast to Hoare's partition scheme. Hoare's uses two pointers, while Lomuto's uses
50+
one. Hoare's partition scheme is generally more efficient as it requires less swaps. See more at
51+
https://www.geeksforgeeks.org/hoares-vs-lomuto-partition-scheme-quicksort/.

src/main/java/algorithms/sorting/quickSort/paranoid/QuickSort.java

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,9 @@
33
/**
44
* Here, we are implementing Paranoid QuickSort where we sort the array in increasing (or more precisely,
55
* non-decreasing) order.
6-
* <p>
7-
* This is basically Lomuto's QuickSort, with an additional check to guarantee a good pivot.
8-
* <p>
9-
* Complexity Analysis:
10-
* Time: (this analysis assumes the absence of many duplicates in our array)
11-
* - Expected worst case: O(nlogn)
12-
* - Expected average case: O(nlogn)
13-
* - Expected best case: O(nlogn)
14-
* <p>
15-
* The additional check to guarantee a good pivot guards against the worst case scenario where the chosen pivot results
16-
* in an extremely imbalanced partitioning. Since the chosen pivot has to at least partition the array into a
17-
* 1/10, 9/10 split, the recurrence relation will be: T(n) = T(n/10) + T(9n/10) + n(# iterations of pivot selection).
18-
* <p>
19-
* The number of iterations of pivot selection is expected to be <2 (more precisely, 1.25). This is because
20-
* P(good pivot) = 8/10. Expected number of tries to get a good pivot = 1 / P(good pivot) = 10/8 = 1.25.
21-
* <p>
22-
* Therefore, the expected time-complexity is: T(n) = T(n/10) + T(9n/10) + 1.25n => O(nlogn).
23-
* <p>
24-
* Edge case: does not terminate
25-
* The presence of this additional check and repeating pivot selection means that if we have an array of
26-
* length n >= 10 containing all/many duplicates of the same number, any pivot we pick will be a bad pivot and we will
27-
* enter an infinite loop of repeating pivot selection.
28-
* <p>
29-
* Space:
30-
* - O(1) excluding memory allocated to the call stack, since partitioning is done in-place
6+
*
7+
* We are implementing this with the Lomuto's partitioning scheme, with an additional check to guarantee a good pivot.
8+
* You could also implement this with the Hoare's partitioning scheme instead.
319
*/
3210

3311
public class QuickSort {
Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,29 @@
1-
![ParanoidQuickSort](../../../../../../../docs/assets/images/ParanoidQuickSort.jpeg)
1+
# Paranoid QuickSort
2+
3+
### Background
4+
Paranoid Quicksort is the naive quicksort with an additional check to guarantee a good pivot.
5+
6+
![ParanoidQuickSort](../../../../../../../docs/assets/images/ParanoidQuickSort.jpeg)
7+
8+
### Complexity Analysis:
9+
Time: (this analysis assumes the absence of many duplicates in our array)
10+
- Expected worst case: O(nlogn)
11+
- Expected average case: O(nlogn)
12+
- Expected best case: O(nlogn)
13+
14+
The additional check to guarantee a good pivot guards against the worst case scenario where the chosen pivot results
15+
in an extremely imbalanced partitioning. Since the chosen pivot has to at least partition the array into a
16+
1/10, 9/10 split, the recurrence relation will be: T(n) = T(n/10) + T(9n/10) + n(# iterations of pivot selection).
17+
18+
The number of iterations of pivot selection is expected to be <2 (more precisely, 1.25). This is because
19+
P(good pivot) = 8/10. Expected number of tries to get a good pivot = 1 / P(good pivot) = 10/8 = 1.25.
20+
21+
Therefore, the expected time-complexity is: T(n) = T(n/10) + T(9n/10) + 1.25n => O(nlogn).
22+
23+
- Edge case: does not terminate
24+
The presence of this additional check and repeating pivot selection means that if we have an array of
25+
length n >= 10 containing all/many duplicates of the same number, any pivot we pick will be a bad pivot and we will
26+
enter an infinite loop of repeating pivot selection.
27+
28+
Space:
29+
- O(1) excluding memory allocated to the call stack, since partitioning is done in-place

src/main/java/algorithms/sorting/quickSort/threeWayPartitioning/QuickSort.java

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,6 @@
33
/**
44
* Here, we are implementing Paranoid QuickSort with three-way partitioning where we sort the array in increasing (or
55
* more precisely, non-decreasing) order.
6-
* <p>
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-
* <p>
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-
* <p>
14-
* Implementation Invariant:
15-
* The pivot and any element numerically equal to the pivot will be in the correct positions in the array. Elements
16-
* to their left are < them and elements to their right are > than them.
17-
* <p>
18-
* Complexity Analysis:
19-
* Time:
20-
* - Worst case: O(nlogn)
21-
* - Average case: O(nlogn)
22-
* - Best case: O(nlogn)
23-
* <p>
24-
* Space:
25-
* - O(1) excluding memory allocated to the call stack, since partitioning is done in-place
266
*/
277

288
public class QuickSort {
Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,25 @@
1-
![ThreeWayPartitioning](../../../../../../../docs/assets/images/ThreeWayPartitioning.jpeg)
1+
# Three-Way Partitioning
2+
3+
### Background
4+
Three-way partitioning is used in QuickSort to tackle the scenario where there are many duplicate elements in the
5+
array being sorted.
6+
7+
The idea behind three-way partitioning is to divide the array into three sections: elements less than the pivot,
8+
elements equal to the pivot, and elements greater than the pivot. By doing so, we can avoid unnecessary comparisons
9+
and swaps with duplicate elements, making the sorting process more efficient.
10+
11+
![ThreeWayPartitioning](../../../../../../../docs/assets/images/ThreeWayPartitioning.jpeg)
12+
13+
### Implementation Invariant:
14+
The pivot and any element numerically equal to the pivot will be in the correct positions in the array. Elements
15+
to their left are < them and elements to their right are > than them.
16+
17+
### Complexity Analysis:
18+
Time:
19+
- Worst case: O(nlogn)
20+
- Average case: O(nlogn)
21+
- Best case: O(nlogn)
22+
23+
Space:
24+
- O(1) excluding memory allocated to the call stack, since partitioning is done in-place
25+

0 commit comments

Comments
 (0)