Skip to main content

Standard Library Algorithms

STL algorithms are generic functions that work with any container through iterators. They provide tested, optimized implementations of common operations. Never write your own sort or search - use the STL!

Algorithms + Iterators = Generic Code

Algorithms work on ranges defined by iterators. One algorithm works with vector, list, array, or any container providing compatible iterators.

Non-Modifying Sequence Operations

#include <algorithm>
#include <vector>

std::vector<int> vec = {1, 2, 3, 4, 5};

auto it = std::find(vec.begin(), vec.end(), 3);
if (it != vec.end()) {
std::cout << "Found at position: " << std::distance(vec.begin(), it);
} else {
std::cout << "Not found";
}

// find_if with predicate
auto it2 = std::find_if(vec.begin(), vec.end(), [](int x) {
return x > 3; // Find first element > 3
});

// find_if_not (first not matching)
auto it3 = std::find_if_not(vec.begin(), vec.end(), [](int x) {
return x < 3; // Find first element >= 3
});

Complexity: O(n)

std::count - Count Elements

std::vector<int> vec = {1, 2, 3, 2, 5, 2};

// Count occurrences
int count = std::count(vec.begin(), vec.end(), 2); // 3

// Count with predicate
int even_count = std::count_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0; // Count even numbers
});

std::all_of, any_of, none_of

std::vector<int> vec = {2, 4, 6, 8};

// All satisfy predicate?
bool all_even = std::all_of(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
}); // true

// Any satisfy predicate?
bool any_greater_5 = std::any_of(vec.begin(), vec.end(), [](int x) {
return x > 5;
}); // true

// None satisfy predicate?
bool none_odd = std::none_of(vec.begin(), vec.end(), [](int x) {
return x % 2 != 0;
}); // true

std::for_each - Apply Function

std::vector<int> vec = {1, 2, 3, 4, 5};

// Apply function to each element
std::for_each(vec.begin(), vec.end(), [](int x) {
std::cout << x << " ";
});

// Modify elements (need reference)
std::for_each(vec.begin(), vec.end(), [](int& x) {
x *= 2; // vec becomes {2, 4, 6, 8, 10}
});

// Return function object
auto sum_func = [total = 0](int x) mutable { return total += x; };
auto final_func = std::for_each(vec.begin(), vec.end(), sum_func);
// Can access accumulated value from final_func

Modifying Sequence Operations

std::copy - Copy Elements

std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(5);

// Copy range
std::copy(src.begin(), src.end(), dst.begin());

// copy_if with predicate
std::vector<int> even;
std::copy_if(src.begin(), src.end(), std::back_inserter(even), [](int x) {
return x % 2 == 0;
}); // even = {2, 4}

// copy_n - copy n elements
std::vector<int> first_three(3);
std::copy_n(src.begin(), 3, first_three.begin()); // {1, 2, 3}

std::move - Move Elements

std::vector<std::string> src = {"one", "two", "three"};
std::vector<std::string> dst(3);

// Move elements (src elements left in valid but unspecified state)
std::move(src.begin(), src.end(), dst.begin());
// dst = {"one", "two", "three"}
// src = {?, ?, ?} (moved-from)

std::transform - Apply Function and Store Result

std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> result(5);

// Unary transform
std::transform(vec.begin(), vec.end(), result.begin(), [](int x) {
return x * x; // Square each element
}); // result = {1, 4, 9, 16, 25}

// Binary transform (combine two ranges)
std::vector<int> vec2 = {10, 20, 30, 40, 50};
std::transform(vec.begin(), vec.end(), vec2.begin(), result.begin(),
[](int a, int b) {
return a + b;
}); // result = {11, 22, 33, 44, 55}

std::fill and std::generate

std::vector<int> vec(10);

// Fill with value
std::fill(vec.begin(), vec.end(), 42); // All elements = 42

// Fill with generated values
int counter = 0;
std::generate(vec.begin(), vec.end(), [&counter]() {
return counter++; // 0, 1, 2, ..., 9
});

std::replace - Replace Elements

std::vector<int> vec = {1, 2, 3, 2, 5, 2};

// Replace all 2s with 99
std::replace(vec.begin(), vec.end(), 2, 99);
// vec = {1, 99, 3, 99, 5, 99}

// replace_if with predicate
std::replace_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0; // Replace even numbers
}, 0);

std::remove - Remove Elements

std::vector<int> vec = {1, 2, 3, 2, 5, 2};

