Cách tốt nhất để sử dụng HashMap trong C ++ là gì?


174

Tôi biết rằng STL có API HashMap, nhưng tôi không thể tìm thấy bất kỳ tài liệu tốt và kỹ lưỡng nào với các ví dụ hay về điều này.

Bất kỳ ví dụ tốt sẽ được đánh giá cao.


Bạn đang hỏi về hash_map C ++ 1x, hoặc về std :: map?
philant

2
Tôi muốn một cái gì đó giống như java.util.HashMap trong C ++ và cách thức ổn định để làm điều đó nếu có. Khác thư viện phi tiêu chuẩn tốt nhất. Các nhà phát triển C ++ thường sử dụng gì khi họ cần HashMap?
dùng855

Câu trả lời:


237

Thư viện tiêu chuẩn bao gồm các container ( std::mapstd::unordered_map) bản đồ được sắp xếp theo thứ tự . Trong một bản đồ được sắp xếp, các phần tử được sắp xếp theo khóa, chèn và truy cập nằm trong O (log n) . Thông thường thư viện tiêu chuẩn bên trong sử dụng cây đen đỏ cho các bản đồ được đặt hàng. Nhưng đây chỉ là một chi tiết thực hiện. Trong một bản đồ không có thứ tự chèn và truy cập nằm trong O (1). Nó chỉ là một tên khác cho một hashtable.

Một ví dụ với (đã ra lệnh) std::map:

#include <map>
#include <iostream>
#include <cassert>

int main(int argc, char **argv)
{
  std::map<std::string, int> m;
  m["hello"] = 23;
  // check if key is present
  if (m.find("world") != m.end())
    std::cout << "map contains key world!\n";
  // retrieve
  std::cout << m["hello"] << '\n';
  std::map<std::string, int>::iterator i = m.find("hello");
  assert(i != m.end());
  std::cout << "Key: " << i->first << " Value: " << i->second << '\n';
  return 0;
}

Đầu ra:

23
Key: xin chào Giá trị: 23

Nếu bạn cần đặt hàng trong thùng chứa của mình và ổn với thời gian chạy O (log n) thì chỉ cần sử dụng std::map.

Mặt khác, nếu bạn thực sự cần một bảng băm (O (1) chèn / truy cập), hãy kiểm tra std::unordered_map, có std::mapAPI tương tự (ví dụ trong ví dụ trên bạn chỉ cần tìm kiếm và thay thế mapbằng unordered_map).

Các unordered_mapthùng chứa đã được giới thiệu với C ++ 11 tiêu chuẩn sửa đổi. Do đó, tùy thuộc vào trình biên dịch của bạn, bạn phải kích hoạt các tính năng của C ++ 11 (ví dụ: khi sử dụng GCC 4.8, bạn phải thêm -std=c++11vào CXXFLAGS).

Ngay cả trước khi phát hành C ++ 11, GCC đã hỗ trợ unordered_map- trong không gian tên std::tr1. Vì vậy, đối với các trình biên dịch GCC cũ, bạn có thể thử sử dụng nó như thế này:

#include <tr1/unordered_map>

std::tr1::unordered_map<std::string, int> m;

Nó cũng là một phần của boost, tức là bạn có thể sử dụng tiêu đề boost tương ứng để có tính di động tốt hơn.


1
Trong khi các thư viện tiêu chuẩn không có một container băm dựa trên bảng, hầu hết các triển khai bao gồm hash_maptừ STL SGI trong một số hình thức này hay cách khác.
James McNellis

@JamesMcNellis được khuyên là unordered_map hoặc hash_map để triển khai HashMap
Shameel Mohamed

2
@ShameelMohamed, 2017, tức là 6 năm sau C ++ 11, thật khó để tìm thấy một STL không cung cấp unordered_map. Vì vậy, không có lý do để xem xét các tiêu chuẩn không hash_map.
maxschlepzig

30

A hash_maplà phiên bản cũ hơn, không được chuẩn hóa cho mục đích tiêu chuẩn hóa được gọi là unordered_map(ban đầu trong TR1 và được bao gồm trong tiêu chuẩn kể từ C ++ 11). Như tên của nó, nó khác với std::mapchủ yếu là không có thứ tự - ví dụ, nếu bạn lặp qua bản đồ từ begin()đến end(), bạn nhận được các mục theo thứ tự theo khóa 1 , nhưng nếu bạn lặp qua unordered_maptừ begin()đến end(), bạn sẽ nhận được các mục trong nhiều hay ít tùy ý.

