Lower bound and upper bound in C and C++ are binary-search operations used to locate insertion positions in sorted data. lower_bound finds the first element that is not less than a target, while upper_bound finds the first element that is greater than the target. Together, they make it efficient to search for values, count duplicates, and maintain ordered collections But it adds up..
Introduction
Finding a value is only part of many programming tasks. You may also need to know where a value should be inserted, whether duplicates exist, or where a group of equal values begins and ends. Lower bound and upper bound operations answer these questions without scanning the entire collection.
Both operations rely on the same underlying principle: a binary search that repeatedly halves the search interval until the interval collapses to a single position that satisfies the predicate. Because the predicate is monotonic (once an element fails the test, all later elements will also fail), the algorithm guarantees O(log n) time for any random‑access range Easy to understand, harder to ignore..
How the algorithms work internally
- Initialization –
lowpoints to the first element,highpoints to one past the last element (end). - Iteration – While
low < high:- Compute
mid = low + (high‑low)/2. - For
lower_boundtest*mid < value.- If true, the desired position must be right of
mid, so setlow = mid+1. - Otherwise (
*mid >= value) the answer is atmidor left of it, so sethigh = mid.
- If true, the desired position must be right of
- For
upper_boundtest*mid <= value.- If true, move
low = mid+1(we need the first element greater than value). - Otherwise set
high = mid.
- If true, move
- Compute
- Termination – When
low == high, the loop exits and that iterator is the result.
Because each step discards roughly half of the remaining candidates, the number of comparisons is bounded by ⌈log₂ N⌉ Small thing, real impact..
Using lower_bound and upper_bound in C++
#include
#include
#include
int main() {
std::vector data = {1, 2, 4, 4, 4, 7, 9};
int target = 4;
auto lb = std::lower_bound(data.end(), target);
auto ub = std::upper_bound(data.begin(), data.begin(), data.
std::cout << "lower_bound points to index "
<< std::distance(data.Consider this: = data. begin(), lb) << " (value "
<< (lb !end() ?
std::cout << "upper_bound points to index "
<< std::distance(data.begin(), ub) << " (value "
<< (ub !In real terms, = data. end() ?
// Number of occurrences of target:
std::cout << "Count of " << target << ": "
<< std::distance(lb, ub) << '\n';
// Insertion point that keeps the vector sorted:
data.insert(lb, 5); // insert before the first element >=5
// data now: 1 2 4 4 4 5 7 9
}
Explanation of the output
lower_boundreturns an iterator to the first4(index 2).upper_boundreturns an iterator to the first element greater than4, i.e., the7at index 5.- The distance between them (
ub - lb) equals the number of duplicates (3). - Inserting at
lower_boundplaces the new element just before the first element that is not less than the target, preserving order.
Working with raw pointers (C‑style arrays)
The same functions accept pointers because they model random‑access iterators:
int arr[] = {3, 5, 5, 8, 10};
int *begin = arr;
int *end = arr + sizeof(arr)/sizeof(arr[0]);
int *lb = std::lower_bound(begin, end, 5);
int *ub = std::upper_bound(begin, end, 5);
The result can be used exactly as with iterators.
Custom comparison predicates
When the container holds structs or you need a non‑default ordering, supply a binary predicate:
struct Record {
int id;
double score;
bool operator<(const Record& other) const { return id < other.id; }
};
std::vector recs = {{1, 9.Practically speaking, 1}, {3, 7. 4}, {3, 8.2}, {5, 6.
auto lb = std::lower_bound(recs.Consider this: id < b. id; });
auto ub = std::upper_bound(recs.Plus, end(), Record{3, 0},
{ return a. begin(), recs.begin(), recs.