The simple implementation idea is to separate the values into three groups: values less than the pivot, values equal to the pivot, and values greater than the pivot.
In pseudocode, the algorithm looks like the following.
algorithm quicksort(A, lo, hi):
if lo < hi then
p :=← pivot(A, lo, hi)
left, right :=← three-way-partition(A, p, lo, hi)
quicksort(A, lo, left - 1)
quicksort(A, right, hi)
The partition procedure looks like the following.
procedure three-way-partition(A, pivot, lo, hi):
l ← lo
r ← lo
u ← hi
while r ≤ u:
if A[r] < pivot:
swap A[l] and A[r]
l ← l + 1
r ← r + 1
else if A[r] > pivot:
swap A[r] and A[u]
u ← u - 1
else: // the element is equal to pivot
r ← r + 1
return l, r
It uses three indices l, r and u (left, right, and upper bound), maintaining the following invariant in the while loop.
lo ≤ l ≤ r ≤ u ≤ hi
- the elements with index in
[lo, l)
are smaller to the pivot. - the elements with index in
[l, r)
are equal to the pivot. - the elements with index in
[r, u]
are not examined yet. - the elements with index in
(u, hi]
are greater than the pivot.
There are a few minor variants. The above should be enough for you to understand what is going on.