Select multiple order statistics in one pass more efficiently than repeated selection.
Multi Selection finds several order statistics at once. Instead of running selection repeatedly for each rank, it reuses partition results to reduce total work.
This is useful when you need multiple quantiles, percentiles, or split points.
Problem
Given an array of length and a sorted list of ranks:
find all corresponding elements.
Algorithm
Use a recursive partitioning strategy similar to Quickselect. At each step, choose a pivot and partition the array. Then distribute the target ranks across the left and right partitions.
multi_select(A, ranks):
if ranks is empty:
return
choose pivot
partition A into L, E, R
let l = size(L)
let e = size(E)
split ranks into:
ranks_L = [k for k < l]
ranks_E = [k for l <= k < l + e]
ranks_R = [k for k >= l + e]
for k in ranks_E:
answer[k] = pivot
multi_select(L, ranks_L)
multi_select(R, [k - l - e for k in ranks_R])Key Idea
Each partition step resolves all ranks that fall within the pivot group. The remaining ranks are passed to smaller subproblems. This avoids reprocessing the entire array for each rank.
Example
Let:
and ranks:
Sorted order:
Results:
- rank 1 →
- rank 3 →
Multi Selection finds both values in a shared partition process.
Correctness
Partition preserves relative ordering with respect to the pivot. Each recursive call adjusts ranks so they refer to the correct subarray. Every rank eventually falls into a pivot group or a base case.
Thus each requested rank is resolved correctly.
Complexity
| case | time |
|---|---|
| average | |
| best structured splits | |
| worst |
With good pivot selection or randomization, the expected time is close to linear for small .
Space:
When to Use
Use Multi Selection when:
- multiple order statistics are needed
- repeated Quickselect would be inefficient
- quantiles or percentiles are required
For a single rank, standard Quickselect is simpler.
Implementation
import random
def multi_select(a, ranks):
result = {}
def select(arr, ks, offset):
if not ks:
return
if len(arr) == 1:
for k in ks:
result[offset + k] = arr[0]
return
pivot = random.choice(arr)
L = [x for x in arr if x < pivot]
E = [x for x in arr if x == pivot]
R = [x for x in arr if x > pivot]
l, e = len(L), len(E)
ks_L = [k for k in ks if k < l]
ks_E = [k for k in ks if l <= k < l + e]
ks_R = [k for k in ks if k >= l + e]
for k in ks_E:
result[offset + k] = pivot
select(L, ks_L, offset)
select(R, [k - l - e for k in ks_R], offset + l + e)
select(a, ranks, 0)
return resultfunc MultiSelect(a []int, ranks []int) map[int]int {
result := make(map[int]int)
var selectFn func([]int, []int, int)
selectFn = func(arr []int, ks []int, offset int) {
if len(ks) == 0 {
return
}
if len(arr) == 1 {
for _, k := range ks {
result[offset+k] = arr[0]
}
return
}
pivot := arr[len(arr)/2]
var L, E, R []int
for _, x := range arr {
if x < pivot {
L = append(L, x)
} else if x > pivot {
R = append(R, x)
} else {
E = append(E, x)
}
}
l, e := len(L), len(E)
var ksL, ksE, ksR []int
for _, k := range ks {
if k < l {
ksL = append(ksL, k)
} else if k < l+e {
ksE = append(ksE, k)
} else {
ksR = append(ksR, k)
}
}
for _, k := range ksE {
result[offset+k] = pivot
}
selectFn(L, ksL, offset)
shifted := make([]int, len(ksR))
for i, k := range ksR {
shifted[i] = k - l - e
}
selectFn(R, shifted, offset+l+e)
}
selectFn(a, ranks, 0)
return result
}