Containere asociative: hartă, set, hartă_neordonată
Aflați despre containerele asociative ordonate și neordonate — hărți, seturi și variantele lor multiple și neordonate. Înțelegeți când să alegeți arbori vs tabelele hash.
Containere asociative comandate vs necomandate
STL oferă două familii de containere asociative:
Comandat (implementat ca arbori roșu-negru):
-std::map— perechi cheie-valoare sortate, chei unice
-std::set— chei unice sortate
-std::multimap— perechi cheie-valoare sortate, chei duplicate permise
-std::multiset— chei sortate, duplicate permise
Neordonat (implementat ca tabele hash):
-std::unordered_map— chei-valoare bazate pe hash, chei unice
-std::unordered_set— chei unice bazate pe hash
-std::unordered_multimap— chei duplicate bazate pe hash
-std::unordered_multiset— bazate pe hash, duplicate
Containerele comandate păstrează elementele sortate și oferă căutarea, inserarea și ștergerea O(log n). Containerele neordonate folosesc hashing pentru operațiuni cu cazuri medii O(1), dar O(n) caz mai rău dacă au loc multe coliziuni.
Folosind harta și set
std::mapstores key-value pairs sorted by key.std::setstores unique sorted values. Both are implemented as self-balancing red-black trees, guaranteeing O(log n) operations.
#include <map>
#include <set>
#include <string>
#include <iostream>
int main() {
// std::map — sorted key-value pairs
std::map<std::string, int> ages;
ages["Alice"] = 30; // insert or update
ages["Bob"] = 25;
ages.insert({"Charlie", 35}); // insert only if key absent
ages.emplace("Diana", 28); // construct in-place
// Lookup
if (auto it = ages.find("Bob"); it != ages.end()) {
std::cout << it->first << " is " << it->second << '\n';
}
// C++17 structured bindings — elegant map iteration
for (const auto& [name, age] : ages) {
std::cout << name << ": " << age << '\n';
}
// Output is sorted by key: Alice, Bob, Charlie, Diana
// std::set — sorted unique values
std::set<int> primes = {2, 3, 5, 7, 11, 13};
primes.insert(5); // no effect — 5 already present
primes.insert(17); // inserted
if (primes.contains(7)) { // C++20
std::cout << "7 is prime\n";
}
// count() — returns 0 or 1 for set (useful pre-C++20)
if (primes.count(4) == 0) {
std::cout << "4 is not prime\n";
}
// lower_bound / upper_bound — range queries
auto lo = primes.lower_bound(5);
auto hi = primes.upper_bound(11);
for (auto it = lo; it != hi; ++it) {
std::cout << *it << ' '; // 5 7 11
}
std::cout << '\n';
}Unordered Containers and Custom Hash Functions
Unordered containers use hash tables for O(1) average-case lookup. For custom key types, you must provide a hash function and an equality operator.
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <iostream>
// Custom type as key
struct Point {
int x, y;
bool operator==(const Point&) const = default; // C++20
};
// Custom hash function
struct PointHash {
std::size_t operator()(const Point& p) const {
// Combine hashes — a common pattern
auto h1 = std::hash<int>{}(p.x);
auto h2 = std::hash<int>{}(p.y);
return h1 ^ (h2 << 1); // simple combine (consider better hashes in production)
}
};
int main() {
// Basic unordered_map — O(1) average lookup
std::unordered_map<std::string, double> prices;
prices["apple"] = 1.50;
prices["banana"] = 0.75;
prices["cherry"] = 3.00;
// try_emplace (C++17) — only constructs value if key is absent
prices.try_emplace("apple", 9.99); // no-op, "apple" exists
prices.try_emplace("date", 4.50); // inserted
// extract and insert (C++17 node API)
auto node = prices.extract("banana");
if (!node.empty()) {
node.key() = "plantain"; // rename key without copy
prices.insert(std::move(node));
}
for (const auto& [fruit, price] : prices) {
std::cout << fruit << ": $" << price << '\n';
}
// Custom type as key
std::unordered_set<Point, PointHash> points;
points.insert({1, 2});
points.insert({3, 4});
points.insert({1, 2}); // duplicate — not inserted
std::cout << "Unique points: " << points.size() << '\n'; // 2
}Multimap and Multiset
std::multimapandstd::multisetallow duplicate keys. Useequal_range()to find all entries with a given key.
#include <map>
#include <set>
#include <iostream>
#include <string>
int main() {
// multimap — multiple values per key
std::multimap<std::string, std::string> phone_book;
phone_book.insert({"Alice", "555-0100"});
phone_book.insert({"Alice", "555-0101"}); // duplicate key OK
phone_book.insert({"Bob", "555-0200"});
// equal_range returns a pair of iterators [first, last)
auto [begin, end] = phone_book.equal_range("Alice");
std::cout << "Alice's numbers:\n";
for (auto it = begin; it != end; ++it) {
std::cout << " " << it->second << '\n';
}
// multiset — count duplicates
std::multiset<int> ms = {1, 2, 2, 3, 3, 3};
std::cout << "Count of 3: " << ms.count(3) << '\n'; // 3
}Custom Comparators for Ordered Containers
Ordered containers default tostd::less(ascending order). You can supply a custom comparator as the third template argument:
std::set<int, std::greater<int>> descending = {1, 5, 3};
// iteration order: 5, 3, 1For custom types, you can either define operator< in your class or pass a comparator functor or lambda:
auto cmp = [](const Point& a, const Point& b) {
return a.x < b.x || (a.x == b.x && a.y < b.y);
};
std::set<Point, decltype(cmp)> point_set(cmp);In C++20, you can use the spaceship operator (operator<=>) to automatically generate all comparison operators.
Utilizareaoperator[]pe unstd::mapsaustd::unordered_mapinseră o valoare construită implicit dacă cheia nu există. Aceasta este o sursă comună de erori subtile:
std::map<std::string, int> m;
int val = m["missing"]; // inserts {"missing", 0} into the map!Use find(), count(), or contains() (C++20) for lookup without modification. Use at() dacă doriți o excepție pentru cheile lipsă.
Folosițiunordered_map/unordered_set atunci când aveți nevoie doar de căutare, inserare și ștergere — litera medie O(1) bate O(log n) pentru colecții mari.
Folosițimap/set când aveți nevoie de:
- Ordine de iterație sortată
- Interogări de interval (lower_bound,upper_bound,equal_range)
- O(log n) garantat în cel mai rău caz (fără coliziuni hash patologice)
Pentru colecțiile mici (mai puțin de ~ 100 de elemente), unstd::vectorsortat custd::lower_boundle poate depăși pe ambele datorită localității cache-ului.
std::mapșistd::setfolosesc arbori roșu-negru — operațiuni O(log n) cu ordine sortatăstd::unordered_mapșistd::unordered_setfolosesc tabele hash - medie O(1), dar necesită o funcție hash- Utilizați legături structurate C++17 (
auto& [key, val]) pentru o iterație curată a hărții operator[]pe hărți inserează în tăcere valorile implicite - utilizațifind()saucontains()pentru o căutare sigură- Tipurile personalizate ca chei necesită
operator<(comandat) sau o funcție hash +operator==(neordonat) try_emplace(C++17) evită construirea valorii dacă cheia există deja
Test — Verifică-ți cunoștințele
(15 XP)1. Care este complexitatea medie de timp a căutării în`std::unordered_map`?
2. Ce se întâmplă când utilizați`operator[]`cu o cheie care nu există într-un`std::map`?
3. Ce container ar trebui să alegeți dacă aveți nevoie de iterații sortate ȘI de interogări de interval (lower_bound/upper_bound)?