Hash table

Mảng băm

Mảng băm là một cấu trúc dữ liệu ánh xạ từ khóa tới dữ liệu. Trong mảng băm các khóa luôn luôn khác nhau. Hàm băm là là hàm số chuyển từ khóa thành giá trị băm tương ứng trong mảng lưu trữ các giá trị cần tìm.
Trong trường hợp lý tưởng thì một hàm băm sẽ luôn chuyển đổi khóa khác nhau thành các giá trị băm khác nhau. Tuy nhiên trong thực tế, các thuật toán băm luôn cung cấp cách giải quyết khi các khóa khác nhau lại băm về cùng một giá trị (hay còn gọi là va chạm băm).

Khi bảng băm có kích thước đủ lớn, thời gian trung bình để tra cứu một phần tử là hằng số O(1), không phụ thuộc vào số lượng phần tử trong bảng băm.

Ví dụ

Sau đây là cách cài đặt mảng băm đơn giản trong C. Ở ví dụ này, chúng ta có danh sách mảng với dữ liệu cần thêm vào bảng băm bao gồm số điện thoại (6 số cuối) và tên. Mảng băm là một mảng lưu các 10 địa chỉ. Hàm băm của chúng ta là lấy số điện thoại chuyển thành số nguyên rồi chia lấy dư cho 11.

Số điện thoại Tên Giá trị băm
124565 Tung 1
672922 Vuong 8
926506 Hoa 9
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct Data {
  int phoneNumber;
  char name[10];
} Data;

typedef struct HashTable {
  Data** dataArray;
} HashTable;

int main() {
  const int SIZE_ARRAY = 10;
  HashTable ht;

  // cấp phát bộ nhớ
  ht.dataArray = (Data**) malloc(sizeof(Data*) * SIZE_ARRAY);

  // khởi tạo giá trị NULL
  for (int i = 0; i < SIZE_ARRAY; i++) {
    ht.dataArray[i] = NULL;
  }

  Data person1 = { .phoneNumber = 124565, .name = "Tung" };
  Data person2 = { .phoneNumber = 672922, .name = "Vuong" };
  Data person3 = { .phoneNumber = 926505, .name = "Hoa" };
}

Hàm thêm giá trị vào hàm băm

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void addToHashTable(HashTable *hashTable, Data* data) {
  int key = data->phoneNumber % 11; // hàm băm
  
  Data* node = (Data*) malloc(sizeof(Data));
  node->phoneNumber = data->phoneNumber; // copy phoneNumber
  strcpy(node->name, data->name); // copy name

  hashTable->dataArray[key] = node;
}

int main() {
  addToHashTable(&ht, &person1);
  addToHashTable(&ht, &person2);
  addToHashTable(&ht, &person3);
}

Kiểm tra giá trị với key

Với hàm băm và key ta tìm được giá trị băm. Sử dụng giá trị băm và key ta có thể tìm được giá trị cần tìm.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Data* findData(HashTable *hashTable, int phoneNumber) {
  int key = phoneNumber % 11;
  Data* node = hashTable->dataArray[key];
  if (node == NULL) {
    return NULL;
  }
  if (node->phoneNumber == phoneNumber) {
    return node;
  }
}

Xóa một giá trị trong bảng băm

Sau đây là hàm một giá trị trong bảng băm dựa theo key.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
void deleleData(HashTable* hashTable, int phoneNumber) {
  int key = phoneNumber % 11;
  Data* node = hashTable->dataArray[key];
  if (node == NULL) {
    return;
  }
  if (node->phoneNumber == phoneNumber) {
    free(node);
    hashTable->dataArray[key] = NULL;
  }
}

Giải quyết va chạm

Có hai cách giải quyết va chạm đó là phương pháp kết nối và phương pháp địa chỉ mở. Phương pháp kết nối lưu các giá trị vào một danh sách liên kết. Còn phương pháp địa chỉ mở thì các cặp vạ chạm khóa-giá trị được lưu trong bảng ở một chỗ khác. Một thuật toán sẽ tìm vị trí tiếp theo của của khóa va chạm này.

Trong ví dụ trên các khóa khi được băm ra sẽ có các giá trị khác nhau. Giờ ta sẽ giải quyết trường hợp va chạm với ví dụ trên khi thêm hai dữ liệu mới như sau:

Số điện thoại Tên Giá trị băm
124565 Tung 1
672922 Vuong 8
926506 Hoa 9
284657 Mai 10
228306 Linh 1

Có va chạm với số điện thoại 124565, tên Tung ở trên khi cùng giá trị băm là 1. Cách cài đặt giải quyết va chạm khi sử dụng danh sách liên kết như sau:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
typedef struct Data {
  int phoneNumber;
  char name[10];
  struct Data* next;
} Data;

void addToHashTable(HashTable *hashTable, Data* data) {
  int key = data->phoneNumber % 11; // hàm băm
  Data* newData = malloc(sizeof(Data)); // tạo dữ liệu mới
  newData->phoneNumber = data->phoneNumber; // copy phoneNumber
  strcpy(newData->name, data->name); // copy name

  // tạo danh sách liên kết
  newData->next = hashTable->dataArray[key];
  hashTable->dataArray[key] = newData;
}

Data* findData(HashTable *hashTable, int phoneNumber) {
  int key = phoneNumber % 11; // hàm băm
  Data* data = hashTable->dataArray[key]; // lấy data

  // lặp qua danh sách liên kết
  while(data != NULL) {

    // nếu trùng phoneNumber thì return
    if (data->phoneNumber == phoneNumber) { 
      return data;
    }
    data = data->next;
  }
  return NULL;
}

void deleleData(HashTable* hashTable, int phoneNumber) {
  int key = phoneNumber % 11;
  Data* node = hashTable->dataArray[key];
  if (node == NULL) {
    return;
  }
  if (node->phoneNumber == phoneNumber) {
    free(node);
    hashTable->dataArray[key] = NULL;
    return;
  }

  Data* preNode = node;
  preNode = node;
  node = node->next;
  // lặp qua danh sách liên kết
  while(node != NULL) {
    // nếu trùng phoneNumber thì xóa phần tử đó
    if (node->phoneNumber == phoneNumber) { 
      preNode->next = node->next;
      free(node);
      return;
    }
    preNode = node;
    node = node->next;
  }
}

Trên đây là ví dụ để minh họa việc sử dụng hashmap. Trong thực tế chúng ta sẽ thường sử dụng hashmap qua các thư viện được viết sẵn.