Binary Search

Introduction

(Copied from a fantastic article written by Iovro on Topcoder, which dives deeper in theory.)

In its simplest form, binary search is used to quickly find a value in a sorted sequence (consider a sequence an ordinary array for now). Binary search maintains a contiguous subsequence of the starting sequence where the target value is surely located. This is called the search space. The search space is initially the entire sequence. At each step, the algorithm compares the median value in the search space to the target value. Based on the comparison and because the sequence is sorted, it can then eliminate half of the search space. By doing this repeatedly, it will eventually be left with a search space consisting of a single element, the target value.

From a broader scope, binary search is a special kind of divide-and-conquer techniques, and Binary Search Trees (BSTs) as well as B+ trees are all based on it.

Five Basic Variants

(Adapted from a GeeksforGeeks article.)

Assume the array is sorted in ascending order and hi >= lo initially.

1. Contains key (True or False)

Returns:

Corresponds to std::binary_search in C++.

bool contains(vector<int>& a, int lo, int hi, int key)
{
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2; // NOTE: avoids possible overflow caused by (lo + hi)!
        if (a[mid] == key) {
            return true;
        } else if (a[mid] < key) {
            // search HIGH part
            lo = mid + 1;
        } else {
            // search LOW part
            hi = mid - 1;
        }
    }
    return false;
}

2. Index of first occurrence of a key

Returns:

Corresponds to std::lower_bound in C++.

(Not care which occurrence? Just return when a[mid] == key!)

int first(vector<int>& a, int lo, int hi, int key)
{
    int ans = -1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == key) {
            // found, note down curr index
            ans = mid;
            // continue searching LOW part
            hi = mid - 1;
        } else if (a[mid] < key) {
            // search HIGH part
            lo = mid + 1;
        } else {
            // search LOW part
            hi = mid - 1;
        }
    }
    return ans;
}

3. Index of last occurrence of a key

Returns:

int last(vector<int>& a, int lo, int hi, int key)
{
    int ans = -1;
    while (lo <= hi) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == key) {
            // found, note down curr index
            ans = mid;
            // continue searching HIGH part
            lo = mid + 1;
        } else if (a[mid] < key) {
            // search HIGH part
            lo = mid + 1;
        } else {
            // search LOW part
            hi = mid - 1;
        }
    }
    return ans;
}

4. Index of (first) smallest element greater than key / Insertion point of key (the last among duplicates after insertion)

Returns:

For insertion point of key (the last among duplicates after insertion):

Corresponds to std::upper_bound in C++.

int min_greater(vector<int>& a, int lo, int hi, int key)
{
    int ans = -1;
    while (low <= high) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == key) {
            // search HIGH part
            lo = mid + 1;
        } else if (a[mid] < key) {
            // search HIGH part
            lo = mid + 1;
        } else {
            // found a greater element, note down curr index
            ans = mid;
            // continue search LOW part
            hi = mid - 1;
        }
    }
    return ans;
}

5. Index of (last) greatest element less than key / Insertion point of key (the first among duplicates after insertion)

Returns:

For insertion point of key (the first among duplicates after insertion):

int max_lesser(vector<int>& a, int lo, int hi, int key)
{
    int ans = -1;
    while (low <= high) {
        int mid = lo + (hi - lo) / 2;
        if (a[mid] == key) {
            // search LOW part
            hi = mid - 1;
        } else if (a[mid] < key) {
            // found a lesser element, note down curr index
            ans = mid;
            // continue search HIGH part
            lo = mid + 1;
        } else {
            // search LOW part
            hi = mid - 1;
        }
    }
    return ans;
}

More Variants

Search in Rotated Sorted Array

Search in 2D Matrix

Find Turning Point of Monotonicity in Array

Exponential Search (esp. for unbounded lists)

Exponential search allows for searching through a sorted, unbounded list for a target value. The algorithm first finds a range in which the target would reside if it were in the list by searching for the first exponent i where the value a^i (typ. a = 2) is greater than the target value. It then performs a binary search on this range. The algorithm is also appliable on bounded lists, and may even out-perform direct binary search when the target value is near the beginning of the list.

Other Wild Variants (I don’t know how to summarize :)