Section 5 · 8 min read

Searching and sorting

Linear and binary search, the comparison sorts and linear-time sorts, their costs, stability and in-place behaviour, and the comparison lower bound.

On this page
  1. Searching
  2. Comparison sorts
  3. Linear-time sorts
  4. Selection
  5. Worked examples
  6. Common traps
  7. Quick revision

Searching

Scan elements one by one. Worst case nn comparisons, Θ(n)\Theta(n) time, works on unsorted data and linked lists. If the key is equally likely to be at any of the nn positions, a successful search makes (n+1)/2(n+1)/2 comparisons on average.

Works on a sorted array with random access. Each step compares the key with the middle element and discards half the range.

binary_search(A, n, key):          // A[0..n-1] sorted
    lo = 0; hi = n - 1
    while lo <= hi:
        mid = (lo + hi) / 2        // integer division
        if A[mid] == key: return mid
        if key < A[mid]: hi = mid - 1
        else: lo = mid + 1
    return -1

With kk probes the algorithm can handle an array of at most 2k−12^k - 1 elements, so the worst case is ⌈log⁡2(n+1)⌉=⌊log⁡2n⌋+1\lceil\log_2(n+1)\rceil = \lfloor\log_2 n\rfloor + 1 probes. Time Θ(log⁡n)\Theta(\log n), iterative space Θ(1)\Theta(1). On a linked list binary search loses its advantage because reaching the middle costs Θ(n)\Theta(n).

Common variants: first occurrence (on a match, record it and continue left), lower bound (first index with A[i]≥A[i] \ge key), and search in a rotated sorted array (one half is always sorted). Interpolation search averages Θ(log⁡log⁡n)\Theta(\log\log n) on uniformly distributed keys but is Θ(n)\Theta(n) in the worst case.

Comparison sorts

Selection sort

Repeatedly select the minimum of the unsorted suffix and swap it to the front. Always n(n−1)/2n(n-1)/2 comparisons and at most n−1n - 1 swaps, so it is the right choice when writes are expensive. Not stable in the usual array form.

Bubble sort

Repeatedly swap adjacent out-of-order pairs. After pass pp the largest pp elements are in place. With an early-exit flag, a sorted input finishes in one pass of n−1n-1 comparisons. The number of swaps equals the number of inversions.

Insertion sort

Insert A[i]A[i] into the sorted prefix A[0..i−1]A[0..i-1] by shifting larger elements right.

for i = 1 to n - 1:
    key = A[i]; j = i - 1
    while j >= 0 and A[j] > key:
        A[j + 1] = A[j]; j = j - 1
    A[j + 1] = key

Shifts equal the number of inversions II. Comparisons lie between II and I+n−1I + n - 1. Best case (sorted) Θ(n)\Theta(n), worst case (reverse sorted) Θ(n2)\Theta(n^2). It is stable, in place, online, and fast on nearly sorted data. Using binary search to find the position cuts comparisons to O(nlog⁡n)O(n\log n) but shifts remain Θ(n2)\Theta(n^2) in the worst case.

Merge sort

Split in half, sort each half, merge. T(n)=2T(n/2)+Θ(n)=Θ(nlog⁡n)T(n) = 2T(n/2) + \Theta(n) = \Theta(n\log n) in every case. Merging lists of sizes mm and kk takes between min⁡(m,k)\min(m, k) and m+k−1m + k - 1 comparisons. The worst-case total for n=2hn = 2^h is nlog⁡2n−n+1n\log_2 n - n + 1. Needs Θ(n)\Theta(n) extra space for arrays, but sorts linked lists with O(1)O(1) extra space. Stable.

Quicksort

Partition around a pivot, then recurse on both sides.

partition(A, lo, hi):              // Lomuto, pivot = A[hi]
    p = A[hi]; i = lo - 1
    for j = lo to hi - 1:
        if A[j] <= p:
            i = i + 1; swap A[i], A[j]
    swap A[i + 1], A[hi]
    return i + 1

