Hash map
Cập nhật lần cuối
Hash map lưu các cặp khóa-giá trị và cho phép bạn tra cứu một giá trị theo khóa của nó trong trung bình O(1). Nó hoạt động giống hệt bảng băm (hash table), nhưng mỗi mục trong bucket mang cả khóa lẫn giá trị đi kèm. Để lưu hoặc lấy một cặp, khóa được băm thành chỉ số bucket, rồi chuỗi của bucket đó được duyệt để tìm khóa khớp. Nhấn Phát ở trên để xem các cặp được đặt vào bucket theo giá trị băm và một giá trị được lấy ra theo khóa.
Đây là cấu trúc đứng sau dict của Python, HashMap của Java và Map/object của JavaScript. Xung đột được xử lý giống như trong bảng băm, ở đây là bằng nối chuỗi (separate chaining), nên hiệu năng phụ thuộc vào một hàm băm tốt và hệ số tải thấp.
Độ phức tạp thời gian
| Thao tác | Trung bình | Xấu nhất |
|---|---|---|
| Put (chèn/cập nhật) | O(1) | O(n) |
| Get (tra cứu) | O(1) | O(n) |
| Xóa | O(1) | O(n) |
| Không gian | O(n) | O(n) |
Hash map trong các ngôn ngữ phổ biến
| Ngôn ngữ | Kiểu |
|---|---|
| Python | dict |
| Java | HashMap |
| JavaScript | Map / object |
| C++ | std::unordered_map |
| Go | map |
Ví dụ minh họa
Chèn các cặp ("cat", 3), ("dog", 5), ("cat", 9), ("emu", 7) vào một map có 8 bucket, dùng hash(key) % 8. Giả sử hash("cat") % 8 = 2, hash("dog") % 8 = 5, hash("emu") % 8 = 2:
| Bước | Cấu trúc | Thao tác |
|---|---|---|
Put ("cat", 3) | bucket 2: [("cat", 3)] | Băm vào bucket 2; chuỗi đang rỗng, nên thêm cặp vào. |
Put ("dog", 5) | bucket 2: [("cat", 3)], bucket 5: [("dog", 5)] | Băm vào bucket 5; chuỗi đang rỗng, nên thêm cặp vào. |
Put ("cat", 9) | bucket 2: [("cat", 9)], bucket 5: [("dog", 5)] | Băm vào bucket 2; khóa "cat" đã có trong chuỗi, nên cập nhật giá trị thành 9. |
Put ("emu", 7) | bucket 2: [("cat", 9), ("emu", 7)], bucket 5: [("dog", 5)] | Băm vào bucket 2; xung đột với "cat", không tìm thấy khóa, nên thêm cặp vào. |
Get "emu" | bucket 2: [("cat", 9), ("emu", 7)] | Băm vào bucket 2; duyệt chuỗi, bỏ qua "cat", khớp "emu", trả về 7. |
Khi nào nên dùng hash map
| Nên dùng khi | Tránh dùng khi |
|---|---|
Bạn cần tra cứu, chèn và xóa theo đúng khóa với chi phí trung bình O(1). | Bạn cần giữ khóa theo thứ tự đã sắp xếp hoặc truy vấn theo khoảng: hãy dùng BST cân bằng hoặc sorted map. |
| Khóa băm được và phép so sánh bằng được định nghĩa rõ ràng (chuỗi, số nguyên, tuple). | Khóa có thể thay đổi sau khi chèn, làm hỏng vị trí bucket của nó. |
| Bạn đang đếm, loại bỏ trùng lặp, lưu cache hoặc đánh chỉ mục theo một mã định danh. | Bạn cần bảo đảm O(log n) trong trường hợp xấu nhất thay vì cận trung bình phân bổ. |
| Thứ tự duyệt không quan trọng, hoặc một map giữ thứ tự chèn là đủ. | Bộ nhớ rất eo hẹp: bucket, khoảng trống dự phòng cho hệ số tải và con trỏ đều tốn thêm bộ nhớ. |
Code Hash Map
Một cách cài đặt Hash Map gọn gàng, chạy được ngay bằng Python, JavaScript, Java, C++, C. Chọn một ngôn ngữ, sao chép code hoặc mở sẵn trong Coddy Playground.
Code Hash Map bằng 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))Code Hash Map bằng 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(", "));Code Hash Map bằng 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}Code Hash Map bằng 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}Code Hash Map bằng 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}Câu hỏi thường gặp về hash map
Hash map và bảng băm khác nhau như thế nào?
Về cơ bản chúng là cùng một cấu trúc: một mảng các bucket được đánh địa chỉ bằng giá trị băm của khóa. Trong cách dùng thông thường, "bảng băm" (hash table) thường chỉ một tập các khóa hoặc kỹ thuật nói chung, còn "hash map" nhấn mạnh việc lưu cặp khóa-giá trị. Một số ngôn ngữ còn phân biệt về an toàn luồng (ví dụ Hashtable và HashMap trong Java), nhưng thuật toán cốt lõi là như nhau.
Độ phức tạp thời gian của hash map là bao nhiêu?
O(1) trung bình khi có hàm băm tốt và hệ số tải thấp. Trong trường hợp bất thường khi mọi khóa đều xung đột vào cùng một bucket, các thao tác suy biến thành O(n), vì vậy việc thay đổi kích thước và hàm băm tốt rất quan trọng.Hash map xử lý thế nào khi hai khóa băm vào cùng một bucket?
Nó giải quyết xung đột. Mô phỏng này dùng nối chuỗi: mỗi bucket giữ một danh sách nhỏ, và cặp mới có khóa băm vào đó sẽ được thêm vào danh sách. Khi tra cứu, map duyệt chuỗi ngắn đó để tìm khóa khớp. Kỹ thuật thay thế là địa chỉ mở (open addressing), dò tìm một ô khác trong mảng.
Hash map hay cây tìm kiếm nhị phân: nên dùng cái nào?
O(1). Dùng cây tìm kiếm nhị phân cân bằng (như TreeMap hoặc std::map) khi bạn cần khóa theo thứ tự đã sắp xếp, truy vấn theo khoảng hoặc tìm phần tử liền trước/liền sau, với chi phí O(log n). BST đánh đổi một chút tốc độ để có bảo đảm về thứ tự mà hash map không cung cấp được.Hệ số tải là gì và vì sao nó kích hoạt việc thay đổi kích thước?
0.75. Thay đổi kích thước tốn O(n) nhưng hiếm khi xảy ra, nên chi phí được phân bổ đều và các thao tác trung bình vẫn là O(1).Có thể dùng đối tượng thay đổi được như list làm khóa của hash map không?
TypeError: unhashable type với một list. Nếu bạn thay đổi khóa sau khi chèn, giá trị băm của nó đổi theo và map không còn tìm thấy nó trong đúng bucket, khiến mục đó bị mất mà không có thông báo nào. Hãy dùng khóa bất biến như chuỗi, số hoặc tuple.