Binary Search
Binary Search halves the search range each step—requires a sorted list.
Click "Step forward" to watch the algorithm state change.
- Sorted linear list (array or list)
- Large data, frequent queries
- Range
- If , success
- If , search right half
- If , search left half
- Stop when
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
// 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 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 nodes.
- The longest successful search takes as many comparisons as the height of the tree, .
- The average search length is approximately .
Take as an example. The tree has height , and the level sizes are , so
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:
| Variant | What it finds |
|---|---|
First position equal to key | the left boundary |
Last position equal to key | the right boundary |
First position greater than or equal to key | the lower bound (lower_bound) |
First position greater than key | the 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”.
- Best:
- Worst/avg:
Exercise 1
For sorted , find .
Approach: Halve the search range at every step.
Steps:
Answer: The target is at index .
Exercise 2
What are the applicable conditions of binary search, and what is its time complexity?
Approach: The precondition of binary search and its efficiency.
Steps:
- Applies to a sorted list
- Time complexity
Answer: A sorted list, with time complexity .
2017·408
What is the search process of binary search, and how efficient is it?
Approach: Describe the search flow and the number of comparisons.
Steps:
- Compare the middle element each time, so the range is halved
- At most comparisons
Answer: The range is halved at every step, so the efficiency is .
