Lessons 1/4 · 25 min
Counting steps: scanning and binary search
A notebook of customers
Awa runs a shop in Bonamoussadi and writes her customers in a notebook: one name and one phone number per line, in the order they came. To find a number she reads line after line. With 50 customers that is bearable. With 10,000 she will read 5,000 lines on average and, at worst, all 10,000. Had she kept the notebook in alphabetical order, she would open it in the middle, see whether the name sought comes before or after, and keep only the right half — the way you look up a word in a dictionary. Same notebook, same content: how it is laid out changes everything.
The vocabulary of cost
We measure an operation's cost in elementary steps (a comparison, a read) as a function of the number n of elements. Three operations come up everywhere:
- lookup: finding a given element;
- insertion: adding an element;
- traversal: visiting every element, for instance to display them all. It costs at least n steps whatever the structure: you cannot visit n elements in fewer than n visits.
We write the order of growth with "big O" notation: O(n) means "cost grows in proportion to n", O(log n) "cost grows like the number of times you can halve n", O(1) "cost does not depend on n". The notation deliberately ignores constants ("3n" and "n" are both O(n)); we come back to this in the last lesson.
The linear scan
Go through in order, until you find it or run out of list. At worst (the element is absent or last) it takes n comparisons.
Binary search
It requires a sorted array. Compare the target with the middle element; depending on the result keep the left or right half, and repeat.
def binary_search(items, target):
low, high = 0, len(items) - 1
while low <= high:
mid = (low + high) // 2
if items[mid] == target:
return mid
if items[mid] < target:
low = mid + 1
else:
high = mid - 1
return NoneNote: Python has no three-way comparison, and this code makes up to two per step (== then <). In this course we count one three-way comparison per step (like a sorting comparator in C, which returns a negative, zero or positive number): that is the convention of the table below.
Worked example. Sorted array [3, 8, 14, 21, 29, 35, 42], target 35. First middle: index 3, value 21; 21 < 35, keep the right (indices 4 to 6). New middle: index 5, value 35: found in 2 steps, instead of 6 for a scan.
Why so few? At each step at most half of the candidates remain: after k steps, at most n ÷ 2^k. We are done when one is left, hence after about log₂ n steps. In this course's counting convention we count one three-way comparison (equal, smaller, larger) per step, so at worst ⌊log₂ n⌋ + 1 steps:
| n | Scan (worst case) | Binary search (worst case) |
|---|---|---|
| 100 | 100 | 7 |
| 1,000 | 1,000 | 10 |
| 5,000 | 5,000 | 13 |
| 100,000 | 100,000 | 17 |
| 1,000,000 | 1,000,000 | 20 |
Multiplying n by 1,000 multiplies the scan by 1,000, but adds only about ten steps to binary search. That is what "scaling" means.
What this does not guarantee
- Binary search only works on sorted data. Sorting itself costs about n × log₂ n comparisons. For a single lookup in 50,000 unsorted items, sorting first costs far more than one scan: it only pays off if you search often.
- Keeping an array sorted has a price at every insertion (lesson 2).
- The "worst case" is not the average case: a scan finds the element around n ÷ 2 on average when it is present. And hidden constants matter for small n: with ten elements the simplest approach is often the fastest.
Recap
Count steps as a function of n: scan = n, binary search ≈ log₂ n (on sorted data). A full traversal always costs at least n. The "worst case" gives a guarantee, not a prediction.
