انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-017-L03

البحث الثنائي

  • الدرس 3 من 7
  • 11 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

Linear search checks every element. If the array is sorted, we can do far better. Binary search looks at the middle element, decides which half cannot contain the target, throws that half away, and repeats on what is left. Each look halves the range, so even a very large array needs only a handful of looks.

Think of a phone book: you open it near the middle, see whether your name comes before or after that page, and narrow down. You never read the whole book.

Why this matters: this "halve the problem" idea appears all through computing, from database indexes to the search methods in the Java library. It is also the most common searching question in junior interviews, and one where small mistakes (the bounds, the middle) are easy to make.

This lesson builds on linear search, while loops, arrays, and the habit of hand-tracing.

Learning Objectives

By the end of this lesson you will be able to:

  • State that binary search works only on sorted data, and explain why.
  • Describe the algorithm using low, high, and mid.
  • Implement binarySearch(int[] a, int target) returning the index or -1.
  • Trace a binary search with a table of low, high, and mid.
  • Estimate the number of steps for a given array size.
  • Explain the integer overflow risk in (low + high) / 2 and use the safer form.
  • Avoid the common off-by-one bounds mistakes.

Written Explanation

The idea

Take the sorted array {2, 5, 8, 12, 16, 23, 38, 56, 72, 91} and search for 72. We keep a range of positions that might still contain the target, described by two indexes: low (the first position) and high (the last position). At the start the range is the whole array.

Repeat:

  1. Find the middle position mid of the range.
  2. If a[mid] equals the target, we are done: return mid.
  3. If a[mid] is smaller than the target, the target can only be to the right, so set low = mid + 1.
  4. If a[mid] is larger, the target can only be to the left, so set high = mid - 1.
  5. If the range becomes empty (low > high), the target is not there: return -1.

Notice mid + 1 and mid - 1. We have already checked mid, so we exclude it from the new range. Writing just mid there can make the loop run forever.

Why it needs sorted data

Discarding half of the array is only safe if we know where the target would be. In a sorted array, "everything left of mid is smaller or equal, everything right is larger or equal", so a target bigger than a[mid] cannot be on the left. In an unsorted array that guarantee is gone: the half we throw away might hold the target, and the algorithm silently reports "not found" for a value that is there. The Java documentation for Arrays.binarySearch states the same requirement: the array must be sorted, and the result is undefined if it is not.

The code

java
static int binarySearch(int[] a, int target) {
    int low = 0, high = a.length - 1;
    while (low <= high) {
        int mid = low + (high - low) / 2;
        if (a[mid] == target) return mid;
        if (a[mid] < target) low = mid + 1;
        else high = mid - 1;
    }
    return -1;
}

Points to notice:

  • The loop condition is low <= high, not low < high. With < a range of exactly one element (low == high) would be skipped and a target in it would be missed.
  • The middle is computed as low + (high - low) / 2. The reason is next.
  • With integer division (high - low) / 2 rounds down, so mid is always inside the range.

The middle and integer overflow

The obvious way to write the middle is (low + high) / 2. For small arrays it works. But recall that an int has a maximum value (about 2.1 billion), and adding past it wraps around to a negative number. If low and high are both very large, low + high can overflow, the "middle" becomes negative, and the program fails with an invalid index. This is a real bug that survived in widely used library code for years; Joshua Bloch described it on the Google Research blog, saying it can appear in arrays of about a billion elements or more.

You can see it with two big numbers:

java
int low = 1_500_000_000, high = 2_000_000_000;
System.out.println((low + high) / 2);
System.out.println(low + (high - low) / 2);

Output:

text
-397483648
1750000000

The first line is nonsense because the sum wrapped around. The second form subtracts first, so it never exceeds high, and gives the correct middle. We always use the safe form. You will not meet arrays this big in this course, but the habit costs nothing.

How many steps?

Each look halves the range. Starting with 1,000 elements the range shrinks roughly 1,000, 500, 250, 125, 62, 31, 15, 7, 3, 1. That is about 10 looks at most. In general, the number of looks is about how many times you can halve n before reaching 1. Doubling the array adds only one more look. Compared with linear search:

ItemsLinear worst caseBinary worst case (about)
10104
1,0001,00010
1,000,0001,000,00020