Partition costs Θ(n)\Theta(n). Balanced splits give Θ(nlog⁡n)\Theta(n\log n); any constant-ratio split such as 1:9 still gives Θ(nlog⁡n)\Theta(n\log n). The worst case Θ(n2)\Theta(n^2) occurs when the pivot is always the minimum or maximum, which happens for first- or last-element pivots on sorted or reverse-sorted input, and on arrays of all-equal keys with Lomuto. Randomised pivot gives expected Θ(nlog⁡n)\Theta(n\log n), about 1.39 nlog⁡2n1.39\,n\log_2 n comparisons. Not stable. In place apart from the recursion stack.

Heapsort

Build a max-heap in Θ(n)\Theta(n), then extract the maximum n−1n - 1 times at O(log⁡n)O(\log n) each. Θ(nlog⁡n)\Theta(n\log n) in all cases, in place, not stable.

Comparison table

Algorithm Best Average Worst Stable In place
Selection n2n^2 n2n^2 n2n^2 No Yes
Bubble (with flag) nn n2n^2 n2n^2 Yes Yes
Insertion nn n2n^2 n2n^2 Yes Yes
Merge nlog⁡nn\log n nlog⁡nn\log n nlog⁡nn\log n Yes No (Θ(n)\Theta(n))
Quick nlog⁡nn\log n nlog⁡nn\log n n2n^2 No Yes (stack only)
Heap nlog⁡nn\log n nlog⁡nn\log n nlog⁡nn\log n No Yes
Counting n+kn + k n+kn + k n+kn + k Yes No
Radix (dd digits) d(n+b)d(n + b) d(n+b)d(n + b) d(n+b)d(n + b) Yes No

Lower bound

Any comparison sort corresponds to a binary decision tree with at least n!n! leaves, so its height is at least log⁡2(n!)=Θ(nlog⁡n)\log_2(n!) = \Theta(n\log n). Merge sort and heapsort are therefore asymptotically optimal. The minimum number of comparisons in the worst case for 5 elements is ⌈log⁡2120⌉=7\lceil\log_2 120\rceil = 7 as a lower bound (and 7 is achievable).

Linear-time sorts

These avoid the lower bound by not using comparisons.

  • Counting sort: keys in 0..k0..k. Count occurrences, take prefix sums, then place elements scanning the input right to left so the sort is stable. Θ(n+k)\Theta(n + k) time and space.
  • Radix sort: sort by the least significant digit first, using a stable sort for each digit. With dd digits in base bb it costs Θ(d(n+b))\Theta(d(n + b)). Keys up to ncn^c written in base nn have cc digits, so radix sort is Θ(n)\Theta(n) for constant cc. Using an unstable per-digit sort breaks correctness.
  • Bucket sort: for keys uniform in [0,1)[0, 1), expected Θ(n)\Theta(n); worst case is the cost of the bucket sort used inside one bucket.

Selection

The kk-th smallest element can be found by quickselect in expected Θ(n)\Theta(n), worst case Θ(n2)\Theta(n^2). The median-of-medians pivot (groups of 5) guarantees T(n)≤T(n/5)+T(7n/10)+O(n)=Θ(n)T(n) \le T(n/5) + T(7n/10) + O(n) = \Theta(n) worst case. Finding both minimum and maximum needs only ⌈3n/2⌉−2\lceil 3n/2\rceil - 2 comparisons by processing elements in pairs, against 2n−22n - 2 for two separate scans. The second largest needs n+⌈log⁡2n⌉−2n + \lceil\log_2 n\rceil - 2 comparisons using a tournament.

Worked examples

Worked example 1: Binary search probes

What is the maximum number of probes for a successful or unsuccessful binary search in a sorted array of 100 elements?

We need the smallest kk with 2k−1≥1002^k - 1 \ge 100. 26−1=632^6 - 1 = 63 is too small and 27−1=1272^7 - 1 = 127 suffices, so the answer is 7. Check with the other form: ⌊log⁡2100⌋+1=6+1=7\lfloor\log_2 100\rfloor + 1 = 6 + 1 = 7.

Worked example 2: Lomuto partition trace

