Vùng chứa kết hợp: bản đồ, bộ, unordered_map
Learn about ordered and unordered associative containers — maps, sets, and their multi- and unordered variants. Understand when to choose trees vs hash tables.
Các vùng chứa kết hợp có thứ tự và không có thứ tự
STL cung cấp hai họ vùng chứa kết hợp:
Đã đặt hàng (được triển khai dưới dạng cây đỏ đen):
-std::map— các cặp khóa-giá trị được sắp xếp, các khóa duy nhất
-std::set— sắp xếp các khóa duy nhất
-std::multimap— các cặp khóa-giá trị được sắp xếp, cho phép khóa trùng lặp
-std::multiset— sắp xếp khóa, cho phép trùng lặp
Không có thứ tự (được triển khai dưới dạng bảng băm):
-std::unordered_map— khóa-giá trị dựa trên hàm băm, khóa duy nhất
-std::unordered_set— khóa duy nhất dựa trên hàm băm
-std::unordered_multimap— khóa trùng lặp, dựa trên hàm băm
-std::unordered_multiset— dựa trên hàm băm, trùng lặp
Các vùng chứa được sắp xếp giữ cho các phần tử được sắp xếp và cung cấp khả năng tra cứu, chèn và xóa O(log n). Các vùng chứa không có thứ tự sử dụng hàm băm cho các hoạt động trong trường hợp trung bình O(1) nhưng trường hợp xấu nhất là O(n) nếu xảy ra nhiều xung đột.
Sử dụng bản đồ và thiết lập
std::maplưu trữ các cặp khóa-giá trị được sắp xếp theo khóa.std::setlưu trữ các giá trị được sắp xếp duy nhất. Cả hai đều được triển khai dưới dạng cây đỏ đen tự cân bằng, đảm bảo hoạt động O(log n).
#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';
}Vùng chứa không có thứ tự và hàm băm tùy chỉnh
Các vùng chứa không có thứ tự sử dụng bảng băm để tra cứu trường hợp trung bình O(1). Đối với các loại khóa tùy chỉnh, bạn phải cung cấp hàm băm và toán tử đẳng thức.
#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 và Multiset
std::multimapvàstd::multisetcho phép khóa trùng lặp. Sử dụngequal_range()để tìm tất cả các mục có khóa nhất định.
#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
}Bộ so sánh tùy chỉnh cho các vùng chứa được đặt hàng
Các vùng chứa được đặt hàng mặc định làstd::less(thứ tự tăng dần). Bạn có thể cung cấp bộ so sánh tùy chỉnh làm đối số mẫu thứ ba:
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<=>) để tự động tạo tất cả toán tử so sánh.
Sử dụngoperator[]trênstd::maphoặcstd::unordered_mapchèn giá trị được tạo mặc định nếu khóa không tồn tại. Đây là nguồn phổ biến của các lỗi nhỏ:
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() nếu bạn muốn có ngoại lệ đối với các khóa bị thiếu.
Sử dụngunordered_map/unordered_set khi bạn chỉ cần tra cứu, chèn và xóa — trường hợp trung bình O(1) vượt trội O(log n) cho các bộ sưu tập lớn.
Sử dụngmap/set khi bạn cần:
- Sắp xếp thứ tự lặp lại
- Truy vấn phạm vi (lower_bound,upper_bound,equal_range)
- Đảm bảo trường hợp xấu nhất là O(log n) (không có xung đột băm bệnh lý)
Đối với các bộ sưu tập nhỏ (ít hơn ~100 phần tử),std::vectorđược sắp xếp vớistd::lower_boundcó thể hoạt động tốt hơn cả do vị trí bộ đệm.
std::mapvàstd::setsử dụng cây đỏ đen - thao tác O(log n) với thứ tự được sắp xếpstd::unordered_mapvàstd::unordered_setsử dụng bảng băm - trung bình O(1) nhưng yêu cầu hàm băm- Sử dụng các liên kết có cấu trúc C++17 (
auto& [key, val]) để lặp lại bản đồ rõ ràng operator[]on maps silently inserts default values — usefind()orcontains()for safe lookup- Custom types as keys require
operator<(ordered) or a hash function +operator==(unordered) try_emplace(C++17) tránh xây dựng giá trị nếu khóa đã tồn tại
Câu đố — Kiểm tra kiến thức
(15 XP)1. What is the average time complexity of lookup in`std::unordered_map`?
2. Điều gì xảy ra khi bạn sử dụng`operator[]`với khóa không tồn tại trong`std::map`?
3. Bạn nên chọn vùng chứa nào nếu cần truy vấn phạm vi VÀ lặp lại được sắp xếp (low_bound/upper_bound)?