Skip to content

Quicksort Median of Three

Quicksort variant that chooses the pivot as the median of the first, middle, and last elements.

Quicksort median of three improves pivot selection by choosing the median value among the first, middle, and last elements of the current range. This avoids some common bad cases, such as already sorted or reverse sorted arrays, where choosing the first or last element as pivot can produce highly unbalanced partitions.

The algorithm keeps the same recursive structure as quicksort. Only the pivot selection step changes.

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

For a range A[lo..hi]A[lo..hi], inspect three candidates:

A[lo],A[mid],A[hi] A[lo],\quad A[mid],\quad A[hi]

where

mid=(lo+hi)/2 mid = \lfloor (lo + hi) / 2 \rfloor

Choose the median of these three values as the pivot.

This is a small sampling strategy. It often gives a better pivot than blindly choosing one endpoint.

Algorithm

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

    pivot_index = median_of_three(A, lo, hi)
    swap A[pivot_index] and A[hi]

    p = partition(A, lo, hi)

    median_of_three_quicksort(A, lo, p - 1)
    median_of_three_quicksort(A, p + 1, hi)
median_of_three(A, lo, hi):
    mid = (lo + hi) // 2

    if A[lo] > A[mid]:
        swap A[lo], A[mid]
    if A[mid] > A[hi]:
        swap A[mid], A[hi]
    if A[lo] > A[mid]:
        swap A[lo], A[mid]

    return mid

Example

Let:

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

For the whole range:

positionvalue
first9
middle7
last1

Sorted candidates:

1, 7, 9 1,\ 7,\ 9

Median pivot:

7 7

Partition around 77:

[2,4,1,7,9] [2, 4, 1, 7, 9]

Then recursively sort the two sides.

Correctness

Median of three changes only the pivot selection rule. Partitioning still places the chosen pivot in its final sorted position, with no larger elements on the left and no smaller elements on the right. The recursive calls sort both sides, so the whole range becomes sorted.

Complexity

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

Median of three improves the likelihood of balanced partitions, but it does not remove the quadratic worst case.

Space:

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

Properties

propertyvalue
stableno
in-placeyes
randomizedno
pivot qualitybetter than endpoint pivot
implementation costlow

Notes

Median of three is a practical default for quicksort implementations. It is especially useful against already sorted input, reverse sorted input, and simple patterned data.

For stronger protection, combine it with introsort, three way partitioning, or randomized fallback.

Implementation

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

        pivot_index = median_of_three(lo, hi)
        a[pivot_index], a[hi] = a[hi], a[pivot_index]

        p = partition(lo, hi)

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

    def median_of_three(lo, hi):
        mid = (lo + hi) // 2

        if a[lo] > a[mid]:
            a[lo], a[mid] = a[mid], a[lo]
        if a[mid] > a[hi]:
            a[mid], a[hi] = a[hi], a[mid]
        if a[lo] > a[mid]:
            a[lo], a[mid] = a[mid], a[lo]

        return mid

    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 MedianOfThreeQuickSort(a []int) {
	var sort func(int, int)

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

		pivotIndex := medianOfThree(a, lo, hi)
		a[pivotIndex], a[hi] = a[hi], a[pivotIndex]

		p := partitionMedianThree(a, lo, hi)

		sort(lo, p-1)
		sort(p+1, hi)
	}

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

func medianOfThree(a []int, lo, hi int) int {
	mid := (lo + hi) / 2

	if a[lo] > a[mid] {
		a[lo], a[mid] = a[mid], a[lo]
	}
	if a[mid] > a[hi] {
		a[mid], a[hi] = a[hi], a[mid]
	}
	if a[lo] > a[mid] {
		a[lo], a[mid] = a[mid], a[lo]
	}

	return mid
}

func partitionMedianThree(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
}