// Remove all 2s (doesn't actually delete!)
auto new_end = std::remove(vec.begin(), vec.end(), 2);
vec.erase(new_end, vec.end()); // Actually delete
// vec = {1, 3, 5}

// Or in one line (C++20)
std::erase(vec, 2); // Combines remove + erase

// remove_if with predicate
auto new_end2 = std::remove_if(vec.begin(), vec.end(), [](int x) {
return x % 2 == 0;
});
vec.erase(new_end2, vec.end());

Important: remove doesn't actually delete elements, just moves them to end!

std::unique - Remove Consecutive Duplicates

std::vector<int> vec = {1, 1, 2, 2, 2, 3, 3, 1};

// Must be sorted for full deduplication!
std::sort(vec.begin(), vec.end()); // {1, 1, 1, 2, 2, 2, 3, 3}

auto new_end = std::unique(vec.begin(), vec.end());
vec.erase(new_end, vec.end());
// vec = {1, 2, 3}

std::reverse - Reverse Range

std::vector<int> vec = {1, 2, 3, 4, 5};

std::reverse(vec.begin(), vec.end());
// vec = {5, 4, 3, 2, 1}

// reverse_copy - copy reversed
std::vector<int> reversed(5);
std::reverse_copy(vec.begin(), vec.end(), reversed.begin());

std::rotate - Rotate Elements

std::vector<int> vec = {1, 2, 3, 4, 5};

// Rotate left (move first element to end)
std::rotate(vec.begin(), vec.begin() + 1, vec.end());
// vec = {2, 3, 4, 5, 1}

// Rotate right (move last to beginning)
std::rotate(vec.rbegin(), vec.rbegin() + 1, vec.rend());
// vec = {1, 2, 3, 4, 5}

std::sort - Sort Range

std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};

// Sort ascending
std::sort(vec.begin(), vec.end());
// vec = {1, 1, 2, 3, 4, 5, 6, 9}

// Sort descending
std::sort(vec.begin(), vec.end(), std::greater<int>());

// Custom comparator
std::vector<std::string> words = {"apple", "zoo", "cat", "dog"};
std::sort(words.begin(), words.end(), [](const auto& a, const auto& b) {
return a.length() < b.length(); // Sort by length
});

Complexity: O(n log n) average
Note: Not stable (equal elements may be reordered)

std::stable_sort - Stable Sort

struct Person {
std::string name;
int age;
};

std::vector<Person> people = {
{"Alice", 30}, {"Bob", 25}, {"Charlie", 30}
};

// Stable sort preserves relative order of equal elements
std::stable_sort(people.begin(), people.end(), [](const auto& a, const auto& b) {
return a.age < b.age;
});
// Alice and Charlie (both 30) stay in original order

std::partial_sort - Partially Sort

std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};

// Sort first 4 elements, rest can be in any order
std::partial_sort(vec.begin(), vec.begin() + 4, vec.end());
// vec = {1, 1, 2, 3, ...(rest unsorted)}

Use case: Finding top N elements efficiently

std::nth_element - Find Nth Element

std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};

// Partition so vec[4] is in correct sorted position
std::nth_element(vec.begin(), vec.begin() + 4, vec.end());
// Elements before vec[4] are <= vec[4]
// Elements after vec[4] are >= vec[4]
// But not fully sorted

// Use case: Finding median
size_t mid = vec.size() / 2;
std::nth_element(vec.begin(), vec.begin() + mid, vec.end());
int median = vec[mid];

Complexity: O(n) average - faster than sorting!

Binary Search (on sorted ranges)

std::binary_search - Check if Element Exists

std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9};

bool found = std::binary_search(vec.begin(), vec.end(), 5); // true

Complexity: O(log n)
Requirement: Range must be sorted!

std::lower_bound - First >= Value

std::vector<int> vec = {1, 2, 4, 4, 4, 6, 7};

auto it = std::lower_bound(vec.begin(), vec.end(), 4);
// Points to first 4
int index = std::distance(vec.begin(), it); // 2

std::upper_bound - First > Value

auto it = std::upper_bound(vec.begin(), vec.end(), 4);
// Points to 6 (first element > 4)

std::equal_range - Range of Equal Elements

auto [lower, upper] = std::equal_range(vec.begin(), vec.end(), 4);
// lower points to first 4
// upper points to 6
// Range [lower, upper) contains all 4s

int count = std::distance(lower, upper); // 3 (three 4s)

Set Operations (on sorted ranges)

