SEN2211 · Week 12 · Searching Algorithms

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.

linear searchsequential searchbinary searchlowhighmidprobecomparison countsorted arrayinterpolation searchworst caseaverage caseinteger overflow

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:

  1. Binary search works only on a sorted array.
  2. The loop condition is low <= high. A one-element range is checked too.
  3. When moving the bound, mid + 1 and mid - 1.
  4. In the worst case ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1 comparisons.
static int linearSearch(int[] a, int x) {
    for (int i = 0; i < a.length; i++)
        if (a[i] == x) return i;
    return -1;
}
CaseComparisonsWhen
Best1the target is the first element
Worstnthe target is the last element or absent
Average (successful)n+12\frac{n+1}{2}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).

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 result on 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 log⁡2n\log_2 n halvings, plus 1 to check the last element. The worst case is ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1:

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
nLinear (worst)Binary (worst)
10104
1000100010
1 000 0001 000 00020
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 T(n)=T(n/2)+1T(n) = T(n/2) + 1, 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.

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.

pos=low+(x−a[low])⋅(high−low)a[high]−a[low]pos = low + \frac{(x - a[low]) \cdot (high - low)}{a[high] - a[low]}

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.

AlgorithmPreconditionAverageWorst
LinearnoneO(n)O(n)
BinarysortedO(log n)O(log n)
Interpolationsorted + numeric, uniform distributionO(log log n)O(n)

Common mistakes

WrongCorrect
Using binary search on an unsorted arraySort first or search linearly
while (low < high)while (low <= high)
low = mid / high = midmid + 1 / mid - 1
mid = (low + high) / 2 is always safeIt overflows with large values; low + (high - low) / 2
Binary search worst case is n/2 comparisons⌊log⁡2n⌋+1\lfloor\log_2 n\rfloor + 1
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 positionIt returns any one
Interpolation is always faster than binaryOn skewed data it drops to O(n)

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

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
7 and 10

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.

Question 2easy

What are the best, worst and average number of comparisons for a successful linear search on n elements (each position equally likely)?

Show answer
best 1, worst n, average (n+1)/2

Target at the start: 1. At the end: n. The average is 1+2+⋯+nn=n+12\frac{1 + 2 + \dots + n}{n} = \frac{n+1}{2}. The program found 500.5 for n = 1000. Big-O: O(n).

Question 3medium

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
mid 4 (21), mid 7 (34); found at index 7 after 2 probes

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.

Question 4medium

Same array, search for 5 (not present). Give each low, high, mid triple and the final values of low and high.

Show answer
(0,9,4), (0,3,1), (0,0,0); stops with low = 1, high = 0; returns -1

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.

Question 5medium

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
10 and 20

The worst case is ⌊log⁡2n⌋+1\lfloor \log_2 n \rfloor + 1. log⁡21000≈9.97\log_2 1000 \approx 9.97 → 10. log⁡2106≈19.93\log_2 10^6 \approx 19.93 → 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.

Question 6medium

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
low=0, high=n-1; while (low <= high) { mid; compare; move low or high }; return -1

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.

Question 7hard

Write a recursive static int binarySearch(int[] a, int x, int low, int high).

Show answer
Base case low > high → -1; otherwise compare a[mid] and recurse on one half.

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.

Question 8hard

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
1 and 5 (only 3 is found)

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.

Question 9medium

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
low + high overflows int; it gives -397483648

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.

Question 10medium

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 = 6, 1 probe

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).

Question 11hard

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
-1 (7 is at index 2 but is never examined)

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.

Question 12hard

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
2; on a match record ans = mid and continue with high = mid - 1

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.

Review card

LINEAR: also works on an unsorted array; best 1, worst n, avg (n+1)/2 → O(n) BINARY: ONLY on a SORTED array; the range halves at each step → O(log n) low = 0, high = n-1; while (low <= high) (<= REQUIRED) mid = low + (high - low) / 2 ((low+high)/2 can overflow int) a[mid] < x → low = mid + 1 a[mid] > x → high = mid - 1 (±1 REQUIRED, otherwise an infinite loop) If not found the loop ends with low = high + 1; low = place to insert Worst probes: floor(log2 n) + 1 n=10→4 100→7 1000→10 10⁶→20 Recursive: base low > high → -1; T(n)=T(n/2)+1, stack O(log n) With duplicates it finds any one; for the first position continue with high = mid - 1 on a match INTERPOLATION: pos = low + (x - a[low])*(high - low)/(a[high] - a[low]) uniform distribution avg O(log log n), skewed distribution worst O(n) Java: Arrays.binarySearch(a, x) if absent -(insertion point) - 1