Three Partition Methods for QuickSort
Problem Description
In the quicksort algorithm, partitioning is the core step. Its goal is to rearrange an array based on a chosen pivot element, such that all elements smaller than the pivot are to its left, and all elements greater than the pivot are to its right. Different partitioning methods affect the algorithm's performance and code implementation. This task requires mastering and implementing three classic partitioning methods: the Lomuto partition scheme, the Hoare partition scheme, and the three-way partition scheme.
Solution Process
-
Understanding the Goal of Partitioning
Given an arrayarrand its left and right boundariesleftandright, select a pivot elementpivot. After the partition operation, the array should satisfy:- All elements in
arr[left...p-1]are <=pivot(for Lomuto and Hoare) or <pivot(for the strict left segment in three-way partition). - All elements in
arr[p+1...right]are >=pivot(for Lomuto and Hoare) or >pivot(for the strict right segment in three-way partition). - The pivot element
pivotitself is at its final correct positionp(for Lomuto and Hoare) or within the middle segment (for three-way partition).
- All elements in
-
Method One: Lomuto Partition Scheme
This is the most intuitive and easiest-to-understand partitioning method.-
Step-by-Step Details:
- Choose Pivot: Typically, select the rightmost element
arr[right]as thepivot. - Initialize Pointer: Set a pointer
iinitialized toleft - 1. All elements atiand to its left represent the "processed and less than or equal to pivot" section. - Traverse the Array: Use another pointer
jto traverse fromlefttoright - 1(sincerightis the pivot). - Compare and Swap: For each
arr[j]:- If
arr[j] <= pivot, this element should belong to the "less than or equal to pivot" region. We expand this region by one: first executei = i + 1, then swaparr[i]andarr[j]. - If
arr[j] > pivot, do nothing, and letjcontinue moving forward.
- If
- Place the Pivot: After the traversal,
ipoints to the last element of the "less than or equal to pivot" region. At this point,i+1is the correct position for the pivot. We swaparr[i+1]andarr[right](the pivot). - Return Pivot Position: Return
i+1as the final position of the pivot after this partition.
- Choose Pivot: Typically, select the rightmost element
-
Code Example (Python):
def lomuto_partition(arr, left, right): pivot = arr[right] # Choose rightmost element as pivot i = left - 1 # Right boundary of <= region for j in range(left, right): # j traverses from left to right-1 if arr[j] <= pivot: i += 1 arr[i], arr[j] = arr[j], arr[i] # Swap small element to <= region # Place pivot in correct position arr[i + 1], arr[right] = arr[right], arr[i + 1] return i + 1 # Return final pivot position -
Characteristics: The code is concise, but when all elements are equal, it results in the worst-case
O(n²)because each partition is extremely unbalanced (one part is n-1, the other is 0).
-
-
Method Two: Hoare Partition Scheme
This is the original method proposed by Tony Hoare, the inventor of quicksort. It is generally more efficient than Lomuto's method and involves fewer swaps.-
Step-by-Step Details:
- Choose Pivot: Typically, select the leftmost element
arr[left]as thepivot. - Initialize Pointers: Set two pointers,
iinitialized toleft - 1andjinitialized toright + 1. - Scan Towards Each Other:
- Move
ifrom left to right until an element>= pivotis found. - Move
jfrom right to left until an element<= pivotis found. - If at this point
i < j, swaparr[i]andarr[j]. Becausearr[i](a "large" element) is incorrectly on the left, andarr[j](a "small" element) is incorrectly on the right; swapping them moves towards correctness.
- Move
- Termination Condition: Repeat step 3 until
i >= j. At this point,jis the partition boundary. - Return Boundary: Return
j. Note that the pivot element is not necessarily at positionj, but it is guaranteed thatarr[left...j] <= pivotandarr[j+1...right] >= pivot.
- Choose Pivot: Typically, select the leftmost element
-
Code Example (Python):
def hoare_partition(arr, left, right): pivot = arr[left] # Choose leftmost element as pivot i = left - 1 j = right + 1 while True: # Note: Move pointer first, then compare i += 1 while arr[i] < pivot: # Find first >= pivot i += 1 j -= 1 while arr[j] > pivot: # Find first <= pivot j -= 1 if i >= j: return j # Return partition boundary j arr[i], arr[j] = arr[j], arr[i] -
Characteristics: More efficient, but slightly more complex to understand. Note that during recursive calls, the intervals should be divided into
[left, j]and[j+1, right].
-
-
Method Three: Three-Way Partition Scheme
This is the optimal method for handling arrays containing many duplicate elements, potentially optimizing complexity close toO(n).-
Step-by-Step Details:
- Choose Pivot: Similarly, select a
pivot(e.g.,arr[left]). - Initialize Pointers: Set three pointers:
lt(less than): All elements at and to the left ofltare strictly less than pivot. Initialized toleft - 1.i(current): Index of the element currently being checked. Initialized toleft.gt(greater than): All elements at and to the right ofgtare strictly greater than pivot. Initialized toright + 1.
- Traverse and Classify: While
i < gt, loop:- If
arr[i] < pivot: It belongs to the "small" region. Swaparr[i]with the element at positionlt+1, thenlt++andi++. Becauselt+1might be the first element of the equal region; after swapping, the equal element moves back, and the small element moves forward. - If
arr[i] == pivot: It is already in the correct position (middle segment), simplyi++. - If
arr[i] > pivot: It belongs to the "large" region. Swaparr[i]with the element at positiongt-1, thengt--. Note, do not incrementihere because the element swapped fromgt-1hasn't been checked yet.
- If
- Partition Result: After the loop ends, the array is divided into three segments:
[left, lt]: Elements strictly less thanpivot.[lt+1, gt-1]: Elements equal topivot.[gt, right]: Elements strictly greater thanpivot.
- Recursion: Only need to recursively sort the strictly less than and strictly greater than segments; the middle segment equal to the pivot is already sorted.
- Choose Pivot: Similarly, select a
-
Code Example (Python):
def three_way_partition(arr, left, right): if left >= right: return pivot = arr[left] lt = left - 1 i = left gt = right + 1 while i < gt: if arr[i] < pivot: lt += 1 arr[lt], arr[i] = arr[i], arr[lt] i += 1 elif arr[i] == pivot: i += 1 else: # arr[i] > pivot gt -= 1 arr[gt], arr[i] = arr[i], arr[gt] # Recursively sort the less-than and greater-than regions # quick_sort_3way(arr, left, lt) # quick_sort_3way(arr, gt, right) return lt, gt # Return the two boundaries -
Characteristics: Effectively handles duplicate elements and is one of the preferred quicksort optimizations in practical engineering (e.g., used in Java's
Arrays.sort()).
-
Summary
- Lomuto Scheme: Easy to understand and implement, often the first choice in teaching, but slightly less efficient.
- Hoare Scheme: The original quicksort method, fewer swaps, higher efficiency.
- Three-Way Partition: Best performance for arrays with many duplicate elements, avoiding performance degradation due to duplicates. In practical applications (e.g., Java's standard library), three-way partition is a common choice.