Partition [3,8,2,5,1,4,7,6][3, 8, 2, 5, 1, 4, 7, 6] with pivot 6 (last element), indices from 0.

  • j=0j = 0: 3 ≤\le 6, i=0i = 0, swap with itself.
  • j=1j = 1: 8 skipped.
  • j=2j = 2: 2, i=1i = 1, swap A[1], A[2]: [3,2,8,5,1,4,7,6][3, 2, 8, 5, 1, 4, 7, 6].
  • j=3j = 3: 5, i=2i = 2: [3,2,5,8,1,4,7,6][3, 2, 5, 8, 1, 4, 7, 6].
  • j=4j = 4: 1, i=3i = 3: [3,2,5,1,8,4,7,6][3, 2, 5, 1, 8, 4, 7, 6].
  • j=5j = 5: 4, i=4i = 4: [3,2,5,1,4,8,7,6][3, 2, 5, 1, 4, 8, 7, 6].
  • j=6j = 6: 7 skipped.

Final swap of A[5] and A[7] gives [3,2,5,1,4,6,7,8][3, 2, 5, 1, 4, 6, 7, 8]. The pivot lands at index 5, exactly the number of elements not larger than it.

Worked example 3: Insertion sort cost

Sort [2,5,1,4,3][2, 5, 1, 4, 3] with insertion sort. Count shifts and comparisons.

Inversions: (2,1), (5,1), (5,4), (5,3), (4,3), so 5 shifts. Comparisons per step:

  • Inserting 5 into [2][2]: 1 comparison (stops at 2).
  • Inserting 1 into [2,5][2, 5]: 2 comparisons (passes 5 and 2, then jj falls below 0, so no stopping comparison).
  • Inserting 4 into [1,2,5][1, 2, 5]: 2 comparisons (passes 5, stops at 2).
  • Inserting 3 into [1,2,4,5][1, 2, 4, 5]: 3 comparisons (passes 5 and 4, stops at 2). Total 1+2+2+3=81 + 2 + 2 + 3 = 8 comparisons and 5 shifts.

Worked example 4: Radix sort cost

Sort nn integers in the range [0,n2−1][0, n^2 - 1]. Comparison sorts need Ω(nlog⁡n)\Omega(n\log n). In base nn each key has 2 digits, so two counting-sort passes of Θ(n+n)\Theta(n + n) each give Θ(n)\Theta(n) overall. If the range were [0,2n][0, 2^n], each key would have about n/log⁡2nn/\log_2 n base-nn digits and radix sort would cost Θ(n2/log⁡n)\Theta(n^2/\log n), worse than merge sort.

Common traps

  • Stability matters only when equal keys exist; the classic unstable sorts are selection, quick and heap.
  • Quicksort worst case also appears on all-equal keys with Lomuto partition, not only on sorted input.
  • "In place" usually allows O(log⁡n)O(\log n) stack; merge sort on arrays is still not in place.
  • Insertion sort is Θ(n+I)\Theta(n + I) where II is the inversion count; a nearly sorted array is linear.
  • Binary search needs random access. Questions about binary search on a linked list expect Θ(n)\Theta(n).
  • Heap building is Θ(n)\Theta(n), not Θ(nlog⁡n)\Theta(n\log n); the sort phase is what costs nlog⁡nn\log n.
  • Selection sort does Θ(n2)\Theta(n^2) comparisons even on sorted input.

Quick revision

  • Binary search worst case: ⌊log⁡2n⌋+1\lfloor\log_2 n\rfloor + 1 probes.
  • Comparison sort lower bound: log⁡2n!=Θ(nlog⁡n)\log_2 n! = \Theta(n\log n).
  • Stable: insertion, bubble, merge, counting, radix. Unstable: selection, quick, heap.
  • Fewest swaps: selection sort (≤n−1\le n - 1).
  • Merge two sorted lists of sizes m,km, k: at most m+k−1m + k - 1 comparisons.
  • Min and max together: ⌈3n/2⌉−2\lceil 3n/2\rceil - 2 comparisons.
  • Median of medians: linear worst case; quickselect: linear expected.
  • Radix sort needs a stable digit sort.

Further reading

Test this section