Counting the Steps in Binary Search
I opened this chapter with the claim that binary search is faster than linear search. Let’s make that precise by counting how many comparisons (target compared to array elements) each one makes. That is a good proxy for how long the search takes.
Best case and worst case
Recall from the dynamic-array chapter the best and worst case of linear search.
- The best case is when the target is at the first position of the array, so we find it immediately with one comparison.
- The worst case is when the target is not present in the array, so we have to check every element until we reach the end.
Now let’s consider the best and worst case of binary search.
- The best case is when the target is right at the middle of the range we are looking at, so we find it with one comparison.
- The worst case is when the target is not present in the array, so we keep halving the range until it becomes empty. The number of comparisons is then the number of times we can halve the range before nothing is left.
The best case of the two searches is the same: one comparison. So the best case cannot tell us much about which search to prefer. The best case also only holds when the target happens to be in the position the search checks first, so it does not tell us much about the search in general. The worst case is the more useful here because it gives us a guarantee about the maximum number of comparisons needed. It says the search will never be slower than that count, for any array and any target.
In linear search, the worst case is comparisons, where is the size of the array. In binary search, the worst case is not comparisons, but some smaller number. Let’s work out what that number is.
How the range shrinks
Start with an array of elements. The first comparison looks at the middle and discards half, leaving a range of about . The next comparison halves that, leaving about . And so on: each step cuts the range in half again.
Let’s tabulate the size of the range after each step:
| Step | Range size | As a power of two |
|---|---|---|
The search keeps going until the range is down to a single element, because after that one more comparison empties it and we stop. After steps the range has shrunk to or .
Solving for the number of steps
The range reaches when
Multiply both sides by :
So is the power you must raise to in order to get . That number is the base-2 logarithm of , written .
If you have not worked with logarithms in a while, this is the reading you need for now: is the number of times you can halve before reaching . Halve 8 and you get 4, then 2, then 1. That is three halvings, and indeed . It is also exactly the number of comparisons our trace made when we searched for in the array \[3, 9, 14, 21, 28, 33, 42, 55\] of size .
First comparison:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│ 3 │ 9 │ 14 │ 21 │ 28 │ 33 │ 42 │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
↑
compare to
Second comparison:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│░ 3░│░ 9░│░14░│░21░│ 28 │ 33 │ 42 │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
↑
compare to
Third comparison:
┌────┬────┬────┬────┬────┬────┬────┬────┐
│░ 3░│░ 9░│░14░│░21░│░28░│░33░│ 42 │ 55 │
└────┴────┴────┴────┴────┴────┴────┴────┘
↑
compare to
For simplicity, we can drop the base and just write to mean . The base is understood to be when we are talking about binary search or other algorithms that halve a range.