map vs. hash_map di C ++

117

Saya punya pertanyaan dengan hash_mapdan mapdalam C ++. Saya mengerti bahwa mapada di STL, tetapi hash_mapbukan standar. Apa perbedaan keduanya?

skydoor
sumber

Jawaban:

133

Mereka diterapkan dengan cara yang sangat berbeda.

hash_map( unordered_mapdi TR1 dan Boost; gunakan itu sebagai gantinya) gunakan tabel hash di mana kuncinya di-hash ke slot di tabel dan nilainya disimpan dalam daftar yang terkait dengan kunci itu.

map diimplementasikan sebagai pohon pencarian biner yang seimbang (biasanya pohon merah / hitam).

An unordered_mapharus memberikan kinerja yang sedikit lebih baik untuk mengakses elemen koleksi yang diketahui, tetapi a mapakan memiliki karakteristik tambahan yang berguna (misalnya disimpan dalam urutan yang diurutkan, yang memungkinkan traversal dari awal hingga akhir). unordered_mapakan lebih cepat saat memasukkan dan menghapus daripada a map.

Joe
sumber
7
Saya tidak sepenuhnya setuju dengan Anda tentang kinerja. Kinerja dipengaruhi oleh sejumlah parameter dan saya akan memarahi setiap programmer yang menggunakan unordered_map hanya untuk 10 entri karena "Ini lebih cepat". Khawatir tentang antarmuka / fungsionalitas dulu, kinerja nanti.
Matthieu M.
24
Ya, ada baiknya jika Anda memahami masalah Anda. Hingga urutan besaran tertentu, ini mungkin merupakan kinerja pencucian, tetapi penting untuk memahami karakteristik kinerja kedua wadah karena mereka menyimpang dengan cara yang berbeda seiring volume data menjadi lebih besar.
Joe
7
Menariknya, saya baru saja menukar std :: map dengan boost :: unordered_map dalam aplikasi tempat saya melakukan banyak pencarian acak, tetapi juga mengulang semua kunci di peta. Saya menghemat banyak waktu dalam pencarian, tetapi mendapatkannya kembali melalui iterasi, jadi saya beralih kembali ke peta dan mencari cara lain untuk meningkatkan kinerja aplikasi.
Erik Garrison
4
@ErikGarrison Jika Anda menggunakan akses acak dan iterasi lebih banyak daripada Anda menyisipkan dan menghapus elemen, Anda dapat memiliki objek Anda di dalam pohon dan hash_map (dengan menyimpan pointer, atau lebih baik lagi dalam shared_ptr, ke objek yang sama di keduanya di jika Anda menggunakan contoh aktual). Anda kemudian akan memiliki waktu akses O (1) waktu melalui hash_map dan waktu iterasi O (n) melalui peta. Tentu saja, Anda harus ingat untuk menambah dan menghapus petunjuk dari keduanya setiap saat. Anda dapat dengan mudah menulis kelas penampung khusus yang (mungkin juga template) yang akan merangkum perilaku ini untuk Anda.
sprite
2
@ErikGarrison Tentu saja, jika Anda mencoba metode ini, Anda akan membayar dengan sedikit ruang tambahan. Namun, karena Anda akan menggunakan pointer, itu seharusnya tidak terlalu berlebihan. Jika Anda benar-benar ingin, Anda bisa berlebihan, dan menulis implementasi Anda sendiri dari AVL dan menggunakan penunjuk node sebagai tipe data Anda di hash_map, ini akan memberi Anda O (1) waktu akses ke node di pohon dari mana Anda akan dapat melakukan iterasi secara linier ke mana pun yang Anda butuhkan. Tentu saja ini akan melibatkan sedikit pengkodean dan saya tidak yakin itu akan terbayar kecuali Anda perlu banyak iterasi dari dan ke lokasi akses acak.
sprite
35

hash_mapadalah ekstensi umum yang disediakan oleh banyak implementasi perpustakaan. Itulah mengapa itu diganti namanya unordered_mapketika ditambahkan ke standar C ++ sebagai bagian dari TR1. map umumnya diimplementasikan dengan pohon biner yang seimbang seperti pohon merah-hitam (implementasinya bervariasi tentunya). hash_mapdan unordered_mapumumnya diimplementasikan dengan tabel hash. Dengan demikian ketertiban tidak terjaga. unordered_mapmasukkan / hapus / kueri akan menjadi O (1) (waktu konstan) di mana peta akan menjadi O (log n) di mana n adalah jumlah item dalam struktur data. Jadi unordered_maplebih cepat, dan jika Anda tidak peduli dengan urutan item harus lebih disukai map. Terkadang Anda ingin menjaga ketertiban (dipesan oleh kunci) dan untuk itu mapakan menjadi pilihan.

janglin
sumber
9
Saya akan menunjukkan bahwa hashmap memiliki kasus akses terburuk O (N) ketika tabrakan mungkin terjadi (hash fcn buruk, faktor pemuatan terlalu tinggi, dll)
KitsuneYMG
Hashmap yang baik memiliki biaya yang diharapkan sebesar O (1), namun tidak dijamin demikian. Hashmaps yang buruk mungkin memiliki biaya yang diharapkan bukan O (1).
jelas pada
14

