map so với hash_map trong C ++


117

Tôi có một câu hỏi với hash_mapmaptrong C ++. Tôi hiểu rằng đó maplà trong STL, nhưng hash_mapkhông phải là một tiêu chuẩn. Sự khác biệt giữa hai là gì?

Câu trả lời:


133

Chúng được thực hiện theo những cách rất khác nhau.

hash_map( unordered_maptrong TR1 và Boost; sử dụng chúng thay thế) sử dụng bảng băm trong đó khóa được băm vào một vị trí trong bảng và giá trị được lưu trữ trong danh sách gắn với khóa đó.

map được thực hiện dưới dạng cây tìm kiếm nhị phân cân bằng (thường là cây đỏ / đen).

An unordered_mapsẽ cho hiệu suất tốt hơn một chút để truy cập các phần tử đã biết của tập hợp, nhưng a mapsẽ có các đặc điểm hữu ích bổ sung (ví dụ: nó được lưu trữ theo thứ tự được sắp xếp, cho phép duyệt từ đầu đến cuối). unordered_mapsẽ nhanh hơn khi chèn và xóa so với a map.


7
Tôi không hoàn toàn đồng ý với bạn về hiệu suất. Hiệu suất bị ảnh hưởng bởi một số tham số và tôi sẽ mắng bất kỳ lập trình viên nào sử dụng bản đồ không có thứ tự cho chỉ 10 mục nhập vì "Nó nhanh hơn". Lo lắng về giao diện / chức năng trước, hiệu suất sau.
Matthieu M.

24
Vâng, có, sẽ hữu ích nếu bạn hiểu vấn đề của mình. Lên đến một số thứ tự cường độ nhất định, nó có thể là một hiệu suất rửa khôn ngoan, nhưng điều quan trọng là phải hiểu các đặc tính hiệu suất của cả hai vùng chứa vì chúng lệch theo những cách khác nhau khi khối lượng dữ liệu lớn hơn.
Joe

7
Điều thú vị là tôi vừa hoán đổi std :: map với boost :: unardered_map trong một ứng dụng mà tôi thực hiện rất nhiều tra cứu ngẫu nhiên, nhưng cũng lặp lại trên tất cả các phím trong bản đồ. Tôi đã tiết kiệm được một lượng lớn thời gian trong việc tra cứu, nhưng đã lấy lại được thông qua các lần lặp lại, vì vậy tôi đã quay lại bản đồ và đang tìm các cách khác để cải thiện hiệu suất ứng dụng.
Erik Garrison

4
@ErikGarrison Nếu bạn sử dụng quyền truy cập và lặp lại ngẫu nhiên nhiều hơn là chèn và xóa các phần tử, bạn có thể có các đối tượng của mình trong cả cây và hash_map (bằng cách lưu trữ một con trỏ, hoặc tốt hơn là shared_ptr, cho các đối tượng giống nhau trong cả hai trường hợp bạn đang sử dụng các phiên bản thực tế). Sau đó, bạn sẽ có thời gian truy cập O (1) thời gian thông qua bản đồ băm và thời gian lặp O (n) qua bản đồ. Tất nhiên, bạn phải nhớ thêm và xóa các con trỏ từ cả hai. Bạn có thể dễ dàng viết một lớp vùng chứa tùy chỉnh (có thể là cả khuôn mẫu nữa) sẽ đóng gói hành vi này cho bạn.
sprite

2
@ErikGarrison Tất nhiên, nếu bạn thử phương pháp này, bạn sẽ phải trả bằng một không gian bổ sung nhỏ. Tuy nhiên, vì bạn đang sử dụng con trỏ, điều đó không nên quá nhiều. Nếu bạn thực sự muốn, bạn có thể viết quá mức và viết triển khai AVL của riêng bạn và sử dụng con trỏ nút làm kiểu dữ liệu của bạn trong hash_map, điều này sẽ cung cấp cho bạn quyền truy cập O (1) thời gian vào một nút trong cây mà từ đó bạn sẽ có thể lặp lại tuyến tính đến bất cứ nơi nào bạn cần. Tất nhiên điều này sẽ liên quan đến khá nhiều mã hóa và tôi không chắc nó sẽ thành công trừ khi bạn cần lặp lại nhiều lần từ và đến các vị trí truy cập ngẫu nhiên.
sprite

35

