الحاويات الترابطية: خريطة، مجموعة، خريطة غير مرتبة
تعرف على الحاويات الترابطية المرتبة وغير المرتبة - الخرائط والمجموعات ومتغيراتها المتعددة وغير المرتبة. افهم متى تختار الأشجار مقابل جداول التجزئة.
الحاويات الترابطية المرتبة مقابل غير المرتبة
توفر المحكمة الخاصة بلبنان عائلتين من الحاويات الترابطية:
مرتبة (يتم تنفيذها كأشجار حمراء وسوداء):
-std::map- أزواج ذات قيمة مفتاحية مرتبة، ومفاتيح فريدة
-std::set- مفاتيح فريدة مرتبة
-std::multimap- أزواج قيمة المفتاح مرتبة، والمفاتيح المكررة مسموح بها
-std::multiset— مفاتيح مرتبة، مسموح بالتكرارات
غير مرتبة (يتم تنفيذها كجداول تجزئة):
-std::unordered_map— قيمة المفتاح المستندة إلى التجزئة، والمفاتيح الفريدة
-std::unordered_set— مفاتيح فريدة قائمة على التجزئة
-std::unordered_multimap- مفاتيح مكررة تعتمد على التجزئة
-std::unordered_multiset- نسخ مكررة تعتمد على التجزئة
تحافظ الحاويات المرتبة على ترتيب العناصر وتوفر البحث عن O(log n) وإدراجها وحذفها. تستخدم الحاويات غير المرتبة التجزئة لعمليات الحالة المتوسطة O(1) ولكن الحالة الأسوأ O(n) في حالة حدوث العديد من الاصطدامات.
باستخدام الخريطة وتعيين
يقومstd::mapبتخزين أزواج القيمة الرئيسية مرتبة حسب المفتاح. يقومstd::setبتخزين القيم المصنفة الفريدة. يتم تنفيذ كلاهما كأشجار ذات لون أحمر وأسود ذاتية التوازن، مما يضمن عمليات 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';
}الحاويات غير المرتبة ووظائف التجزئة المخصصة
تستخدم الحاويات غير المرتبة جداول التجزئة للبحث عن الحالة المتوسطة لـ O(1). بالنسبة لأنواع المفاتيح المخصصة، يجب عليك توفير دالة تجزئة وعامل مساواة.
#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
}الخرائط المتعددة والمجموعات المتعددة
يسمحstd::multimapوstd::multisetبمفاتيح مكررة. استخدمequal_range()للعثور على جميع الإدخالات باستخدام مفتاح معين.
#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
}مقارنات مخصصة للحاويات المطلوبة
الحاويات المطلوبة الافتراضية هيstd::less(ترتيب تصاعدي). يمكنك توفير مقارنة مخصصة كوسيطة القالب الثالث:
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<=>) لإنشاء جميع عوامل المقارنة تلقائيًا.
يؤدي استخدامoperator[]علىstd::mapأوstd::unordered_map إلى إدراج قيمة تم إنشاؤها افتراضيًا في حالة عدم وجود المفتاح. هذا مصدر شائع للأخطاء الدقيقة:
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() إذا كنت تريد استثناءً للمفاتيح المفقودة.
استخدمunordered_map/unordered_set عندما تحتاج فقط إلى البحث والإدراج والحذف - تتفوق الحالة المتوسطة O(1) على O(log n) للمجموعات الكبيرة.
استخدمmap/set عندما تحتاج إلى:
- ترتيب التكرار مرتبة
- استعلامات النطاق (lower_bound،upper_bound،equal_range)
- أسوأ حالة مضمونة لـ O(log n) (لا توجد تصادمات تجزئة مرضية)
بالنسبة للمجموعات الصغيرة (أقل من 100 عنصر تقريبًا)، يمكن أن يتفوق أداءstd::vectorمعstd::lower_boundعلى كليهما بسبب منطقة ذاكرة التخزين المؤقت.
- يستخدم
std::mapوstd::setالأشجار ذات اللون الأحمر والأسود — عمليات O(log n) بترتيب مفروز - يستخدم
std::unordered_mapوstd::unordered_setجداول التجزئة - متوسط O(1) ولكنهما يتطلبان وظيفة تجزئة - استخدم الارتباطات الهيكلية C++17 (
auto& [key, val]) لتكرار الخريطة النظيفة - يُدرج
operator[]على الخرائط القيم الافتراضية بصمت — استخدمfind()أوcontains()للبحث الآمن - تتطلب الأنواع المخصصة كمفاتيح
operator<(مرتبة) أو وظيفة التجزئة +operator==(غير مرتبة) - يتجنب
try_emplace(C++17) إنشاء القيمة إذا كان المفتاح موجودًا بالفعل
اختبار — اختبر معرفتك
(15 نقطة خبرة)1. ما هو متوسط التعقيد الزمني للبحث في`std::unordered_map`؟
2. ماذا يحدث عند استخدام`operator[]`مع مفتاح غير موجود في`std::map`؟
3. ما هي الحاوية التي يجب أن تختارها إذا كنت بحاجة إلى تكرار مصنف واستعلامات نطاق (lower_bound/upper_bound)؟