I counted a missing target in a sorted array of 1,000 elements with a counter and got 9 looks for the value below the smallest (the "always go left" path), which fits within the "at most about 10" bound.

Examples

Example 1: finding 72 (simple)

java
int[] a = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};

A version of the method with a println inside the loop shows each step. Output for target 72:

text
low=0 high=9 mid=4 a[mid]=16
low=5 high=9 mid=7 a[mid]=56
low=8 high=9 mid=8 a[mid]=72
8

Reading it as a table:

looklowhighmida[mid]decision
10941616 < 72, so low = 5
25975656 < 72, so low = 8
389872found, return 8

Three looks for ten elements. Linear search would have needed nine.

Example 2: a target that is not there (practical)

Same array, target 10:

text
low=0 high=9 mid=4 a[mid]=16
low=0 high=3 mid=1 a[mid]=5
low=2 high=3 mid=2 a[mid]=8
low=3 high=3 mid=3 a[mid]=12
-1

After the fourth look, a[3] is 12, which is larger than 10, so high = 2. Now low is 3 and high is 2, so low > high, the loop ends, and the method returns -1. Notice the one-element range low=3 high=3: with the condition low < high the loop would have stopped one look too early. The edge cases also work: an empty array gives -1 (the first test 0 <= -1 is false), and {4} with target 4 returns 0.

Example 3: the unsorted-data trap (realistic)

A beginner forgets to sort the data:

java
int[] unsorted = {40, 10, 30, 20, 50};
System.out.println(binarySearch(unsorted, 10) + " " + binarySearch(unsorted, 30));

Output:

text
-1 2

The value 10 is in the array, at index 1, but the search says -1. The first look is mid = 2, which holds 30. Since 30 is larger than 10, the algorithm discards everything from index 2 on and keeps {40, 10}. The next look is index 0, which holds 40, also larger than 10, so the algorithm discards that side too and the range is empty. The array was not in order, so the "smaller values are on the left" assumption was false. The search for 30 works only because 30 happens to sit exactly at the first middle. This is the dangerous part: the code gives right answers some of the time, so the bug can hide. Sort first (with Arrays.sort from the arrays utility class), or use linear search.

Backend Insight

Whenever data is kept sorted so it can be found quickly, a halving search is the basic way to find it. The library's search methods use it, and database indexes follow a similar "keep it sorted so lookups are cheap" idea, though real indexes use more elaborate structures. The practical rule for a backend developer is a trade-off: sorting costs time once, searching sorted data is then cheap many times. If a list is searched thousands of times and rarely changes, keep it sorted. If it changes constantly and is searched rarely, a linear scan may be the better choice.

For real code, prefer the tested library method Arrays.binarySearch. Its documentation says it returns the index if the key is found, and otherwise a negative number computed from the insertion point, so the "not found" convention differs from our -1: check >= 0 for "found" rather than comparing with -1. Also, if the array has repeated values, it does not promise which of them is returned.

Common Mistakes

Mistake 1: Binary search on unsorted data

The most common and most serious mistake. The answer can be wrong without any error message. Always make sure the array is sorted ascending before you search, or sort it first.

Mistake 2: Integer overflow in the middle

  • Risky: int mid = (low + high) / 2;
  • Safe: int mid = low + (high - low) / 2;

Mistake 3: Off-by-one bounds

  • high = a.length instead of a.length - 1 reads past the end.
  • while (low < high) skips the last one-element range.
  • low = mid or high = mid instead of mid + 1 or mid - 1 can loop forever, because the range may stop shrinking.

When in doubt, trace a tiny array of one and two elements by hand.

Mistake 4: Searching a descending array with ascending logic

The comparison a[mid] < target assumes ascending order. On an array sorted high to low, the logic must be reversed.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. What must be true about an array before binary search is used on it?

أظهر الحل A1

A1. It must be sorted (ascending, for the code in this lesson).

A2. In the sorted array {3, 8, 15, 21, 27, 34, 40, 52}, what are low, high, and mid at the first look?

أظهر الحل A2

A2. low = 0, high = 7, mid = 3 (value 21).

A3. Roughly how many looks does binary search need for 1,000,000 sorted elements?

أظهر الحل A3

A3. About 20 looks.