hash_maplà một phần mở rộng phổ biến được cung cấp bởi nhiều triển khai thư viện. Đó chính xác là lý do tại sao nó được đổi tên thành unordered_mapkhi nó được thêm vào tiêu chuẩn C ++ như một phần của TR1. bản đồ thường được thực hiện với một cây nhị phân cân bằng giống như một cây đỏ đen (tất nhiên là các cách triển khai khác nhau). hash_mapunordered_mapthường được triển khai với bảng băm. Do đó trật tự không được duy trì. unordered_mapinsert / delete / query sẽ là O (1) (thời gian không đổi) trong đó map sẽ là O (log n) với n là số lượng mục trong cấu trúc dữ liệu. Như vậy unordered_maplà nhanh hơn, và nếu bạn không quan tâm đến thứ tự của các mặt hàng nên được ưu tiên hơn map. Đôi khi bạn muốn duy trì trật tự (sắp xếp theo khóa) và đó mapsẽ là sự lựa chọn.


9
Tôi sẽ chỉ ra rằng hashmap có truy cập trường hợp tồi tệ nhất của O (N) khi va chạm có khả năng (fcn băm xấu, tải yếu tố quá cao, vv)
KitsuneYMG

Một hashmap tốt có chi phí dự kiến ​​là O (1), nó không được đảm bảo là như vậy. Các bản đồ băm xấu có thể có chi phí dự kiến ​​không phải là O (1).
Rõ ràng hơn

14

Một số khác biệt chính là yêu cầu về độ phức tạp.

  • A mapyêu cầu O(log(N))thời gian cho các thao tác chèn và tìm, vì nó được triển khai dưới dạng cấu trúc dữ liệu Cây Đỏ-Đen .

  • An unordered_mapyêu cầu thời gian 'trung bình' O(1)cho các lần chèn và tìm, nhưng được phép có thời gian trong trường hợp xấu nhất là O(N). Điều này là do nó được triển khai bằng cấu trúc dữ liệu Bảng băm .

Vì vậy, thông thường, unordered_mapsẽ nhanh hơn, nhưng tùy thuộc vào các phím và hàm băm bạn lưu trữ, có thể trở nên tồi tệ hơn nhiều.


4

Thông số C ++ không nói chính xác thuật toán bạn phải sử dụng cho các vùng chứa STL. Tuy nhiên, nó đặt ra một số ràng buộc nhất định đối với hiệu suất của chúng, điều này loại trừ việc sử dụng bảng băm mapvà các vùng chứa kết hợp khác. (Chúng thường được triển khai nhất với cây đỏ / đen.) Những ràng buộc này yêu cầu hiệu suất trong trường hợp xấu nhất cho các vùng chứa này tốt hơn so với bảng băm có thể cung cấp.

Tuy nhiên, nhiều người thực sự muốn có bảng băm, vì vậy các thùng chứa liên kết STL dựa trên băm đã là một phần mở rộng phổ biến trong nhiều năm. Do đó, họ đã thêm unordered_mapvà như vậy vào các phiên bản sau của tiêu chuẩn C ++.


Nó thực sự đã được thêm vào TR1 (std :: tr1 :: unsrdered_map), không phải C ++ 0x
Terry Mahaffey

Tôi nghĩ rằng lý do mapnói chung là một ngôi nhà cân bằng là do việc sử dụng operator<()làm phương tiện xác định vị trí.
KitsuneYMG

@kts: Có bất kỳ triển khai STL nào thực sự sử dụng cây B không?
bk1e

Về mặt kỹ thuật, tất cả các cây tìm kiếm nhị phân đều là cây b (cây 1-2). Điều đó đang được nói, tôi không biết bất kỳ STL nào sử dụng bất kỳ thứ gì khác ngoài màu đỏ / đen
KitsuneYMG

@ bk1e Cây B "thích hợp" đặc biệt hữu ích trong cơ sở dữ liệu, nơi bạn muốn các nút cây "béo" phù hợp với các trang đĩa. OTOH, điều này không quá hữu ích trong mô hình bộ nhớ "phẳng" được sử dụng trong các chương trình "bình thường" - tất cả các triển khai STL mà tôi biết đều sử dụng cây đỏ-đen.
Branko Dimitrijevic

3

mapđược thực hiện từ balanced binary search tree(thường là a rb_tree), vì tất cả các thành viên trong balanced binary search treeđều được sắp xếp nên bản đồ cũng vậy;

