Binary Search

Idea

Binary Search halves the search range each step—requires a sorted list.

Binary search

Click "Step forward" to watch the algorithm state change.

When to use

  • Sorted linear list (array or list)
  • Large data, frequent queries

Algorithm

  1. Range [low,high][low, high]
  2. mid=⌊(low+high)/2⌋mid = \lfloor (low + high) / 2 \rfloor
  3. If A[mid]=xA[mid] = x, success
  4. If A[mid]<xA[mid] < x, search right half
  5. If A[mid]>xA[mid] > x, search left half
  6. Stop when low>highlow > high

Pseudo-code:

low = 0, high = n-1
while low <= high:
    mid = (low + high) // 2
    if A[mid] == x:
        return mid
    elif A[mid] < x:
        low = mid + 1
    else:
        high = mid - 1
return -1

Implementation

// Binary search, recursive form. Requires A[0..n-1] to be sorted ascending.
int BinarySearch(int A[], int low, int high, int key) {
    if (low > high) return -1;            // not found
    int mid = (low + high) / 2;
    if (A[mid] == key) return mid;        // found
    else if (A[mid] < key)
        return BinarySearch(A, mid + 1, high);  // keep the right half
    else
        return BinarySearch(A, low, mid - 1);   // keep the left half
}

The recursive form is the clearest way to state the algorithm, but it is not the fastest: every level of recursion costs a stack frame. In production the iterative form shown in the pseudo-code above is preferred, because it keeps the two bounds in registers and touches memory only when it actually reads an element.

The decision tree and the average search length

The whole search process can be drawn as a decision tree: every node is one comparison against the midpoint mid of the current range.

  • The decision tree is a balanced binary tree (or close to it), with one node per element, so it has nn nodes.
  • The longest successful search takes as many comparisons as the height of the tree, ⌈log⁡2(n+1)⌉\lceil \log_2(n+1) \rceil.
  • The average search length is approximately ASL≈log⁡2(n+1)−1ASL \approx \log_2(n+1) - 1.

Take n=11n = 11 as an example. The tree has height ⌈log⁡212⌉=4\lceil \log_2 12 \rceil = 4, and the level sizes are 1,2,4,41, 2, 4, 4, so

ASL=1×1+2×2+3×4+4×411=1+4+12+1611=3311=3ASL = \frac{1\times1 + 2\times2 + 3\times4 + 4\times4}{11} = \frac{1+4+12+16}{11} = \frac{33}{11} = 3

That is, a successful search takes 3 comparisons on average.

The point of the tree is that it explains why the average is close to the worst case. Because the tree is balanced, almost every element sits on one of the lower levels, so the average number of comparisons is close to the height — the algorithm is not merely fast in the worst case, it is fast on average too. It also makes the cost model independent of the data distribution, unlike linear search, whose cost depends on where the target happens to sit.

In practice you often need a boundary rather than an element — the first position of a value, or the first position at which a value would be inserted. These “binary search variants” are a frequent interview topic:

VariantWhat it finds
First position equal to keythe left boundary
Last position equal to keythe right boundary
First position greater than or equal to keythe lower bound (lower_bound)
First position greater than keythe upper bound (upper_bound)

The implementation trick is the same in all four: on a hit, do not return — keep shrinking the range in one direction instead. The left-boundary search keeps moving high down; the right-boundary search keeps moving low up. The loop then ends with the boundary sitting in the variable you tracked, whether or not an exact match ever occurred.

// First index with A[i] >= key, or n if there is none.
int LowerBound(int A[], int n, int key) {
    int low = 0, high = n;
    while (low < high) {
        int mid = low + (high - low) / 2;
        if (A[mid] < key) low = mid + 1;
        else high = mid;
    }
    return low;
}

Note the half-open range [low, high) and the low < high loop condition: this shape returns n when no element qualifies, so the caller never needs a special case for “not found”.

Complexity

  • Best: O(1)O(1)
  • Worst/avg: O(log⁡2n)O(\log_2 n)

Exercises

Exercise 1

For sorted A=[1,3,5,7,9,11]A = [1, 3, 5, 7, 9, 11], find 77.

Binary SearchMidpointSearch Range

Approach: Halve the search range at every step.

Steps:

  1. low=0,high=5,mid=2,A[2]=5<7low=0, high=5, mid=2, A[2]=5\lt7
  2. low=3,high=5,mid=4,A[4]=9>7low=3, high=5, mid=4, A[4]=9>7
  3. low=3,high=3,mid=3,A[3]=7low=3, high=3, mid=3, A[3]=7

Answer: The target is at index 33.

Exercise 2

What are the applicable conditions of binary search, and what is its time complexity?

Binary SearchSorted ListTime Complexity

Approach: The precondition of binary search and its efficiency.

Steps:

  1. Applies to a sorted list
  2. Time complexity O(log⁡2n)O(\log_2 n)

Answer: A sorted list, with time complexity O(log⁡2n)O(\log_2 n).

Past exam

2017·408

What is the search process of binary search, and how efficient is it?

Binary SearchSearch ProcessTime Complexity

Approach: Describe the search flow and the number of comparisons.

Steps:

  1. Compare the middle element each time, so the range is halved
  2. At most log⁡2n\log_2 n comparisons

Answer: The range is halved at every step, so the efficiency is O(log⁡2n)O(\log_2 n).

Found an issue with this content?

Tell us what could be improved.