An unordered_mapthường được dự kiến ​​sẽ có độ phức tạp không đổi. Đó là, việc chèn, tra cứu, v.v., thường mất một khoảng thời gian cố định, bất kể có bao nhiêu mục trong bảng. Một std::mapsự phức tạp đó là logarit về số lượng vật phẩm được lưu trữ - có nghĩa là thời gian để chèn hoặc truy xuất một vật phẩm tăng lên, nhưng khá chậm , khi bản đồ phát triển lớn hơn. Ví dụ: nếu mất 1 micro giây để tra cứu một trong 1 triệu vật phẩm, thì bạn có thể mong đợi nó sẽ mất khoảng 2 micro giây để tra cứu một trong 2 triệu vật phẩm, 3 micro giây cho một trong 4 triệu vật phẩm, 4 micro giây cho một trong 8 triệu các mặt hàng, vv

Từ quan điểm thực tế, đó thực sự không phải là toàn bộ câu chuyện. Theo tự nhiên, một bảng băm đơn giản có kích thước cố định. Việc điều chỉnh nó theo các yêu cầu kích thước thay đổi cho một thùng chứa mục đích chung là không tầm thường. Do đó, các hoạt động (có khả năng) phát triển bảng (ví dụ: chèn) có khả năng tương đối chậm (nghĩa là hầu hết đều khá nhanh, nhưng theo định kỳ, một hoạt động sẽ chậm hơn nhiều). Tra cứu, không thể thay đổi kích thước của bảng, thường nhanh hơn nhiều. Do đó, hầu hết các bảng dựa trên hàm băm có xu hướng tốt nhất khi bạn thực hiện nhiều tra cứu so với số lần chèn. Đối với các tình huống bạn chèn nhiều dữ liệu, sau đó lặp qua bảng một lần để lấy kết quả (ví dụ: đếm số lượng từ duy nhất trong một tệp) có thể là mộtstd::map sẽ nhanh như vậy, và thậm chí có thể nhanh hơn nữa (nhưng, một lần nữa, độ phức tạp tính toán là khác nhau, do đó cũng có thể phụ thuộc vào số lượng từ duy nhất trong tệp).


1std::less<T> Theo mặc định , thứ tự được xác định bởi tham số mẫu thứ ba khi bạn tạo bản đồ .


1
Tôi nhận ra rằng tôi sẽ đến 9 năm sau khi câu trả lời được đăng nhưng ... bạn có liên kết đến một tài liệu đề cập đến thực tế rằng một bản đồ không có thứ tự có thể thu nhỏ kích thước không? Thông thường, bộ sưu tập std chỉ phát triển. Ngoài ra, nếu bạn chèn nhiều dữ liệu nhưng biết trước nhiều hơn hoặc ít hơn bao nhiêu khóa bạn sẽ chèn, bạn có thể chỉ định kích thước của bản đồ khi tạo, về cơ bản sẽ vô hiệu hóa chi phí thay đổi kích thước (vì sẽ không có bất kỳ) .
Zonko

@Zonko: Xin lỗi, tôi đã không nhận thấy điều này khi được hỏi. Theo như tôi biết, một unordered_map không co lại, ngoại trừ phản ứng với việc gọi rehash. Khi bạn gọi rehash, bạn chỉ định kích thước cho bảng. Kích thước đó sẽ được sử dụng trừ khi làm như vậy sẽ vượt quá hệ số tải tối đa được chỉ định cho bảng (trong trường hợp đó, kích thước sẽ được tăng tự động để giữ hệ số tải trong giới hạn).
Jerry Coffin

22

Đây là một ví dụ đầy đủ và linh hoạt hơn mà không bỏ sót cần thiết bao gồm để tạo ra các lỗi biên dịch:

#include <iostream>
#include <unordered_map>

class Hashtable {
    std::unordered_map<const void *, const void *> htmap;

public:
    void put(const void *key, const void *value) {
            htmap[key] = value;
    }

    const void *get(const void *key) {
            return htmap[key];
    }

};

int main() {
    Hashtable ht;
    ht.put("Bob", "Dylan");
    int one = 1;
    ht.put("one", &one);
    std::cout << (char *)ht.get("Bob") << "; " << *(int *)ht.get("one");
}

