Quicksort variant that partitions the array using two pivots into three regions.
Dual pivot quicksort extends quicksort by using two pivots instead of one. It partitions the array into three regions in a single pass:
- elements less than the first pivot
- elements between the two pivots
- elements greater than the second pivot
This approach reduces comparisons in practice and improves performance on modern hardware.
It is used in standard library implementations such as Java for primitive arrays.
Problem
Given an array of length , reorder it such that:
Idea
Choose two pivots and such that:
Partition the array into three regions:
Then recursively sort the three regions.
Algorithm
dual_pivot_quicksort(A, lo, hi):
if lo >= hi:
return
if A[lo] > A[hi]:
swap A[lo], A[hi]
p = A[lo]
q = A[hi]
lt = lo + 1
gt = hi - 1
i = lo + 1
while i <= gt:
if A[i] < p:
swap A[i], A[lt]
lt += 1
else if A[i] > q:
while A[gt] > q and i < gt:
gt -= 1
swap A[i], A[gt]
gt -= 1
if A[i] < p:
swap A[i], A[lt]
lt += 1
i += 1
lt -= 1
gt += 1
swap A[lo], A[lt]
swap A[hi], A[gt]
dual_pivot_quicksort(A, lo, lt - 1)
dual_pivot_quicksort(A, lt + 1, gt - 1)
dual_pivot_quicksort(A, gt + 1, hi)Example
Let:
Choose pivots:
Partition:
| region | values |
|---|---|
| < 3 | [1, 2] |
| between | [3, 5, 7] |
| > 7 | [9, 8] |
Recursively sort each region:
Final result:
Correctness
The partition step ensures:
- all elements before are smaller than
- all elements between and lie within the interval
- all elements after are larger than
Placing the pivots into their correct positions divides the array into three independent subproblems. Recursively sorting these regions produces a fully sorted array.
Complexity
| case | time |
|---|---|
| best | |
| average | |
| worst |
In practice, dual pivot quicksort reduces constant factors compared to standard quicksort.
Space:
| case | space |
|---|---|
| average | |
| worst |
Properties
| property | value |
|---|---|
| stable | no |
| in-place | yes |
| partitioning | three-way |
| practical speed | very fast |
Notes
Dual pivot quicksort often outperforms single pivot quicksort due to fewer comparisons and better cache usage. However, it is more complex and sensitive to implementation details.
It works best when pivot selection produces balanced partitions.
Implementation
def dual_pivot_quicksort(a):
def sort(lo, hi):
if lo >= hi:
return
if a[lo] > a[hi]:
a[lo], a[hi] = a[hi], a[lo]
p, q = a[lo], a[hi]
lt = lo + 1
gt = hi - 1
i = lo + 1
while i <= gt:
if a[i] < p:
a[i], a[lt] = a[lt], a[i]
lt += 1
elif a[i] > q:
while a[gt] > q and i < gt:
gt -= 1
a[i], a[gt] = a[gt], a[i]
gt -= 1
if a[i] < p:
a[i], a[lt] = a[lt], a[i]
lt += 1
i += 1
lt -= 1
gt += 1
a[lo], a[lt] = a[lt], a[lo]
a[hi], a[gt] = a[gt], a[hi]
sort(lo, lt - 1)
sort(lt + 1, gt - 1)
sort(gt + 1, hi)
sort(0, len(a) - 1)
return afunc DualPivotQuickSort(a []int) {
var sort func(int, int)
sort = func(lo, hi int) {
if lo >= hi {
return
}
if a[lo] > a[hi] {
a[lo], a[hi] = a[hi], a[lo]
}
p, q := a[lo], a[hi]
lt, gt := lo+1, hi-1
i := lo + 1
for i <= gt {
if a[i] < p {
a[i], a[lt] = a[lt], a[i]
lt++
} else if a[i] > q {
for a[gt] > q && i < gt {
gt--
}
a[i], a[gt] = a[gt], a[i]
gt--
if a[i] < p {
a[i], a[lt] = a[lt], a[i]
lt++
}
}
i++
}
lt--
gt++
a[lo], a[lt] = a[lt], a[lo]
a[hi], a[gt] = a[gt], a[hi]
sort(lo, lt-1)
sort(lt+1, gt-1)
sort(gt+1, hi)
}
sort(0, len(a)-1)
}