Binary Search on Sorted Arrays
A binary sort tree is a binary tree in which every node is at least as large as everything in its left subtree and no larger than everything in its right subtree.
Try it: Binary Search
How binary search finds a value in a sorted list by checking the middle and throwing away the half that cannot contain it.
How it works
- Start with the whole sorted list as the search interval (lo to hi).
- Look at the middle value.
- If it is the target, stop. If the target is larger, discard the left half; if smaller, discard the right half.
- Repeat until the target is found or the interval is empty (not found).
Default run (7 steps): Search for 17 in [2, 5, 9, 13, 17, 21, 30]. The whole array is the search interval: lo = 0, hi = 6. … 17 = 17 — found at index 4 after 3 comparisons.
Loading the simulation…
Why Sorted Storage Needs a Smarter Lookup
Suppose you must keep a list of strings in alphabetical order, and you repeatedly look items up and add new ones. A linked list is a natural first choice, but it has a cost. To add an item in the right place, you must walk through the list until you find that place, and on average that means examining about half of the items. Looking up an item costs roughly the same amount of work.
A sorted array improves the lookup side. Because the items sit in a known order, a binary search can locate an item quickly. The trouble then moves to insertion: making room for a new item in the middle of an array means shifting, on average, half of the existing items over by one position. This article is about the first half of that story, the fast lookup that sorted order makes possible, and then about a tree-shaped arrangement that also makes insertion efficient.
The routine you will meet below takes a sorted array and a target value. It returns the position in the array where the target sits, or the value -1 when the target is absent. Keep one question in mind as you read: how does the routine decide where to look next, and how does it know when to stop?
Narrowing Possible Positions Step by Step
A search routine has to answer two different questions: where the target is, and whether it is there at all. The second question is easy to overlook, because a routine that only handles successful searches would look correct on most inputs. A correct routine must give a definite answer in both cases.
The key fact is that inspecting a single item can rule out a large block of positions, but only because the array is sorted. If the items increase from left to right, then comparing the target with one item tells you which side of that item the target must lie on. Everything on the far side can be discarded in one move.
Rather than repeatedly modifying the array, the routine keeps track of the range of locations that could still contain the target. Two boundary variables mark that range, a lower boundary and an upper boundary. At each step, the routine looks at the item in the middle of the range. The result of that single comparison leads to one of three outcomes: the middle item is the target, the target lies in the upper half of the range, or the target lies in the lower half of the range.
The loop runs while the lower boundary has not passed the upper boundary. Each pass inspects the middle, compares, and discards one half. The loop ends in one of two ways: either the target is found at the middle, or the range becomes empty. The source's comment on that final condition puts it plainly: when the upper boundary falls below the lower boundary, the target is known not to be in the array.
Each comparison in binary search leaves about half of the remaining possible positions. The search never needs to examine every element, because the sorted order lets one comparison rule out a whole block of positions at once.
Tracing a Successful Search
Finding 23 in a sorted array of ten items
Search the sorted array [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] for the target 23. The positions are numbered 0 to 9.
- 1Set the boundaries
Start with low = 0 and high = 9, so every position from 0 to 9 is still possible.
- 2First middle item
Compute mid = (0 + 9) // 2 = 4. The item at index 4 is 16. Since 23 is greater than 16, and the array increases from left to right, 23 can only be at index 5 or later. Set low = 5. Indices 0 to 4 are discarded.
- 3Second middle item
Now the range is 5 to 9, so compute mid = (5 + 9) // 2 = 7. The item at index 7 is 56. Since 23 is smaller than 56, it can only be at index 6 or earlier within the range. Set high = 6. Indices 7 to 9 are discarded.
- 4Third middle item
The range is now 5 to 6, so compute mid = (5 + 6) // 2 = 5. The item at index 5 is 23. This matches the target, so the search stops.
23 is found at index 5 after three comparisons. A ten-item array needed only three inspections.
Code That Returns -1 When Nothing Matches
The following Python function is an illustrative version of the routine described in the source. It keeps a lower and an upper boundary, looks at the middle of the range, and returns -1 when the range becomes empty.
5- low and high mark the range of positions that could still hold the target
- mid is the middle position of the current range
- Moving high below mid discards the upper half; moving low above mid discards the lower half
- The final return -1 is the answer when the range has become empty
Predicting a Failed Search
What do you think happens?
Using the same array [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], what does the routine return when the target is 40?
Reveal answer
-1
The range shrinks from 0 to 9, then to 5 to 9, then to 5 to 6, then to 6 to 6 after comparing 23 and then 38. After comparing 38, 40 is larger, so low moves to 7. Now low is 7 and high is 6, so the range is empty and the routine returns -1. The target is absent.
Binary Sort Trees as an Ordered Structure
When an ordered collection is arranged as a binary tree with a particular ordering rule, both searching and insertion can be efficient. A binary tree built this way is called a binary sort tree, often abbreviated BST. The ideas below explain the arrangement, the guarantees it provides, and the conditions under which those guarantees turn into speed.
Counting Steps: Binary Versus Linear
The source makes the efficiency argument concrete. Each step leaves about half of the remaining locations, so the number of locations left falls quickly. From a range of 1000 locations, the search narrows to about 500, then about 250, then about 125, and so on, until only one location remains after about 10 steps. The number of steps is approximately the base-2 logarithm of the number of items, so a million items need only about 20 steps.
Because the step count grows with the logarithm of the array size, a tenfold or even a thousandfold increase in items adds only a few more steps. Doubling the array adds roughly one more step, because the base-2 logarithm increases by one each time the size doubles. Linear search, which may inspect every element, grows in step with the size of the array itself.
| Array size | Binary search steps (approximate) | Linear search worst case |
|---|---|---|
| 1,000 | About 10 | Up to 1,000 inspections |
| 2,000 | About 11 | Up to 2,000 inspections |
| 1,000,000 | About 20 | Up to 1,000,000 inspections |
Binary search figures follow the approximate base-2 logarithm of the size. Linear search may need to look at every element.
Mistakes Beginners Make
Common mistake Running binary search on an array that is not sorted
What it looks likeSearching [56, 2, 91, 8, 23] for 23 by comparing with the middle item and discarding a half.
Why it failsThe discard decision is valid only when the array increases from left to right. Comparing with one item says nothing reliable about the far side.
Do this insteadSort the array first, or use a method that does not depend on order.
Common mistake Forgetting the absent case
What it looks likeA loop that returns only when items[mid] equals the target and then ends without a final return.
Why it failsA failed search needs a definite answer. The routine returns -1 when the range becomes empty.
Do this insteadAfter the loop, when low has passed high, return -1.
Common mistake Checking only the immediate children of each node
What it looks likeA tree with root 50, left child 30, and 60 as the right child of 30.
Why it failsNode 30 is smaller than 50 and 60 is larger than 30, but 60 sits in the left subtree of 50 and is larger than 50, which breaks the rule.
Do this insteadCompare each node with everything in its left and right subtrees.
Common mistake Assuming any insertion order keeps the tree fast
Practice: Trace the Search
Using the sorted array [2, 5, 8, 12, 16, 23, 38, 56, 72, 91], trace binary search for the target 40. List each mid index and the item found there, state which side of the middle item 40 lies on, and state the final result.
Hints
- Start with low = 0 and high = 9.
- After each comparison, move either low or high so that the discarded half is no longer in the range.
- The search can end with a range that has become empty, which means 40 is absent.
Key Takeaways
- Binary search finds an item in a sorted array by inspecting the middle of a shrinking range. Each comparison discards about half of the remaining possible positions.
- Sorted order is what makes discarding valid: if the array increases from left to right, one comparison tells you which side of the middle the target must lie on.
- A binary sort tree applies the same elimination idea to a tree. Every node is at least as large as its left subtree and no larger than its right subtree, and an inorder traversal therefore outputs the items in increasing order.
- Search and insertion in a binary sort tree are efficient only when the tree stays close to balanced, and insertion order decides whether that happens.
- Binary search needs about log base 2 of n steps, so a million items need only about 20, while linear search may need to inspect every element.