Vẫn không đặc biệt hữu ích cho các khóa, trừ khi chúng được xác định trước là con trỏ, bởi vì giá trị khớp sẽ không làm được! (Tuy nhiên, vì tôi thường sử dụng chuỗi cho khóa, thay thế "chuỗi" cho "const void *" trong khai báo khóa sẽ giải quyết vấn đề này.)


4
Tôi phải nói rằng, ví dụ này là một thực tiễn rất tệ trong C ++. Bạn đang sử dụng một ngôn ngữ được gõ mạnh và phá hủy nó bằng cách sử dụng void*. Đối với người mới bắt đầu, không có lý do gì để bọc unordered_mapnó như là một phần của tiêu chuẩn và giảm khả năng duy trì mã. Tiếp theo, nếu nhấn mạnh vào gói nó, sử dụng templates. Đó chính xác là những gì họ đang làm.
Guyarad

Đánh máy mạnh? Bạn có thể có nghĩa là gõ tĩnh. Việc anh ta có thể đi từ const char ptr đến void lặng lẽ khiến C ++ tĩnh, nhưng không mạnh mẽ, đánh máy. Có các loại, nhưng trình biên dịch sẽ không nói gì trừ khi bạn bật một số cờ tối nghĩa mà rất có thể không tồn tại.
Sahsahae

6

Bằng chứng std::unordered_mapsử dụng bản đồ băm trong GCC stdlibc ++ 6.4

Điều này đã được đề cập tại: https://stackoverflow.com/a/3578247/895245 nhưng trong câu trả lời sau: Cấu trúc dữ liệu nào nằm trong std :: map trong C ++? Tôi đã cung cấp thêm bằng chứng về việc triển khai GCC stdlibc ++ 6.4 bằng cách:

  • Bước GDB gỡ lỗi vào lớp
  • phân tích đặc tính hiệu suất

Dưới đây là bản xem trước của biểu đồ đặc tính hiệu suất được mô tả trong câu trả lời đó:

nhập mô tả hình ảnh ở đây

Cách sử dụng lớp tùy chỉnh và hàm băm với unordered_map

Câu trả lời này đóng đinh nó: C ++ unordered_map sử dụng loại lớp tùy chỉnh làm khóa

Trích: bình đẳng:

struct Key
{
  std::string first;
  std::string second;
  int         third;

  bool operator==(const Key &other) const
  { return (first == other.first
            && second == other.second
            && third == other.third);
  }
};

Hàm băm:

namespace std {

  template <>
  struct hash<Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::size_t;
      using std::hash;
      using std::string;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<string>()(k.first)
               ^ (hash<string>()(k.second) << 1)) >> 1)
               ^ (hash<int>()(k.third) << 1);
    }
  };

}

0

Đối với những người trong chúng ta đang cố gắng tìm ra cách băm các lớp của chính mình trong khi vẫn sử dụng mẫu tiêu chuẩn, có một giải pháp đơn giản:

  1. Trong lớp học của bạn, bạn cần xác định một quá tải toán tử đẳng thức ==. Nếu bạn không biết cách thực hiện việc này, GeekforGeek có một hướng dẫn tuyệt vời https://www.geekforgeek.org/operator-overloading-c/

  2. Trong không gian tên tiêu chuẩn, khai báo một cấu trúc mẫu được gọi là hàm băm với tên lớp của bạn là loại (xem bên dưới). Tôi đã tìm thấy một blogpost tuyệt vời cũng cho thấy một ví dụ về tính toán băm bằng XOR và bẻ khóa, nhưng nó nằm ngoài phạm vi của câu hỏi này, nhưng nó cũng bao gồm các hướng dẫn chi tiết về cách thực hiện bằng cách sử dụng hàm băm cũng như https://prateekvjoshi.com/ 2014/06/05 / using-hash-function-in-c-for-user-định-class /

namespace std {

  template<>
  struct hash<my_type> {
    size_t operator()(const my_type& k) {
      // Do your hash function here
      ...
    }
  };

}
  1. Vì vậy, sau đó để triển khai hàm băm bằng hàm băm mới của bạn, bạn chỉ cần tạo std::maphoặc std::unordered_mapgiống như bạn thường làm và sử dụng my_typelàm khóa, thư viện chuẩn sẽ tự động sử dụng hàm băm mà bạn đã xác định trước (trong bước 2) để băm chìa khóa của bạn.
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.