std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {3, 4, 5, 6, 7};
std::vector<int> result;

// Union (all elements from both)
std::set_union(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// result = {1, 2, 3, 4, 5, 6, 7}

// Intersection (common elements)
result.clear();
std::set_intersection(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// result = {3, 4, 5}

// Difference (in v1 but not v2)
result.clear();
std::set_difference(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// result = {1, 2}

// Symmetric difference (in one but not both)
result.clear();
std::set_symmetric_difference(v1.begin(), v1.end(), v2.begin(), v2.end(),
std::back_inserter(result));
// result = {1, 2, 6, 7}

Heap Operations

std::vector<int> vec = {3, 1, 4, 1, 5, 9};

// Make heap (max heap by default)
std::make_heap(vec.begin(), vec.end());
// vec = {9, 5, 4, 1, 1, 3} (heap property satisfied)

// Push to heap
vec.push_back(7);
std::push_heap(vec.begin(), vec.end());

// Pop from heap
std::pop_heap(vec.begin(), vec.end());
vec.pop_back(); // Actually remove

// Sort heap
std::sort_heap(vec.begin(), vec.end());
// vec is now sorted

Min/Max Operations

std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6};

// Min/max element
auto min_it = std::min_element(vec.begin(), vec.end());
auto max_it = std::max_element(vec.begin(), vec.end());

std::cout << "Min: " << *min_it; // 1
std::cout << "Max: " << *max_it; // 9

// Min and max together
auto [min, max] = std::minmax_element(vec.begin(), vec.end());

// Min/max of values
int smaller = std::min(5, 10);
int larger = std::max(5, 10);
auto [a, b] = std::minmax(5, 10); // {5, 10}

Permutations

std::vector<int> vec = {1, 2, 3};

// Next permutation
do {
for (int x : vec) std::cout << x << " ";
std::cout << "\n";
} while (std::next_permutation(vec.begin(), vec.end()));

// Prints all permutations:
// 1 2 3
// 1 3 2
// 2 1 3
// 2 3 1
// 3 1 2
// 3 2 1

// Previous permutation
std::prev_permutation(vec.begin(), vec.end());

Numeric Algorithms

#include <numeric>

std::vector<int> vec = {1, 2, 3, 4, 5};

// Sum (accumulate)
int sum = std::accumulate(vec.begin(), vec.end(), 0); // 15

// Product
int product = std::accumulate(vec.begin(), vec.end(), 1, std::multiplies<>());

// Custom operation
int result = std::accumulate(vec.begin(), vec.end(), 0, [](int acc, int x) {
return acc + x * x; // Sum of squares
}); // 55

// Inner product
std::vector<int> vec2 = {1, 2, 3, 4, 5};
int dot = std::inner_product(vec.begin(), vec.end(), vec2.begin(), 0);
// 1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 55

// Partial sum
std::vector<int> partial(5);
std::partial_sum(vec.begin(), vec.end(), partial.begin());
// partial = {1, 3, 6, 10, 15}

// Adjacent difference
std::vector<int> diff(5);
std::adjacent_difference(vec.begin(), vec.end(), diff.begin());
// diff = {1, 1, 1, 1, 1}

// iota - fill with incrementing values
std::vector<int> seq(10);
std::iota(seq.begin(), seq.end(), 0);
// seq = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

C++17 Parallel Algorithms

#include <execution>

std::vector<int> vec(1000000);

// Sequential
std::sort(vec.begin(), vec.end());

// Parallel
std::sort(std::execution::par, vec.begin(), vec.end());

// Parallel unsequenced (most aggressive)
std::sort(std::execution::par_unseq, vec.begin(), vec.end());

// Works with many algorithms:
std::for_each(std::execution::par, vec.begin(), vec.end(), [](int& x) {
x *= 2;
});

std::transform(std::execution::par, vec.begin(), vec.end(), vec.begin(),
[](int x) { return x * x; });
Algorithm Quick Reference

Finding: find, find_if, count, count_if
Copying: copy, copy_if, move
Transforming: transform, replace, fill, generate
Removing: remove, remove_if, unique
Sorting: sort, stable_sort, partial_sort, nth_element
Searching: binary_search, lower_bound, upper_bound
Set ops: set_union, set_intersection, set_difference
Min/Max: min_element, max_element
Numeric: accumulate, inner_product, partial_sum
Always O(n log n) or better when sorted
Parallel (C++17) = add execution policy for speedup