Search algorithms: linear, binary, interpolation
Linear search, the loop and recursive forms of binary search, comparison counts, and the idea of interpolation search. The exam asks for a low/mid/high trace table and the worst-case number of comparisons.
Finding a student number in a list, looking up a word in a dictionary, finding a product in a price list: all are search problems. There are two basic strategies. Linear search looks one by one from start to end; it works on any array but is slow. Binary search works only on a sorted array but is incredibly fast: at most 20 steps for a million elements. The exam asks for the low/mid/high trace table of binary search, the number of comparisons, and small mistakes in the code.
Golden rules:
- Binary search works only on a sorted array.
- The loop condition is
low <= high. A one-element range is checked too. - When moving the bound,
mid + 1andmid - 1. - In the worst case comparisons.
Linear search (linear / sequential search)
static int linearSearch(int[] a, int x) {
for (int i = 0; i < a.length; i++)
if (a[i] == x) return i;
return -1;
}
| Case | Comparisons | When |
|---|---|---|
| Best | 1 | the target is the first element |
| Worst | n | the target is the last element or absent |
| Average (successful) | every position equally likely |
All are O(n). Advantage: it doesn’t require sorting, and it works on a linked list too. Even if the array is sorted, linear search doesn’t use that; for a value that isn’t there it always goes to the end (in a sorted list you can add an early exit, week 3).
Binary search
When you look up a word in a dictionary you open in the middle. If the word you are looking for comes before that page, you discard the right half entirely. At each step the search space is halved.
static int binarySearch(int[] a, int x) {
int low = 0, high = a.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == x) return mid;
else if (a[mid] < x) low = mid + 1;
else high = mid - 1;
}
return -1;
}
The trace table
The array (indices on top):
index: 0 1 2 3 4 5 6 7 8 9
value: 3 8 12 17 21 25 30 34 41 47
Searching for 47 (the rightmost element, one of the worst cases):
low=0 high=9 mid=4 a[mid]=21
low=5 high=9 mid=7 a[mid]=34
low=8 high=9 mid=8 a[mid]=41
low=9 high=9 mid=9 a[mid]=47
result 9 probes=4
Searching for 5 (not in the array):
low=0 high=9 mid=4 a[mid]=21
low=0 high=3 mid=1 a[mid]=8
low=0 high=0 mid=0 a[mid]=3
stop: low=1 high=0
result -1 probes=3
The loop ends when low > high. At that moment low is the position where the searched value can be inserted without breaking the order (5 should go to index 1). When it doesn’t find it, Java’s ready-made Arrays.binarySearch returns -(low) - 1: −2 in this example.
TIP · Trace table layout. On the exam, write
low | high | mid | a[mid] | comparison resulton each line. When computing mid, don’t forget integer division: (0 + 9) / 2 = 4, not 4.5.
How many comparisons?
Each probe halves the range. Going from n elements down to 1 element needs halvings, plus 1 to check the last element. The worst case is :
n=10 worst probes=4 floor(log2 n)+1=4
n=100 worst probes=7 floor(log2 n)+1=7
n=1000 worst probes=10 floor(log2 n)+1=10
n=1000000 worst probes=20 floor(log2 n)+1=20
| n | Linear (worst) | Binary (worst) |
|---|---|---|
| 10 | 10 | 4 |
| 1000 | 1000 | 10 |
| 1 000 000 | 1 000 000 | 20 |
| 10⁹ | 10⁹ | 30 |
The cost: the array must be sorted. Sorting takes O(n log n) at best (week 14). Sorting isn’t worth it for a single search; it is worth it if many searches will be made on the same array.
The recursive form
static int binarySearch(int[] a, int x, int low, int high) {
if (low > high) return -1;
int mid = low + (high - low) / 2;
if (a[mid] == x) return mid;
if (a[mid] < x) return binarySearch(a, x, mid + 1, high);
return binarySearch(a, x, low, mid - 1);
}
The recurrence is , with solution O(log n) (week 11). The loop version uses O(1), and the recursive version uses O(log n) extra stack memory.
Three classic mistakes
1. while (low < high). When the range shrinks to a single element, the loop ends without looking at that element. On {1, 3, 5}:
find 1 -> -1 find 3 -> 1 find 5 -> -1
2. low = mid or high = mid. When low = 3, high = 4, mid = 3; if you write low = mid, low never changes: an infinite loop.
3. Overflow of (low + high) / 2.
int low = 1_500_000_000, high = 2_000_000_000;
System.out.println((low + high) / 2 + " vs " + (low + (high - low) / 2));
-397483648 vs 1750000000
This bug stayed in Java’s own library for years. It shows up with very large arrays; on the exam you are expected to write the correct formula.
PITFALL · Binary search on an unsorted array. It gives no error, it silently returns a wrong result. On
{9, 2, 7, 4, 1, 8, 3}, 2 and 8 are found by luck but 7 is not found (−1), because the algorithm discards the left half where 7 is in the first step.
Duplicate values
On {2, 4, 4, 4, 7} standard binary search returns index 2 for 4, because the first mid is there. If you want the first position, don’t stop at a match: record the result and keep searching on the left with high = mid - 1. This variation returns index 1. For the last position, symmetrically low = mid + 1.
Interpolation search
When you look for “Yılmaz” in a phone book, you open not in the middle but near the end. Interpolation search does the same: it estimates the position of the target by looking at its value.
Then, as in binary search, it compares with a[pos] and moves low or high.
low=0 high=9 pos=6 a[pos]=70
6 probes=1
8 probes(skewed)=9
8 probes(binary)=3
The first two lines: in the array {10, 20, ..., 100}, 70 was found on the first estimate. The last two lines: in a skewed array like {1, 2, ..., 9, 1000}, searching for 9 took 9 probes with interpolation and 3 probes with binary search. The value 1000 keeps shifting the estimate to the left.
| Algorithm | Precondition | Average | Worst |
|---|---|---|---|
| Linear | none | O(n) | O(n) |
| Binary | sorted | O(log n) | O(log n) |
| Interpolation | sorted + numeric, uniform distribution | O(log log n) | O(n) |
Common mistakes
| Wrong | Correct |
|---|---|
| Using binary search on an unsorted array | Sort first or search linearly |
while (low < high) | while (low <= high) |
low = mid / high = mid | mid + 1 / mid - 1 |
mid = (low + high) / 2 is always safe | It overflows with large values; low + (high - low) / 2 |
| Binary search worst case is n/2 comparisons | |
| Binary search on a linked list is O(log n) | There is no O(1) access to the middle; meaningless for a list |
| On a duplicate array it returns the first position | It returns any one |
| Interpolation is always faster than binary | On skewed data it drops to O(n) |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
Linear search is run on {3, 8, 12, 17, 21, 25, 30, 34, 41, 47}. How many elements are compared when searching for 30? For 5?
Show answer
30 is at index 6: a[0]..a[6], seven comparisons. 5 is not in the array: linear search is unaware that the array is sorted and looks at all ten elements.
What are the best, worst and average number of comparisons for a successful linear search on n elements (each position equally likely)?
Show answer
Target at the start: 1. At the end: n. The average is . The program found 500.5 for n = 1000. Big-O: O(n).
Binary search (mid = low + (high - low) / 2) looks for 34 in {3, 8, 12, 17, 21, 25, 30, 34, 41, 47}. List the values of mid and a[mid] in order.
Show answer
low = 0, high = 9 → mid = 4, a[4] = 21 < 34, low = 5. low = 5, high = 9 → mid = 7, a[7] = 34: found. Linear search would have made 8 comparisons.
Same array, search for 5 (not present). Give each low, high, mid triple and the final values of low and high.
Show answer
21 > 5 → high = 3. mid 1: 8 > 5 → high = 0. mid 0: 3 < 5 → low = 1. low > high, the loop ends. The final value of low (1) is the place where 5 should be inserted. That is why Java's Arrays.binarySearch returns −(low) − 1 = −2.
What is the maximum number of probes (comparisons with a[mid]) binary search needs on a sorted array of 1000 elements? Of 1 000 000?
Show answer
The worst case is . → 10. → 20. The program found the same numbers by trying every target. Linear search could make 1000 and 1 000 000 comparisons on the same arrays.
Write an iterative static int binarySearch(int[] a, int x) that returns the index of x in the sorted array a, or -1.
Show answer
static int binarySearch(int[] a, int x) {
int low = 0, high = a.length - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == x) return mid;
else if (a[mid] < x) low = mid + 1; // right half
else high = mid - 1; // left half
}
return -1;
}Three critical details: the condition <= (a one-element range must be checked too), mid + 1 / mid - 1 (otherwise an infinite loop), and the mid formula that doesn't overflow.Write a recursive static int binarySearch(int[] a, int x, int low, int high).
Show answer
static int binarySearch(int[] a, int x, int low, int high) {
if (low > high) return -1; // empty range
int mid = low + (high - low) / 2;
if (a[mid] == x) return mid;
if (a[mid] < x) return binarySearch(a, x, mid + 1, high);
return binarySearch(a, x, low, mid - 1);
}The first call is binarySearch(a, x, 0, a.length - 1). The recurrence T(n) = T(n/2) + 1 → O(log n) time, O(log n) stack. In the array above, 3 probes for 12 and 4 probes for 50.This version uses while (low < high). On the array {1, 3, 5}, which searches fail even though the value is present?
int low = 0, high = a.length - 1;
while (low < high) {
int mid = (low + high) / 2;
if (a[mid] == x) return mid;
else if (a[mid] < x) low = mid + 1;
else high = mid - 1;
}
return -1;Show answer
3 is found at the first mid. For 1: mid 1 (3 > 1) → high = 0; when low = high = 0, 0 < 0 is false, and the loop ends without looking at a[0]: −1. For 5, in the same way it stops at low = high = 2. A one-element range is never checked. The right condition is low <= high.
Why is mid = low + (high - low) / 2 preferred over mid = (low + high) / 2? What does the second give for low = 1 500 000 000, high = 2 000 000 000?
Show answer
The sum of two large ints exceeds the int limit (about 2.147 billion) and wraps to negative: (1.5e9 + 2e9) / 2 = −397 483 648. Accessing the array with a negative index crashes. low + (high - low) / 2 never overflows: 1 750 000 000.
Interpolation search estimates pos = low + (x - a[low]) * (high - low) / (a[high] - a[low]). For {10, 20, ..., 100} (10 elements) and x = 70, what is the first pos, and how many probes are needed?
Show answer
pos = 0 + (70 − 10) · 9 / (100 − 10) = 540 / 90 = 6. a[6] = 70: found on the first try. If the values are uniformly distributed, the average is O(log log n). But on a skewed distribution like {1, 2, ..., 9, 1000}, finding 9 took 9 probes (binary search 3): the worst case is O(n).
Binary search is (incorrectly) run on the unsorted array {9, 2, 7, 4, 1, 8, 3} looking for 7. What does it return?
Show answer
mid 3: a[3] = 4 < 7 → go right (low = 4). mid 5: 8 > 7 → high = 4. mid 4: 1 < 7 → low = 5. Ends: −1. Binary search rests on the assumption 'everything on the left is smaller'; in an unsorted array, even though some searches hit by luck (2 and 8 were found), the result is unreliable.
The sorted array is {2, 4, 4, 4, 7}. Which index does the standard binary search return for 4? How would you change it to always return the first occurrence?
Show answer
The first mid = 2 and a[2] = 4: it returns immediately. Standard binary search returns any match among duplicate values. For the first position, don't stop at a match; keep searching on the left:
int low = 0, high = a.length - 1, ans = -1;
while (low <= high) {
int mid = low + (high - low) / 2;
if (a[mid] == x) { ans = mid; high = mid - 1; }
else if (a[mid] < x) low = mid + 1;
else high = mid - 1;
}
return ans;This returns 1 on this array. Still O(log n).Verification: All search methods were run on the JDK in verify/SEN2211/W12.java (java W12.java). The trace tables come from the low/high/mid lines the program prints at each step, and the comparison (probe) counts from the counter. The worst-case counts were found by trying every possible target for n = 10, 100, 1000 (trying 4000 targets at the ends for 10⁶) and compared with ⌊log₂ n⌋ + 1.