hash_mapđược triển khai từ hashtable. Vì tất cả thành viên trong hashtablekhông được sắp xếp nên các thành viên trong hash_map(unordered_map)không được sắp xếp.

hash_mapkhông phải là thư viện chuẩn c ++, nhưng bây giờ nó được đổi tên thành unordered_map(bạn có thể nghĩ nó đã được đổi tên) và trở thành thư viện chuẩn c ++ kể từ c ++ 11, hãy xem câu hỏi này Sự khác biệt giữa hash_map và unardered_map? để biết thêm chi tiết.

Dưới đây tôi sẽ đưa ra một số giao diện cốt lõi từ mã nguồn về cách thực hiện hai loại bản đồ.

bản đồ:

Đoạn mã dưới đây chỉ để cho thấy rằng, bản đồ chỉ là một trình bao bọc của một balanced binary search tree, hầu như tất cả những gì hàm của nó chỉ là gọi balanced binary search treehàm.

template <typename Key, typename Value, class Compare = std::less<Key>>
class map{
    // used for rb_tree to sort
    typedef Key    key_type;

    // rb_tree node value
    typedef std::pair<key_type, value_type> value_type;

    typedef Compare key_compare;

    // as to map, Key is used for sort, Value used for store value
    typedef rb_tree<key_type, value_type, key_compare> rep_type;

    // the only member value of map (it's  rb_tree)
    rep_type t;
};

// one construct function
template<typename InputIterator>
map(InputIterator first, InputIterator last):t(Compare()){
        // use rb_tree to insert value(just insert unique value)
        t.insert_unique(first, last);
}

// insert function, just use tb_tree insert_unique function
//and only insert unique value
//rb_tree insertion time is : log(n)+rebalance
// so map's  insertion time is also : log(n)+rebalance 
typedef typename rep_type::const_iterator iterator;
std::pair<iterator, bool> insert(const value_type& v){
    return t.insert_unique(v);
};

hash_map:

hash_mapđược triển khai từ hashtablecó cấu trúc giống như sau:

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

Trong đoạn mã dưới đây, tôi sẽ đưa ra phần chính của hashtable, và sau đó đưa ra hash_map.

// used for node list
template<typename T>
struct __hashtable_node{
    T val;
    __hashtable_node* next;
};

template<typename Key, typename Value, typename HashFun>
class hashtable{
    public:
        typedef size_t   size_type;
        typedef HashFun  hasher;
        typedef Value    value_type;
        typedef Key      key_type;
    public:
        typedef __hashtable_node<value_type> node;

        // member data is buckets array(node* array)
        std::vector<node*> buckets;
        size_type num_elements;

        public:
            // insert only unique value
            std::pair<iterator, bool> insert_unique(const value_type& obj);

};

Giống như map'sthành viên duy nhất là rb_tree, hash_map'sthành viên duy nhất là hashtable. Mã chính của nó như sau:

template<typename Key, typename Value, class HashFun = std::hash<Key>>
class hash_map{
    private:
        typedef hashtable<Key, Value, HashFun> ht;

        // member data is hash_table
        ht rep;

    public:
        // 100 buckets by default
        // it may not be 100(in this just for simplify)
        hash_map():rep(100){};

        // like the above map's insert function just invoke rb_tree unique function
        // hash_map, insert function just invoke hashtable's unique insert function
        std::pair<iterator, bool> insert(const Value& v){
                return t.insert_unique(v);
        };

};

Hình ảnh dưới đây cho thấy khi một hash_map có 53 nhóm và chèn một số giá trị, đó là cấu trúc bên trong.

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

Hình ảnh dưới đây cho thấy một số khác biệt giữa bản đồ và hash_map (bản đồ không có thứ tự), hình ảnh đến từ Cách chọn giữa bản đồ và bản đồ không có thứ tự? :

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


1

Tôi không biết điều gì mang lại, nhưng, hash_map mất hơn 20 giây để xóa () 150K khóa số nguyên không dấu và giá trị float. Tôi chỉ đang chạy và đọc mã của người khác.

Đây là cách nó bao gồm hash_map.

#include "StdAfx.h"
#include <hash_map>

Tôi đọc cái này ở đây https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

nói rằng clear () là bậc của O (N). Điều đó đối với tôi, rất lạ, nhưng, đó là cách của nó.

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.