Beberapa perbedaan utama terletak pada persyaratan kompleksitas.

  • A mapmembutuhkan O(log(N))waktu untuk operasi penyisipan dan pencarian, karena ini diimplementasikan sebagai struktur data Pohon Merah-Hitam .

  • An unordered_mapmemerlukan waktu 'rata-rata' O(1)untuk penyisipan dan penemuan, tetapi diizinkan untuk memiliki waktu kasus terburuk O(N). Ini karena diimplementasikan menggunakan struktur data Tabel Hash .

Jadi, biasanya, unordered_mapakan lebih cepat, tetapi tergantung pada tombol dan fungsi hash yang Anda simpan, bisa menjadi jauh lebih buruk.

R Samuel Klatchko
sumber
4

Spesifikasi C ++ tidak mengatakan dengan tepat algoritme apa yang harus Anda gunakan untuk penampung STL. Namun, hal itu menempatkan batasan tertentu pada kinerja mereka, yang mengesampingkan penggunaan tabel hash untuk mapdan wadah asosiatif lainnya. (Mereka paling sering diterapkan dengan pohon merah / hitam.) Batasan ini memerlukan kinerja kasus terburuk yang lebih baik untuk penampung ini daripada yang dapat diberikan tabel hash.

Banyak orang benar-benar menginginkan tabel hash, bagaimanapun, jadi wadah asosiatif STL berbasis hash telah menjadi ekstensi umum selama bertahun-tahun. Akibatnya, mereka menambahkan unordered_mapdan menambahkannya ke versi standar C ++.

Warren Young
sumber
Itu sebenarnya ditambahkan di TR1 (std :: tr1 :: unordered_map), bukan C ++ 0x
Terry Mahaffey
Saya kira alasan mapumumnya pohon yang seimbang adalah karena digunakan operator<()sebagai alat untuk menentukan lokasi.
KitsuneYMG
@ kts: Apakah ada implementasi STL yang benar-benar menggunakan pohon-B?
bk1e
Secara teknis semua pohon pencarian biner adalah b-pohon (1-2 pohon). Karena itu, saya tidak tahu ada STL yang menggunakan apa pun selain merah / hitam
KitsuneYMG
@ bk1e B-tree yang "tepat" sangat berguna dalam database, di mana Anda menginginkan node pohon "gemuk" yang selaras dengan halaman disk. OTOH, ini tidak begitu berguna dalam model memori "datar" yang digunakan dalam program "normal" - semua implementasi STL yang saya ketahui menggunakan pohon merah-hitam.
Branko Dimitrijevic
3

mapdiimplementasikan dari balanced binary search tree(biasanya a rb_tree), karena semua anggota di balanced binary search treediurutkan begitu juga dengan map;

hash_mapdiimplementasikan dari. hashtableKarena semua hashtableanggota dalam hash_map(unordered_map)tidak diurutkan sehingga anggota dalam tidak diurutkan.

hash_mapbukan pustaka standar c ++, tetapi sekarang diubah namanya menjadi unordered_map(Anda dapat menganggapnya berganti nama) dan menjadi pustaka standar c ++ sejak c ++ 11 lihat pertanyaan ini Perbedaan antara hash_map dan unordered_map? untuk lebih detail.

Di bawah ini saya akan memberikan beberapa antarmuka inti dari kode sumber tentang bagaimana peta tipe dua diimplementasikan.

peta:

Kode di bawah ini hanya untuk menunjukkan bahwa, map hanyalah pembungkus dari sebuah balanced binary search tree, hampir semua fungsinya hanya memanggil balanced binary search treefungsi tersebut.

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_mapdiimplementasikan dari hashtableyang strukturnya agak seperti ini:

masukkan deskripsi gambar di sini

Pada kode di bawah ini, saya akan memberikan bagian utama dari hashtable, dan kemudian memberikan 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);

};

Seperti map'shanya anggota rb_tree, hash_map'ssatu - satunya anggota hashtable. Itu kode utama seperti di bawah ini:

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);
        };

};

Gambar di bawah ini menunjukkan ketika hash_map memiliki 53 keranjang, dan memasukkan beberapa nilai, struktur internalnya.

masukkan deskripsi gambar di sini

Gambar di bawah ini menunjukkan beberapa perbedaan antara map dan hash_map (unordered_map), gambar berasal dari Bagaimana memilih antara map dan unordered_map? :

masukkan deskripsi gambar di sini

Jayhello
sumber
1

Saya tidak tahu apa yang memberi, tetapi, hash_map membutuhkan lebih dari 20 detik untuk menghapus () 150K kunci integer unsigned dan nilai float. Saya hanya menjalankan dan membaca kode orang lain.

Beginilah cara menyertakan hash_map.

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

Saya membaca ini di sini https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

mengatakan bahwa clear () adalah urutan O (N). Bagi saya, itu sangat aneh, tapi begitulah adanya.

BoBoDev
sumber