Searching — Linear Search and Binary Search
Linear search and its comparison counts, binary search (iterative and recursive) with its invariant and exact worst-case comparison count, variants for first/last occurrence and insertion points, Python's bisect module, binary search on the answer, common off-by-one bugs, and when sorting first pays off.
Linear search
Scan elements one by one until the key is found.
def linear_search(a, x):
for i, v in enumerate(a):
if v == x:
return i
return -1
| Case | Comparisons |
|---|---|
| Best (first element) | 1 |
| Worst / unsuccessful | |
| Average, successful, uniform position |
, works on unsorted data and on any sequential structure (linked lists, streams). For a key present with probability at a uniform position: expected comparisons .
Sentinel trick: append the key at the end so the loop needs no bounds check (halves comparisons in low-level code).
Binary search
On a sorted array, compare the key with the middle element and discard the half that cannot contain it.
def binary_search(a, x):
lo, hi = 0, len(a) - 1
while lo <= hi: # invariant: if x is in a, it is in a[lo..hi]
mid = (lo + hi) // 2
if a[mid] == x:
return mid
elif a[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return -1 # lo is now the insertion point
def binary_search_rec(a, x, lo, hi):
if lo > hi:
return -1
mid = (lo + hi) // 2
if a[mid] == x:
return mid
if a[mid] < x:
return binary_search_rec(a, x, mid + 1, hi)
return binary_search_rec(a, x, lo, mid - 1)
Cost
. Worst-case number of three-way comparisons: (10 for , 20 for ). The comparisons trace a path in a balanced binary decision tree of height .
Variants
| Goal | Idea |
|---|---|
| First occurrence (lower bound) | on equality, record and continue left (hi = mid - 1) |
| Last occurrence (upper bound) | on equality, continue right |
Count of x | upper_bound − lower_bound |
| Insertion point | the final lo |
| Rotated sorted array | one half is always sorted; check which and whether x lies in it |
| Peak element | move towards the larger neighbour |
Python's bisect module: bisect_left(a, x) (first index with a[i] >= x), bisect_right(a, x) (first index with a[i] > x), insort(a, x) (insert in order; because of shifting).
Binary search on the answer
If a predicate is monotone (false … false, true … true), binary search finds the boundary in evaluations: integer square roots, minimum capacity to ship packages in days, smallest threshold achieving a target, root-finding by bisection on a continuous function.
Sort first, or search repeatedly?
One search: linear search, . Many searches ( of them): sort once in , then each, total — or build a hash set for expected lookups if you only need membership (see hash tables).
| Method | Preprocessing | Per query | Needs |
|---|---|---|---|
| Linear search | — | nothing | |
| Binary search | sort | order, random access | |
| Hash set | expected | hashable keys; no order | |
| Balanced BST | order; supports updates |
Quick revision
Practice questions
GATE-style questions: 2 one-mark and 7 two-mark. Wrong MCQs cost ⅓ of their marks in the real exam; MSQ and NAT have no negative marking. Attempt each before opening the solution.
The maximum number of element comparisons (three-way: less / equal / greater) made by binary search on a sorted array of 1000 elements is ______.
Linear search is applied to an array of 9 distinct elements, and the key is present at a uniformly random position. The expected number of comparisons is ______.
What does this program print?
def bs(a, x):
lo, hi = 0, len(a) - 1
mids = []
while lo <= hi:
mid = (lo + hi) // 2
mids.append(a[mid])
if a[mid] == x:
return mids
if a[mid] < x:
lo = mid + 1
else:
hi = mid - 1
return mids
print(bs([2, 5, 8, 12, 16, 23, 38, 56, 72, 91], 23))
For binary search to run in time, which conditions are required?
One or more options are correct.
What does this program print?
from bisect import bisect_left, bisect_right
a = [1, 2, 2, 2, 3, 5]
print(bisect_right(a, 2) - bisect_left(a, 2))
The worst-case time complexity of binary search on a sorted array of elements is
Binary search (three-way comparisons) is run on a sorted array of 64 elements. The maximum number of comparisons in an unsuccessful search is ______.
In the loop while lo <= hi: with mid = (lo + hi) // 2, which change can make binary search loop forever?
What does this program print?
def isqrt(n):
lo, hi = 0, n
while lo < hi:
mid = (lo + hi + 1) // 2
if mid * mid <= n:
lo = mid
else:
hi = mid - 1
return lo
print(isqrt(50))