Hash Map (מפת גיבוב)
עודכן לאחרונה
Hash map שומר זוגות מפתח-ערך ומאפשר למצוא ערך לפי המפתח שלו ב-O(1) בממוצע. הוא עובד בדיוק כמו טבלת גיבוב, אבל כל רשומה בדלי נושאת גם מפתח וגם את הערך המשויך לו. כדי לשמור או לשלוף זוג, מגבבים את המפתח לאינדקס של דלי, ואז סורקים את השרשרת של הדלי כדי למצוא את המפתח המתאים. לחצו על הפעלה למעלה כדי לראות זוגות מוצבים לפי דלי מגובב וערך נשלף לפי המפתח שלו.
זה המבנה שמאחורי dict של Python, HashMap של Java ו-Map/אובייקטים של JavaScript. התנגשויות מטופלות באותה דרך כמו בטבלת גיבוב, כאן בעזרת שרשור נפרד, ולכן הביצועים תלויים בפונקציית גיבוב טובה ובמקדם עומס נמוך.
סיבוכיות זמן
| פעולה | ממוצע | המקרה הגרוע |
|---|---|---|
| Put (הכנסה או עדכון) | O(1) | O(n) |
| Get (חיפוש) | O(1) | O(n) |
| מחיקה | O(1) | O(n) |
| זיכרון | O(n) | O(n) |
Hash map בשפות נפוצות
| שפה | טיפוס |
|---|---|
| Python | dict |
| Java | HashMap |
| JavaScript | Map / object |
| 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. |
מתי להשתמש ב-hash map
| השתמשו בו כאשר | הימנעו ממנו כאשר |
|---|---|
אתם צריכים חיפוש, הכנסה ומחיקה של O(1) בממוצע לפי מפתח מדויק. | אתם צריכים מפתחות בסדר ממוין או שאילתות טווח: השתמשו ב-BST מאוזן או במפה ממוינת. |
| המפתחות ניתנים לגיבוב והשוויון מוגדר היטב (מחרוזות, מספרים שלמים, tuples). | המפתחות ניתנים לשינוי ויכולים להשתנות אחרי ההכנסה, מה שמשבש את הדלי שלהם. |
| אתם סופרים, מסירים כפילויות, שומרים במטמון או מאנדקסים לפי מזהה. | אתם צריכים הבטחות של 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}שאלות נפוצות על hash map
מה ההבדל בין hash map לטבלת גיבוב (hash table)?
מהי סיבוכיות הזמן של hash map?
O(1) בממוצע עם פונקציית גיבוב טובה ומקדם עומס נמוך. במקרה הפתולוגי שבו כל המפתחות מתנגשים לאותו דלי, הם מידרדרים ל-O(n), ולכן שינוי גודל וגיבוב טוב חשובים.איך hash map מטפל בשני מפתחות שמגובבים לאותו דלי?
Hash map או עץ חיפוש בינארי: במה להשתמש?
O(1) בממוצע. השתמשו בעץ חיפוש בינארי מאוזן (כמו TreeMap או std::map) כשצריך מפתחות בסדר ממוין, שאילתות טווח או חיפוש קודם ועוקב, שעולים O(log n). ה-BST מוותר על מעט מהירות בתמורה להבטחות סדר ש-hash map לא יכול לספק.מהו מקדם העומס ולמה הוא גורם לשינוי גודל?
0.75. שינוי גודל הוא O(n) אבל נדיר, ולכן העלות מתפזרת ושומרת על פעולות ממוצעות של O(1).האם אפשר להשתמש באובייקט שניתן לשינוי, כמו רשימה, כמפתח ב-hash map?
TypeError: unhashable type עבור list. אם משנים מפתח אחרי שהוכנס, הגיבוב שלו משתנה והמפה כבר לא מוצאת אותו בדלי הנכון, והרשומה הולכת לאיבוד בשקט. השתמשו במפתחות שאינם ניתנים לשינוי, כמו מחרוזות, מספרים או tuples.