Linear & Binary Search
Unordered scanning vs divide-and-conquer, 3 invariant formulations, avoiding integer overflow via mid = low + (high - low) / 2.
Reinforce your mental model: run, pause, and inspect live pointers with our interactive visualizer:
Topics Covered:
49. Linear Search (Sequential Search, Sentinel Optimization & Branch Prediction) • 50. Binary Search (Logarithmic Divide-and-Conquer Search, Formal Loop Invariant Proof & Integer Overflow Prevention) • Decision Tree Lower Bound • Branchless Binary Search
Search algorithms represent computing's most fundamental query primitives, answering whether a target entity exists within a collection and identifying its precise location. The architectural approach to search hinges directly on the ordering invariants of the underlying data. Across unordered collections, exhaustive linear scanning is mathematically optimal without pre-indexing. When a collection is sorted, however, order permits the elimination of exponential fractions of the search space in each step. This chapter analyzes sequential linear search, sentinel loop optimizations, the divide-and-conquer mechanics of binary search, arithmetic integer overflow prevention, formal loop invariant proofs, and modern branchless optimizations.
Learning Objectives
#- Formulate the fundamental search problem across arbitrary versus monotonically ordered sequences.
- Implement linear search and apply the sentinel optimization technique to eliminate per-iteration boundary checks.
- Master binary search's invariant-driven search interval halving and prevent 32-bit signed integer overflow.
- Formally prove the correctness of binary search using mathematical induction over loop invariants.
- Derive the formal logarithmic recurrence using the Master Theorem and decision tree lower bounds.
- Implement branchless binary search patterns to avoid CPU branch misprediction penalties on modern superscalar architectures.
Topic 49: Linear Search (Sequential Scanning)
#1. Problem Definition & Operational Mechanics
#Given an arbitrary array containing elements, determine whether a specified target value exists in . If found, return its zero-based index ; otherwise, return .
Linear Search inspects every cell sequentially from index to :
- Preconditions: Zero. Works across completely unordered collections, linked lists, files, and input streams.
- Decision Contract: Halts immediately on the first matching element.
2. Systems Optimization: Sentinel Linear Search
#Standard linear search incurs two branch comparisons on every single iteration:
- Loop boundary condition:
i < n - Value equality check:
A[i] == target
In high-throughput loops processing millions of records, branch predictor overhead degrades CPU pipelining. Sentinel Linear Search eliminates the boundary condition from the inner loop entirely:
- Temporarily store the original last element: .
- Overwrite the last element with the target: (acting as a guaranteed loop terminator).
- Scan using only the equality condition:
while A[i] != target: i++. - Restore .
- Check if or the restored last element matches the target.
FUNCTION SentinelLinearSearch(A: Array of Element, n: Integer, target: Element) → Integer:
if n == 0:
return -1
last ← A[n - 1]
A[n - 1] ← target // Install sentinel
i ← 0
// Only ONE comparison per iteration: no i < n check!
while A[i] ≠ target:
i ← i + 1
A[n - 1] ← last // Restore original element
if i < n - 1 or A[n - 1] == target:
return i
return -1
Topic 50: Binary Search (Logarithmic Divide-and-Conquer)
#1. Conceptual Architecture & Interval Contraction Geometry
When an array is strictly sorted in non-decreasing order (), we can test the central element (). If the target does not match , order guarantees that an entire half of the remaining elements can be discarded immediately.
2. Formal Induction Proof of the Binary Search Invariant
#Loop Invariant:
At the start of every iteration of the while (low <= high) loop, if the target exists anywhere in array , it must be located within the active subarray boundary .
Initialization (Base Case):
- Prior to loop execution, and .
- The active interval is , which encompasses the entire collection.
- If the target exists, it is trivially within this range. The invariant holds.
Maintenance (Inductive Step):
- Assume the invariant holds at the beginning of an iteration: .
- Calculate .
- Case 1 (): The element is found; algorithm terminates correctly.
- Case 2 ():
- Because is sorted in non-decreasing order:
- Therefore,
targetcannot exist at index or any index to its left (). - Setting restricts the interval to without eliminating any potential match. The invariant is preserved.
- Because is sorted in non-decreasing order:
- Case 3 ():
- Symmetrically, for all .
- Setting preserves the invariant.
Termination:
- The loop terminates either when (returning the valid index), or when .
- If , the candidate interval is empty.
- By the invariant, if
targetexisted, it must be in this empty interval. Thus,targetis provably absent from . The algorithm correctly returns .
3. Systems Optimization: Branchless Binary Search
#On modern superscalar CPU pipelines, conditional branches (if (A[mid] < target)) trigger pipeline stalls upon branch misprediction. When binary searching, the comparison outcome is effectively random (50/50), causing high branch misprediction rates (~15–20 CPU cycles penalty per branch).
A Branchless Binary Search uses conditional moves (cmov) or pointer arithmetic to eliminate branches:
#include <span>
#include <cstddef>
// Branchless Binary Search: Eliminates CPU branch mispredictions
int branchless_binary_search(std::span<const int> arr, int target) {
const int* base = arr.data();
size_t n = arr.size();
while (n > 1) {
size_t half = n / 2;
// Compiler emits conditional move (cmov) instead of jump instruction
base = (base[half] < target) ? base + half : base;
n -= half;
}
return (*base == target) ? static_cast<int>(base - arr.data()) : -1;
}4. Key Takeaways
#- Unsorted Generality: Linear search requires zero preconditions, operating across arbitrary streams in time.
- Sentinel Optimization: Placing a temporary copy of the target at array end eliminates the
i < nloop boundary branch. - Logarithmic Scaling: Binary search halves the remaining search space at every comparison, achieving performance across sorted containers.
- Overflow Prevention: Always compute midpoint as to avoid signed integer wraparound.
- Branchless Search: Eliminating branch mispredictions via conditional pointer arithmetic dramatically accelerates binary search on modern superscalar processors.
Academic Attribution & References
#- Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2022). Introduction to Algorithms (4th ed.), Section 2.3 & Chapter 12. MIT Press.
- Bentley, J. (2000). Programming Pearls (2nd ed.), Column 4: Writing Correct Programs. Addison-Wesley.
- Knuth, D. E. (1998). The Art of Computer Programming, Volume 3: Sorting and Searching (2nd ed.), Section 6.2: Searching by Comparison of Keys. Addison-Wesley.
- Bloch, J. (2006). Extra, Extra - Read All About It: Nearly All Binary Searches and Mergesorts are Broken. Google Research Blog.