Hash Map
آخر تحديث
تخزّن خريطة التجزئة أزواج المفتاح-القيمة وتتيح لك البحث عن قيمة بمفتاحها في O(1) في المتوسط. تعمل تمامًا مثل جدول التجزئة، لكن كل مُدخَل في الدلو يحمل مفتاحًا وقيمته المرتبطة به معًا. لتخزين زوج أو جلبه، يُجزَّأ المفتاح إلى فهرس دلو، ثم تُمسَح سلسلة ذلك الدلو بحثًا عن المفتاح المطابق. اضغط على تشغيل بالأعلى لمشاهدة الأزواج توضَع حسب الدلو المُجزَّأ وقيمة تُسترجَع بمفتاحها.
هذه هي البنية وراء dict في بايثون وHashMap في جافا وMap/الكائنات في جافاسكريبت. تُعالَج التصادمات بالطريقة نفسها كما في جدول التجزئة - هنا عبر التسلسل المنفصل - لذا يعتمد الأداء على دالة تجزئة جيدة وعامل تحميل منخفض.
التعقيد الزمني
| العملية | المتوسط | أسوأ حالة |
|---|---|---|
| إدراج/تحديث (put) | O(1) | O(n) |
| بحث (get) | O(1) | O(n) |
| حذف | O(1) | O(n) |
| المساحة | O(n) | O(n) |
خريطة التجزئة في اللغات الشائعة
| اللغة | النوع |
|---|---|
| Python | dict |
| Java | HashMap |
| JavaScript | Map / كائن |
| C++ | std::unordered_map |
| Go | map |
مثال محلول
إدراج الأزواج ("cat", 3) و("dog", 5) و("cat", 9) و("emu", 7) في خريطة بـ 8 دلاء، باستخدام hash(key) % 8. لنفترض hash("cat") % 8 = 2 وhash("dog") % 8 = 5 وhash("emu") % 8 = 2:
| الخطوة | البنية | الإجراء |
|---|---|---|
Put ("cat", 3) | الدلو 2: [("cat", 3)] | يُجزَّأ إلى الدلو 2؛ السلسلة فارغة، لذا يُضاف الزوج. |
Put ("dog", 5) | الدلو 2: [("cat", 3)]، الدلو 5: [("dog", 5)] | يُجزَّأ إلى الدلو 5؛ السلسلة فارغة، لذا يُضاف الزوج. |
Put ("cat", 9) | الدلو 2: [("cat", 9)]، الدلو 5: [("dog", 5)] | يُجزَّأ إلى الدلو 2؛ المفتاح "cat" موجود بالفعل في السلسلة، لذا تُحدَّث قيمته إلى 9. |
Put ("emu", 7) | الدلو 2: [("cat", 9), ("emu", 7)]، الدلو 5: [("dog", 5)] | يُجزَّأ إلى الدلو 2؛ يتصادم مع "cat"، ولا يُعثَر على المفتاح، لذا يُضاف الزوج. |
Get "emu" | الدلو 2: [("cat", 9), ("emu", 7)] | يُجزَّأ إلى الدلو 2؛ تُمسَح السلسلة، ويُتخطَّى "cat"، ويطابق "emu"، وتُعاد 7. |
متى تستخدم خريطة التجزئة
| استخدمها عندما | تجنّبها عندما |
|---|---|
تحتاج بحثًا وإدراجًا وحذفًا بمتوسط O(1) عبر مفتاح دقيق. | تحتاج إلى إبقاء المفاتيح مرتبة أو استعلامات نطاق - استخدم شجرة بحث ثنائية متوازنة أو خريطة مرتبة. |
| المفاتيح قابلة للتجزئة والمساواة معرَّفة جيدًا (سلاسل، أعداد صحيحة، صفوف). | المفاتيح قابلة للتغيير ويمكن أن تتغير بعد الإدراج، مما يُفسد دلوها. |
| تقوم بالعدّ أو إزالة التكرار أو التخزين المؤقت أو الفهرسة عبر معرِّف. | تحتاج إلى ضمانات أسوأ حالة O(log n) بدل حدود متوسطة مُستهلَكة. |
| ترتيب التكرار غير مهم، أو تكفي خريطة مرتبة حسب الإدراج. | الذاكرة محدودة جدًا - الدلاء وهامش عامل التحميل والمؤشرات تضيف عبئًا. |
كود Hash Map
تنفيذ نظيف وقابل للتشغيل لخوارزمية Hash Map بلغات Python, JavaScript, Java, C++, C. اختر لغة، وانسخ الكود، أو افتحه محمّلًا مسبقًا في ساحة تجربة Coddy.
كود Hash Map بلغة Python
1# Python's built-in dict is a hash map: O(1) average2# insert, lookup, and delete.3inventory = {"apple": 3, "banana": 7}4
5# Insert and update6inventory["cherry"] = 57inventory["apple"] += 28
9# Lookup, with .get for a safe default on missing keys10print("apple: ", inventory["apple"])11print("mango: ", inventory.get("mango", 0))12
13# Membership test and delete14print("banana in stock:", "banana" in inventory)15del inventory["banana"]16print("banana in stock:", "banana" in inventory)17
18# Iterate over key-value pairs19for fruit, count in sorted(inventory.items()):20 print(f"{fruit}: {count}")21
22# Classic hash map use case: counting frequencies23words = "the quick brown fox jumps over the lazy dog the end".split()24freq = {}25for word in words:26 freq[word] = freq.get(word, 0) + 127
28print("Occurrences of the:", freq["the"])29print("Most common word: ", max(freq, key=freq.get))كود Hash Map بلغة JavaScript
1// The built-in Map is the idiomatic hash map in JavaScript:2// any key type, insertion-ordered iteration, O(1) average lookups.3const ages = new Map();4
5ages.set("Alice", 30);6ages.set("Bob", 25);7ages.set("Carol", 35);8ages.set("Bob", 26); // set on an existing key updates the value9
10console.log("Bob:", ages.get("Bob"));11console.log("Has Dave?", ages.has("Dave"));12console.log("Size:", ages.size);13
14ages.delete("Carol");15console.log("Size after delete:", ages.size);16
17for (const [name, age] of ages) {18 console.log(`${name} is ${age}`);19}20
21console.log("Keys:", [...ages.keys()].join(", "));كود Hash Map بلغة Java
1import java.util.HashMap;2import java.util.Map;3
4public class Main {5 public static void main(String[] args) {6 Map<String, Integer> ages = new HashMap<>();7 ages.put("Alice", 30);8 ages.put("Bob", 25);9 ages.put("Carol", 35);10
11 System.out.println("Bob -> " + ages.get("Bob"));12 System.out.println("contains Carol: " + ages.containsKey("Carol"));13 System.out.println("Dave or default: " + ages.getOrDefault("Dave", -1));14
15 // getOrDefault makes frequency counting a one-liner16 String[] words = {"apple", "banana", "apple", "cherry", "apple"};17 Map<String, Integer> counts = new HashMap<>();18 for (String w : words) {19 counts.put(w, counts.getOrDefault(w, 0) + 1);20 }21 System.out.println("apple count: " + counts.get("apple"));22
23 ages.remove("Bob");24 System.out.println("size after remove: " + ages.size());25
26 for (Map.Entry<String, Integer> entry : ages.entrySet()) {27 System.out.println(entry.getKey() + " is " + entry.getValue());28 }29 }30}كود Hash Map بلغة C++
1#include <algorithm>2#include <iostream>3#include <string>4#include <unordered_map>5#include <vector>6
7int main() {8 std::unordered_map<std::string, int> ages;9
10 // Insert and update11 ages["alice"] = 30; // operator[] inserts or overwrites12 ages.insert({"bob", 25}); // insert keeps an existing value13 ages.emplace("carol", 41);14 ages["alice"] = 31;15
16 // Lookup17 auto it = ages.find("bob");18 if (it != ages.end()) {19 std::cout << "bob is " << it->second << "\n";20 }21 std::cout << "has dave: " << ages.count("dave") << "\n";22
23 // Remove24 ages.erase("carol");25
26 // Iterate (sort keys first: unordered_map has no defined order)27 std::vector<std::string> keys;28 for (const auto& entry : ages) keys.push_back(entry.first);29 std::sort(keys.begin(), keys.end());30 for (const std::string& name : keys) {31 std::cout << name << " = " << ages[name] << "\n";32 }33 std::cout << "size: " << ages.size() << "\n";34 return 0;35}كود Hash Map بلغة C
1#include <stdbool.h>2#include <stdio.h>3#include <stdlib.h>4#include <string.h>5
6#define BUCKETS 167
8typedef struct Node {9 char key[24];10 int value;11 struct Node* next;12} Node;13
14// A small string -> int hash map with a reusable put/get/remove API15typedef struct {16 Node* buckets[BUCKETS];17 int size;18} HashMap;19
20unsigned int hashKey(const char* key) {21 unsigned int h = 5381;22 for (; *key; key++) h = h * 33 + (unsigned char)*key;23 return h % BUCKETS;24}25
26void mapPut(HashMap* map, const char* key, int value) {27 unsigned int i = hashKey(key);28 for (Node* n = map->buckets[i]; n != NULL; n = n->next) {29 if (strcmp(n->key, key) == 0) {30 n->value = value;31 return;32 }33 }34 Node* n = malloc(sizeof(Node));35 strcpy(n->key, key);36 n->value = value;37 n->next = map->buckets[i];38 map->buckets[i] = n;39 map->size++;40}41
42bool mapGet(const HashMap* map, const char* key, int* out) {43 for (Node* n = map->buckets[hashKey(key)]; n != NULL; n = n->next) {44 if (strcmp(n->key, key) == 0) {45 *out = n->value;46 return true;47 }48 }49 return false;50}51
52void mapRemove(HashMap* map, const char* key) {53 Node** link = &map->buckets[hashKey(key)];54 while (*link != NULL) {55 if (strcmp((*link)->key, key) == 0) {56 Node* old = *link;57 *link = old->next;58 free(old);59 map->size--;60 return;61 }62 link = &(*link)->next;63 }64}65
66int main(void) {67 HashMap map = {0};68 mapPut(&map, "alice", 30);69 mapPut(&map, "bob", 25);70 mapPut(&map, "carol", 41);71 mapPut(&map, "alice", 31); // updates the existing key72 int age;73 if (mapGet(&map, "bob", &age)) printf("bob is %d\n", age);74 printf("has dave: %s\n", mapGet(&map, "dave", &age) ? "yes" : "no");75 mapRemove(&map, "carol");76 printf("size: %d\n", map.size);77 return 0;78}الأسئلة الشائعة حول خريطة التجزئة
ما الفرق بين خريطة التجزئة وجدول التجزئة؟
ما هو التعقيد الزمني لخريطة التجزئة؟
O(1) في المتوسط مع دالة تجزئة جيدة وعامل تحميل منخفض. في الحالة المرضية التي تتصادم فيها جميع المفاتيح في دلو واحد، تتدهور إلى O(n)، ولهذا يهم إعادة التحجيم والتجزئة الجيدة.كيف تتعامل خريطة التجزئة مع مفتاحين يُجزَّآن إلى الدلو نفسه؟
خريطة التجزئة أم شجرة البحث الثنائية: أيهما أستخدم؟
O(1). استخدم شجرة بحث ثنائية متوازنة (مثل TreeMap أو std::map) عندما تحتاج إلى مفاتيح مرتبة أو استعلامات نطاق أو عمليات بحث عن السابق/اللاحق، وتكلفتها O(log n). تُقايض الشجرة قليلًا من السرعة مقابل ضمانات ترتيب لا تستطيع خريطة التجزئة توفيرها.ما هو عامل التحميل ولماذا يُطلق إعادة التحجيم؟
0.75. إعادة التحجيم هي O(n) لكنها نادرة، فتُستهلَك وتُبقي العمليات المتوسطة عند O(1).هل يمكنني استخدام كائن قابل للتغيير مثل قائمة كمفتاح لخريطة التجزئة؟
TypeError: unhashable type من أجل list مثلًا. إذا غيّرت مفتاحًا بعد إدراجه، تتغير تجزئته ولا تستطيع الخريطة إيجاده بعد ذلك في الدلو الصحيح، فتفقد المُدخَل بصمت. استخدم مفاتيح غير قابلة للتغيير مثل السلاسل أو الأرقام أو الصفوف.