Skip to content

Quicksort

Divide and conquer sorting algorithm that partitions the array around a pivot and recursively sorts both sides.

Quicksort is a divide and conquer sorting algorithm. It chooses a pivot value, partitions the array so that smaller elements go before the pivot and larger elements go after it, then recursively sorts both sides.

It is one of the most important practical sorting algorithms because it is fast in memory, has good cache behavior, and usually needs only logarithmic stack space. Its main weakness is that bad pivot choices can produce quadratic time.

Problem

Given an array AA of length nn, reorder it such that:

A[0]A[1]A[n1] A[0] \le A[1] \le \dots \le A[n-1]

Idea

Choose a pivot pp. Rearrange the array into three logical parts:

values p,p,values p \text{values } \le p,\quad p,\quad \text{values } \ge p

After partitioning, the pivot is in its final sorted position. The left and right parts can then be sorted independently.

Algorithm

quicksort(A, lo, hi):
    if lo >= hi:
        return

    p = partition(A, lo, hi)

    quicksort(A, lo, p - 1)
    quicksort(A, p + 1, hi)

One simple partition scheme is Lomuto partition.

partition(A, lo, hi):
    pivot = A[hi]
    i = lo

    for j from lo to hi - 1:
        if A[j] <= pivot:
            swap A[i] and A[j]
            i += 1

    swap A[i] and A[hi]
    return i

Example

Let:

A=[4,2,7,1,5] A = [4, 2, 7, 1, 5]

Choose pivot 55.

Partition:

valueaction
4move left
2move left
7stay right
1move left

After partition:

[4,2,1,5,7] [4, 2, 1, 5, 7]

The pivot 55 is now fixed. Recursively sort:

[4,2,1] [4, 2, 1]

and

[7] [7]

Final result:

[1,2,4,5,7] [1, 2, 4, 5, 7]

Correctness

Partition places the pivot into a position where every element on its left is less than or equal to the pivot and every element on its right is greater than or equal to the pivot. The pivot therefore has its final sorted position.

The recursive calls sort the left and right subarrays. Since all left elements are no larger than the pivot and all right elements are no smaller than the pivot, combining the sorted left side, the pivot, and the sorted right side gives a sorted array.

Complexity

casetime
bestO(nlogn)O(n \log n)
averageO(nlogn)O(n \log n)
worstO(n2)O(n^2)

The worst case occurs when partitions are extremely unbalanced, for example when the pivot is repeatedly the smallest or largest element.

Space usage comes from recursion:

casespace
balanced recursionO(logn)O(\log n)
worst recursionO(n)O(n)

Properties

propertyvalue
stableno
in-placeyes
comparison basedyes
adaptiveno
cache behaviorgood

Notes

Quicksort is fast in practice, but raw quicksort should be protected against bad pivots. Common improvements include randomized pivots, median of three selection, three way partitioning for duplicate heavy arrays, and introsort fallback to heapsort.

Implementation

def quicksort(a):
    def sort(lo, hi):
        if lo >= hi:
            return

        p = partition(lo, hi)
        sort(lo, p - 1)
        sort(p + 1, hi)

    def partition(lo, hi):
        pivot = a[hi]
        i = lo

        for j in range(lo, hi):
            if a[j] <= pivot:
                a[i], a[j] = a[j], a[i]
                i += 1

        a[i], a[hi] = a[hi], a[i]
        return i

    sort(0, len(a) - 1)
    return a
func QuickSort(a []int) {
	var sort func(int, int)

	sort = func(lo, hi int) {
		if lo >= hi {
			return
		}

		p := partition(a, lo, hi)
		sort(lo, p-1)
		sort(p+1, hi)
	}

	sort(0, len(a)-1)
}

func partition(a []int, lo, hi int) int {
	pivot := a[hi]
	i := lo

	for j := lo; j < hi; j++ {
		if a[j] <= pivot {
			a[i], a[j] = a[j], a[i]
			i++
		}
	}

	a[i], a[hi] = a[hi], a[i]
	return i
}