The Set ADT
An unordered collection of distinct elements. Core ops:
-
contains(x)— membership. -
add(x)— insert; no-op if already present. -
remove(x)— delete; no-op if absent.
Derived: size, empty, iteration. Everything else (union, intersection, subset, filter, map) is built from these.
Keys vs. elements
The set stores elements compared by some key — often the element itself. When elements are complex (structs), you pass a comparator (ordered set) or a hash + equality (unordered).
Subset testing
iff . Iterate the smaller side, probe the larger. for hashed , for tree-based.
Set operations
Union / intersection commute; difference does not.
// Hashed: iterate one, probe the other
for (auto& x : A)
if (B.contains(x)) out.insert(x);
// Sorted (std::set): use STL merge-style
std::set_intersection(
A.begin(), A.end(),
B.begin(), B.end(),
std::inserter(out, out.end())); Always iterate the smaller set
Intersection cost drops to . Single biggest micro-optimization in set-heavy code.
Cost table
hashed sorted (tree)
contains
add / remove
union / inters.
sorted iter
lower_bound —
expected.
Static vs. dynamic
Static set: fixed membership after construction; supports read-only + new-producing ops. Allows compact encodings (sorted array, bitset, perfect hash, Bloom filter). Dynamic: supports add / remove. Pay more per element for that flexibility.
need static dynamic
build once, query yes overkill small memory yes harder mutate at runtime no yes tuned lookup perfect hash, bitset hash / tree
Containers you’ll actually use
container order impl.
std::set sorted red-black tree
std::multiset sorted, dups red-black tree
std::unordered_set hash order open-addr. hash
Python set hash open-addr. hash
Python frozenset hash, immutable open-addr. hash
Rust HashSet / BTreeSet — / sorted hash / B-tree
Picking the implementation
-
Just need membership, order doesn’t matter:
unordered_set. -
Need sorted iteration, range queries, or
lower_bound:set. -
Fixed membership, lookup-only: sorted array +
std::binary_search, bitset, or perfect hash. -
Extremely tight memory, “probably in set” OK: Bloom filter (approximate).
Common patterns
-
Dedup:
std::set<int> s(v.begin(), v.end())orsort + unique + erase. -
Seen-set during traversal: avoid cycles in BFS / DFS.
-
Set as filter: keep only items whose key is in a whitelist / not in a blacklist.
Top gotchas
-
Relying on iteration order in
unordered_set— implementation-defined and libc-version-dependent. -
Mutating elements in a sorted set in a way that changes their sort key — invalidates the invariant silently.
-
Using
set_intersection/set_unionfrom<algorithm>on unsorted ranges — requires sorted input. -
Writing as in math then forgetting which STL algorithm corresponds (
set_difference). -
Assuming “const” means truly immutable in C++ — someone can
const_castit away.