Quick Sort in Java
A PT teacher wants a line of students sorted by height. She picks one student, say Advitha, and says: "Everyone shorter than Advitha, stand on her left. Everyone taller, on her right." Advitha is now exactly where she belongs in the final line, even though neither side is sorted yet. The teacher then repeats the same trick on the left group and on the right group. That is quick sort: pick a pivot, partition everything around it, and repeat on each side. It is one of the fastest sorting algorithms in practice. In this lesson, you will learn how partitioning works, write quick sort in Java with a trace, and see its best and worst cases.
What Is Quick Sort?
Quick sort in Java is a divide and conquer sorting algorithm that is used to sort an array by choosing a pivot element, partitioning the array so that smaller elements come before the pivot and larger ones after it, and then recursively sorting the two parts.
- It sorts in place, needing only
O(log n)stack space on average. - It is O(n log n) on average and very fast in practice.
- It is O(n²) in the worst case, when the pivot is always the smallest or largest element.
- It is not stable.
- Java's
Arrays.sortfor primitive arrays uses a dual-pivot quick sort.
How It Works
- Choose a pivot. This lesson uses the last element (the Lomuto scheme).
- Partition: walk through the range, and move every element less than or equal to the pivot to the front part. Then swap the pivot right after that front part. The pivot is now in its final position.
- Recurse on the part left of the pivot and the part right of it.
- A part with 0 or 1 elements is already sorted.
Quick Sort in Java
Let's see an example that sorts the array from the picture and prints every partition.
import java.util.Arrays;
public class QuickSort {
static void quickSort(int[] a, int low, int high) {
if (low >= high) {
return;
}
int p = partition(a, low, high);
System.out.println("pivot " + a[p] + " placed at " + p + ": "
+ Arrays.toString(a));
quickSort(a, low, p - 1);
quickSort(a, p + 1, high);
}
static int partition(int[] a, int low, int high) {
int pivot = a[high];
int i = low - 1; // end of the "small" part
for (int j = low; j < high; j++) {
if (a[j] <= pivot) {
i++;
swap(a, i, j);
}
}
swap(a, i + 1, high); // pivot goes right after it
return i + 1;
}
static void swap(int[] a, int x, int y) {
int t = a[x];
a[x] = a[y];
a[y] = t;
}
public static void main(String[] args) {
int[] heights = {29, 10, 14, 37, 13, 25};
quickSort(heights, 0, heights.length - 1);
System.out.println("sorted: " + Arrays.toString(heights));
}
}pivot 25 placed at 3: [10, 14, 13, 25, 29, 37]
pivot 13 placed at 1: [10, 13, 14, 25, 29, 37]
pivot 37 placed at 5: [10, 13, 14, 25, 29, 37]
sorted: [10, 13, 14, 25, 29, 37]Explanation:
- The first partition uses 25 as the pivot and gives
[10, 14, 13, 25, 29, 37], exactly as in the picture. 25 is at index 3 and will never move again. - Quick sort then works on the left part
[10, 14, 13]with pivot 13, and on the right part[29, 37]with pivot 37. - In
partition,imarks the end of the "less than or equal" part. Every timea[j]belongs there,igrows anda[j]is swapped in. - Unlike merge sort, the work happens before the recursive calls, and no extra array is needed.
Best, Average and Worst Case
The speed depends on how evenly the pivot splits the array.
| Case | When | Big-O |
|---|---|---|
| Best | Each pivot lands in the middle | O(n log n) |
| Average | Random data | O(n log n) |
| Worst | Each pivot is the smallest or largest, for example already sorted input with a last-element pivot | O(n²) |
| Space | Recursion stack | O(log n) average, O(n) worst |
Let's see the worst case happen: on sorted input, the last element is always the largest, so each partition peels off only one element.
public class WorstCase {
static long comparisons = 0;
static void quickSort(int[] a, int low, int high) {
if (low >= high) return;
int pivot = a[high], i = low - 1;
for (int j = low; j < high; j++) {
comparisons++;
if (a[j] <= pivot) {
i++;
int t = a[i]; a[i] = a[j]; a[j] = t;
}
}
int t = a[i + 1]; a[i + 1] = a[high]; a[high] = t;
quickSort(a, low, i);
quickSort(a, i + 2, high);
}
static long run(int[] a) {
comparisons = 0;
quickSort(a, 0, a.length - 1);
return comparisons;
}
public static void main(String[] args) {
int n = 1000;
int[] sorted = new int[n];
int[] mixed = new int[n];
for (int k = 0; k < n; k++) {
sorted[k] = k;
mixed[k] = (k * 7919) % n; // a scrambled order
}
System.out.println("sorted input: " + run(sorted));
System.out.println("mixed input: " + run(mixed));
}
}sorted input: 499500
mixed input: 11414Explanation: on sorted input, quick sort makes about n² / 2 comparisons, almost 500,000 for 1,000 elements. On scrambled input, it makes about 11,400, more than 40 times fewer. Real implementations avoid the worst case by choosing the pivot more carefully: a random element, or the median of three (first, middle and last).
Quick Sort vs Merge Sort
| Point | Quick sort | Merge sort |
|---|---|---|
| Worst case | O(n²) (rare with a good pivot) | O(n log n) always |
| Extra memory | O(log n) stack | O(n) array |
| Stable | No | Yes |
| Speed in practice | Usually faster (works in place, cache friendly) | Slightly slower |
| Used by Java for | Primitive arrays (dual-pivot quick sort) | Objects (TimSort) |
Interview Corner
When does quick sort hit its worst case?
When every pivot is the smallest or largest element, so each partition removes only one element. With a first or last element pivot, sorted or reverse-sorted input causes this.
Why does Java use quick sort for int[] but merge sort for Object[]?
Stability can't be seen with primitives, so the faster in-place quick sort is used. Objects need a stable sort, so TimSort is used.
Is quick sort in place?
Yes. Partitioning swaps elements inside the array; only the recursion stack uses extra memory.
Practice
- Easy: Change
partitionto sort in descending order by changing one comparison. Test it on{29, 10, 14, 37, 13, 25}. - Harder: Before partitioning, swap a random element between
lowandhighinto positionhigh(usejava.util.concurrent.ThreadLocalRandom). RunWorstCaseagain and compare the comparison count on sorted input.
Key Takeaways
Was this tutorial helpful?