Skip to content

Quickselect

Select the k-th smallest element in an unsorted array using partitioning.

Quickselect finds the k-th smallest element in an unsorted array. It uses the same partition idea as Quicksort but only recurses into one side, which reduces work.

You use it when you need a single order statistic such as median, top-k, or percentile, without fully sorting the array.

Problem

Given an array AA of length nn and an integer kk with 0k<n0 \le k < n, find a value vv such that

v=k-th smallest element of A v = \text{k-th smallest element of } A

Algorithm

Choose a pivot, partition the array into elements less than pivot and greater than pivot, then recurse only into the side that contains the k-th element.

quickselect(A, k):
    if length(A) == 1:
        return A[0]

    pivot = choose_pivot(A)

    L = [x in A where x < pivot]
    E = [x in A where x == pivot]
    R = [x in A where x > pivot]

    if k < length(L):
        return quickselect(L, k)
    else if k < length(L) + length(E):
        return pivot
    else:
        return quickselect(R, k - length(L) - length(E))

In practice, this is implemented in-place using partition indices.

Example

Let

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

Find the element with k=2k = 2 (0-based), which means the 3rd smallest.

Sorted order would be:

[2,3,4,7,9] [2, 3, 4, 7, 9]

So the answer is:

4 4

Quickselect avoids full sorting and isolates the region containing the answer.

Correctness

Partition guarantees that all elements in the left part are smaller than pivot, and all in the right part are larger. Therefore, the rank of the pivot is known after partition. The recursion preserves the invariant that the k-th element lies in the chosen subarray.

Complexity

casetime
bestO(n)O(n)
averageO(n)O(n)
worstO(n2)O(n^2)

Worst case occurs when pivot selection is consistently poor. Randomization avoids this in practice.

Space complexity:

O(1) O(1)

for in-place version.

When to Use

Use Quickselect when:

  • you need median or k-th element
  • you do not need full sorting
  • input fits in memory
  • average case performance is sufficient

For guaranteed linear time, use Median of Medians, but with higher constant cost.

Implementation

def quickselect(a, k):
    def partition(left, right, pivot_index):
        pivot = a[pivot_index]
        a[pivot_index], a[right] = a[right], a[pivot_index]
        store = left
        for i in range(left, right):
            if a[i] < pivot:
                a[store], a[i] = a[i], a[store]
                store += 1
        a[right], a[store] = a[store], a[right]
        return store

    left, right = 0, len(a) - 1

    while True:
        if left == right:
            return a[left]

        pivot_index = (left + right) // 2
        pivot_index = partition(left, right, pivot_index)

        if k == pivot_index:
            return a[k]
        elif k < pivot_index:
            right = pivot_index - 1
        else:
            left = pivot_index + 1
func Quickselect(a []int, k int) int {
	left, right := 0, len(a)-1

	for {
		if left == right {
			return a[left]
		}

		pivotIndex := (left + right) / 2
		pivotIndex = partition(a, left, right, pivotIndex)

		if k == pivotIndex {
			return a[k]
		} else if k < pivotIndex {
			right = pivotIndex - 1
		} else {
			left = pivotIndex + 1
		}
	}
}

func partition(a []int, left, right, pivotIndex int) int {
	pivot := a[pivotIndex]
	a[pivotIndex], a[right] = a[right], a[pivotIndex]

	store := left
	for i := left; i < right; i++ {
		if a[i] < pivot {
			a[store], a[i] = a[i], a[store]
			store++
		}
	}
	a[right], a[store] = a[store], a[right]
	return store
}