Two-way quicksort
The two-way quicksort algorithm is an improved version of randomized quicksort. The partition process uses two index values (i, j) to traverse the array, placing<velements to the left of the position pointed to by index i, and placing>velements to the right of the position pointed to by index j,vRepresents the calibration value.
Applicability
The time and space complexity are the same as randomized quicksort. For arrays with many duplicate elements, using the randomized quicksort from the previous section is very inefficient, because the subarray lengths after partition for data greater than or less than the pivot become extremely unbalanced, and it may even degenerate into an algorithm withO(n*2)time complexity. For this situation, the two-way quicksort algorithm can be used.
Process Diagram
Use two index values (i, j) to traverse our sequence, placing<=velements to the left of the position pointed to by index i, and placing>=velements to the right of the position pointed to by index j, balancing the left and right subarrays.

Java example code
Source code package download:Download
QuickSort2Ways.java file code:
/**
* Two-Way Quick Sort
*/
public class QuickSort2Ways {
// Core code --- start
private static int partition(Comparable[] arr, int l, int r){
// Randomly select a value as the pivot in the range arr[l...r]
swap( arr, l , (int)(Math.random()*(r-l+1))+l );
Comparable v = arr[l];
// arr[l+1...i) <= v; arr(j...r] >= v
int i = l+1, j = r;
while( true ){
while( i <= r && arr[i].compareTo(v) < 0 )
i ++;
while( j >= l+1 && arr[j].compareTo(v) > 0 )
j --;
if( i > j )
break;
swap( arr, i, j );
i ++;
j --;
}
swap(arr, l, j);
return j;
}
// Core code --- end
// Recursively use quicksort to sort the range arr[l...r]
private static void sort(Comparable[] arr, int l, int r){
if (l >= r) {
return;
}
int p = partition(arr, l, r);
sort(arr, l, p-1 );
sort(arr, p+1, r);
}
public static void sort(Comparable[] arr){
int n = arr.length;
sort(arr, 0, n-1);
}
private static void swap(Object[] arr, int i, int j) {
Object t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
// Test QuickSort
public static void main(String[] args) {
// The Two-Way Quick Sort algorithm is also an O(nlogn) complexity algorithm
// Can easily handle data on the order of 1 million within 1 second
// Quick Sort is also an algorithm with O(n log n) complexity
// Can easily handle data on the order of 1 million within 1 second
int N = 1000000;
Integer[] arr = SortTestHelper.generateRandomArray(N, 0, 100000);
sort(arr);
SortTestHelper.printArray(arr);
}
}
Detailed explanation of the algorithm principle
The core of two-way quicksort lies in itsTwo-pointer partitioning methodUnlike one-way quicksort (which scans from only one end), it uses two pointers that scan from the head and tail of the subarray to be sorted toward the middle, ensuring that equal elements are not all pushed to one side.
Core: the two-way partitioning process
Suppose we want to sort the arrayarrmiddle index fromlefttorightportion.
Select pivot value: fromarr[left...right]Randomly select an element from it as the pivot valuepivotRandomization is used to avoid the worst case on sorted arrays.
Initialize pointers:
i = left + 1A pointer scanning from left to right, looking for the firstgreater than or equal topivotelements.j = rightA pointer scanning from right to left, looking for the firstless than or equal topivotelements.
Scanning and swapping loop:
whileThe loop, with the condition beingi <= j。- inner
whileloop: letiKeep moving right untilarr[i] >= pivot。 - inner
whileloop: letjKeep moving left untilarr[j] <= pivot。 - At this point,
arr[i]is a "large" element that should not be in the left half,arr[j]is a "small" element that should not be in the right half. - If at this point
i <= j, then swaparr[i]andarr[j], theni++,j--then continue the outer loop.
Place the pivot value and return the partition point:
- After the loop ends,
iandjalready crossed (j < i). At this pointarr[left](i.e., the pivot) needs to be placed in the correct position. - will
arr[left]andarr[j]swap. BecausejThe position where it finally stops, and the element it points to isthe last element less than or equal to the pivot valueelements. - return
jas the new partition point. At this point,arr[left...j-1] <= pivot,arr[j+1...right] >= pivot, andarr[j] == pivot。
recursively sort
get the partition pointpAfter that, for the left subarrayarr[left...p-1]and the right subarrayarr[p+1...right]Repeat the above process recursively until the subarray length is 1.
Code implementation
Let us understand two-way quicksort concretely through a complete Java implementation.
1. Main sorting function
Example
// Public sorting interface
public static void sort(int[] arr) {
if (arr == null || arr.length < 2) {
return; // Handle boundary condition: array is empty or has only one element, no need to sort
}
quickSort(arr, 0, arr.length - 1); // Call the recursive quick sort function
}
// Recursive quick sort function
private static void quickSort(int[] arr, int left, int right) {
// Recursion termination condition: when left >= right, the subarray is already sorted or empty
if (left >= right) {
return;
}
// Key step: perform two-way partitioning and return the partition index
int p = partition(arr, left, right);
// Recursively sort the left half (left, p-1)
quickSort(arr, left, p - 1);
// Recursively sort the right half (p+1, right)
quickSort(arr, p + 1, right);
}
}
2. Core: two-way partitioning function
This is the core of the algorithm. Please understand it carefully together with the above flowchart and comments.
Example
private static int partition(int[] arr, int left, int right) {
// 1. Randomly select a pivot and swap it to the left position, avoiding the worst case on sorted arrays
int randomIndex = left + (int)(Math.random() * (right - left + 1));
swap(arr, left, randomIndex);
int pivot = arr[left]; // Pivot value
// 2. Initialize the two pointers
// i: scan from left to right, looking for elements >= pivot
// j: scan from right to left, looking for elements <= pivot
int i = left + 1;
int j = right;
// 3. Main loop: while i and j haven't crossed
while (i <= j) {
// 3.1 Move left pointer i: find the first element >= pivot
// Note boundary i <= right to prevent array out-of-bounds
while (i <= right && arr[i] < pivot) {
i++;
}
// 3.2 Move right pointer j: find the first element <= pivot
// Note boundary j >= left+1, because the left position is the pivot itself
while (j >= left + 1 && arr[j] > pivot) {
j--;
}
// 3.3 Check pointer status
// If i > j, scanning is complete, left and right partitions are ready, no need to swap
if (i > j) {
break;
}
// 3.4 Swap arr[i] and arr[j]
// Now arr[i] >= pivot, arr[j] <= pivot, swap them so elements on both sides are in place
swap(arr, i, j);
// After swapping, move pointers to continue scanning
i++;
j--;
}
// 4. Place the pivot at its final correct position j
// After the loop ends, j points to the last element <= pivot
swap(arr, left, j);
// 5. Return the index j of the partition point
return j;
}
// Helper function: swap the positions of two elements in an array
private static void swap(int[] arr, int i, int j) {
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
3. Testing and verification
Let us test our implementation with an array containing duplicate elements.
Example
public static void main(String[] args) {
// Test case 1: array containing many duplicate elements
int[] arr1 = {4, 2, 2, 8, 3, 3, 1, 5, 3, 2};
System.out.println("Before sorting: " + Arrays.toString(arr1));
TwoWayQuickSort.sort(arr1);
System.out.println("After sorting: " + Arrays.toString(arr1));
// Test case 2: large randomly generated array
int[] arr2 = new int[20];
Random rand = new Random();
for (int i = 0; i < arr2.length; i++) {
arr2[i] = rand.nextInt(50); // Generate random numbers from 0-49, duplicates likely
}
System.out.println("\n"Before sorting random array: " + Arrays.toString(arr2));
TwoWayQuickSort.sort(arr2);
System.out.println("After sorting random array: " + Arrays.toString(arr2));
// Verify whether the sorting result is correct
for (int i = 1; i < arr2.length; i++) {
if (arr2[i] < arr2[i-1]) {
System.out.println("Sorting error!");
return;
}
}
System.out.println("Sorting result verification passed!");
}
}
Test data output example:
排序前: [4, 2, 2, 8, 3, 3, 1, 5, 3, 2] 排序后: [1, 2, 2, 2, 3, 3, 3, 4, 5, 8] 随机数组排序前: [17, 33, 12, 48, 8, 2, 41, ...] 随机数组排序后: [2, 8, 12, 17, 33, 33, 41, 48, ...] 排序结果验证通过!
Algorithm analysis and comparison
Time complexity
- Average case:$O(n \log n)$. Two-way quicksort keeps the recursion tree relatively balanced by evenly distributing duplicate elements.
- Worst case:$O(n^2)$. Although randomized pivot selection greatly reduces the probability, the worst case can still occur when every partition is extremely unbalanced (for example, when the pivot is always the current minimum or maximum value).
- Best case:$O(n \log n)$. Each partition divides the array evenly.
Space complexity
- It is mainly the space occupied by the recursive call stack.
- The average depth is $O(\log n)$, and the worst-case depth is $O(n)$.
- Therefore,The average space complexity is $O(\log n)$, and the worst case is $O(n)$.。
Stability
Quicksort (including two-way quicksort) is not a stable sorting algorithm, because during partitioning, swaps of non-adjacent elements can disrupt the original relative order of equal elements.
Comparison of one-way, two-way, and three-way quicksort
| Features | One-way quicksort (Lomuto) | Two-way quicksort | Three-way quicksort |
|---|---|---|---|
| Partition method | Single pointer scans from left to right | Two pointers scan from both ends toward the middle | Three pointers, dividing the array into<pivot, =pivot, >pivotThree parts |
| Handling duplicate elements | Poor, may lead to unbalanced partitioning | Good, can evenly distribute duplicate elements | Optimal, can handle all elements equal to the pivot at once |
| Code complexity | Simple | Medium | Slightly complex |
| Applicable scenarios | Teaching, no/few duplicate elements | General-purpose, especially suitable for scenarios where duplicate elements may exist | Scenarios with a large number of duplicate elements |