Level B: Application

B1. Trace a search for 34 in {3, 8, 15, 21, 27, 34, 40, 52} with a table of low, high, mid, and a[mid].

أظهر الحل B1

B1.

looklowhighmida[mid]decision
10732121 < 34, low = 4
247534found, index 5

B2. Trace a search for 10 in the same array. What is returned, and which look ends the loop?

أظهر الحل B2

B2. Target 10:

looklowhighmida[mid]decision
10732121 > 10, high = 2
202188 < 10, low = 2
32221515 > 10, high = 1

Now low = 2 > high = 1, so the loop ends after the third look and the method returns -1.

B3. Explain in two sentences why (low + high) / 2 can fail but low + (high - low) / 2 does not.

أظهر الحل B3

B3. The sum low + high can exceed the largest int, wrap around to a negative number, and give an invalid middle index. high - low is never bigger than high, so adding it to low stays inside the valid range.

Level C: Challenge

C1. Write a recursive version static int recursive(int[] a, int target, int low, int high) using what you learned about recursion (base case first). Test it on the array from Example 1 for target 23.

أظهر الحل C1

C1.

java
static int recursive(int[] a, int target, int low, int high) {
    if (low > high) return -1;
    int mid = low + (high - low) / 2;
    if (a[mid] == target) return mid;
    if (a[mid] < target) return recursive(a, target, mid + 1, high);
    return recursive(a, target, low, mid - 1);
}

The base case is the empty range. Called as recursive(a, 23, 0, a.length - 1) on the Example 1 array it returns 5.

C2. Change the method so that, when the target is absent, it returns the position where the target would be inserted to keep the array sorted. What does it return for 10 in {3, 8, 15, 21, 27, 34, 40, 52}?

أظهر الحل C2

C2. Inside the loop use only if (a[mid] < target) low = mid + 1; else high = mid - 1; (no separate equality test), and after the loop return low;. This returns the index of the first element that is not smaller than the target, which is where it would be inserted (if the target is present, its first position). For 10 in the array it returns 2, and for 60 it returns 8 (the array length). The library method uses the same idea, with a different encoding of the negative result.

Mini Challenge

Write binarySearch and a tiny test program. Create a sorted array of 1,000 even numbers (i * 2), add a step counter to your method, and report the step count for: a value at the very start, the very end, an odd number in the middle (missing), and a value beyond the end (missing). Then call it on an array of two elements and on an empty array.

Check your work: no step count exceeds 10, and both small arrays give the right answer without an exception.

Summary & Next Lesson

Key takeaways

  • Binary search finds a value in a sorted array by repeatedly checking the middle and discarding the half that cannot contain the target.
  • It keeps low and high, loops while (low <= high), and moves with mid + 1 or mid - 1.
  • On unsorted data it can return wrong answers silently. Sort first, or use linear search.
  • Compute the middle as low + (high - low) / 2 to avoid integer overflow.
  • Each look halves the range: about 10 looks for 1,000 items and about 20 for a million.

Next lesson

The next lesson, "Bubble sort", turns to the other classic problem. It shows how to put an unsorted array in order, which is exactly what binary search needs.

Self-check

  1. What requirement does binary search have on the array?
  2. Why is the loop condition low <= high and not low < high?
  3. What happens to the range when a[mid] < target?
  4. Why is low + (high - low) / 2 safer than (low + high) / 2?
  5. Roughly how many looks for 1,000 items?
أظهر الإجابات
  1. It must be sorted.
  2. A range with one element has low == high, and that element still has to be checked.
  3. low becomes mid + 1, discarding the left half and mid itself.
  4. The sum low + high can overflow int for huge arrays; the subtraction form cannot.
  5. About 10.

References

  • Oracle, Arrays (Java SE 21 API). binarySearch requires a sorted array (results undefined otherwise), returns the index if found and -(insertion point) - 1 otherwise, and does not guarantee which duplicate is found.
  • Joshua Bloch, Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken. Describes the overflow in (low + high) / 2, the roughly one-billion-element scale at which it shows up, and the low + (high - low) / 2 fix.
  • The look counts (10 for 1,000 and about 20 for a million) come from repeated halving and from running the code; all Java outputs were produced on JDK 21.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.