Mengapa ada orang yang menggunakan set alih-alih unordered_set?

145

C ++ 0x memperkenalkan unordered_setyang tersedia di boostdan banyak tempat lain. Apa yang saya mengerti adalah bahwa unordered_settabel hash dengan O(1)kompleksitas pencarian. Di sisi lain, settidak lain adalah pohon dengan log(n)kompleksitas pencarian. Mengapa di bumi ada orang yang menggunakan setbukan unordered_set? yaitu apakah ada kebutuhan untuk setlagi?

AraK
sumber
22
Pertanyaan Anda pada dasarnya adalah menanyakan apakah perlu pohon lagi.
Vinko Vrsalovic
2
Saya pikir saya menyatakan dengan jelas di baris pertama, bahwa ini adalah pertanyaan yang bodoh. Saya kehilangan sesuatu dan sekarang saya mendapat jawaban :)
288
2
Alasan sebenarnya adalah bahwa segala sesuatunya tidak seperti B&W. Ada banyak warna abu-abu dan warna lain di antaranya. Anda harus ingat wadah ini adalah alat. Terkadang kinerja tidak penting dan kenyamanan jauh lebih berarti. Jika semua orang mencari solusi yang paling efisien, kami tidak akan pernah menggunakan C ++ (belum lagi Python) sejak awal dan terus menulis dan mengoptimalkan kode dalam bahasa mesin.
AturSams
(Kenapa ada orang yang menggunakan nama generik untuk implementasi / antarmuka dengan janji-janji di luar yang tersirat dengan nama itu, menciptakan situasi canggung untuk yang tanpa?)
greybeard

Jawaban:

219

Ketika, untuk seseorang yang ingin mengulangi item set, urutan penting.

bayangan bulan
sumber
Apakah dipesan sesuai dengan urutan penyisipan, atau sesuai dengan perbandingan nyata menggunakan operator < >?
SomethingSomething
2
Ini dipesan menggunakan std :: less secara default; Anda dapat mengesampingkan ini dan menyediakan operator perbandingan Anda sendiri. cplusplus.com/reference/set/set
moonshadow
Atau kadang-kadang ketika Anda hanya ingin mengulang, meskipun pesanannya tidak masalah.
mfnx
319

Set yang tidak dipesan harus membayar waktu akses rata-rata O (1) dalam beberapa cara:

  • setmenggunakan lebih sedikit memori daripada unordered_setmenyimpan jumlah elemen yang sama.
  • Untuk sejumlah kecil elemen , pencarian dalam setmungkin lebih cepat daripada pencarian dalam unordered_set.
  • Meskipun banyak operasi lebih cepat dalam kasus rata-rata untuk unordered_set, mereka sering dijamin memiliki lebih baik kompleksitas kasus terburuk untuk set(misalnya insert).
  • Itu set macam unsur-unsur berguna jika Anda ingin akses mereka dalam rangka.
  • Anda dapat leksikografi membandingkan berbeda sets dengan <, <=, >dan >=. unordered_setTidak diperlukan untuk mendukung operasi ini.

sth
sumber
9
+1, semua poin bagus. Orang-orang cenderung mengabaikan fakta bahwa hashtable memiliki O (1) waktu akses rata-rata , artinya kadang-kadang mereka dapat mengalami penundaan besar. Perbedaannya bisa penting untuk sistem waktu nyata.
j_random_hacker
Poin yang bagus, namun di sini ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) dinyatakan bahwa kita dapat membandingkan unordered_sets.
Michiel uit het Broek
5
Tentukan "sejumlah kecil elemen"
Sunjay Varma
4
@SunjayVarma biasanya 100 elemen adalah cut-off yang bagus di antara keduanya. Jika ragu, tidak ada yang dapat menggantikan kinerja pengujian keduanya dalam use case khusus Anda.
Nate
3
@MichieluithetBroek Hanya perbandingan kesetaraan yang dinyatakan, bukan dipesan ( <).
Lisisus
26

Setiap kali Anda lebih suka pohon ke tabel hash.

Misalnya, tabel hash adalah "O (n)" di kasus terburuk. O (1) adalah kasus rata-rata. Pohon adalah "O ( log n)" paling buruk.

Mehrdad Afshari
sumber
18
/ Seimbang / pohon adalah O (dalam n) dalam kasus terburuk. Anda dapat berakhir dengan O (n) pohon (daftar dasarnya terkait).
strager
5
Jika Anda bisa menulis fungsi hash yang cukup cerdas, Anda hampir selalu bisa mendapatkan O (1) dari hashtable. Jika Anda tidak dapat menulis fungsi hash seperti itu jika Anda perlu mengulangi "agar" di atas set Anda, maka Anda harus menggunakan pohon. Tetapi Anda tidak boleh menggunakan pohon karena Anda takut dengan "O (n) kinerja kasus terburuk."
Justin L.
6
stager: Untuk menjadi jagoan, ya. Namun, kita berbicara tentang set di C ++ yang biasanya diimplementasikan sebagai pohon pencarian biner seimbang . Kita harus menentukan operasi aktual untuk membicarakan kompleksitas. Dalam konteks ini jelas bahwa kita berbicara tentang pencarian.
Mehrdad Afshari
1
Justin L: Itu hanya satu alasan kamu lebih suka pohon. Inti dari jawaban saya adalah baris pertama. Setiap kali Anda lebih suka struktur data pohon ke tabel hash. Ada banyak kasus bahwa pohon lebih disukai daripada tabel hash. Tabel hash khususnya menyedot hal-hal seperti "rentang persimpangan."
Mehrdad Afshari
2
pohon hampir secara universal menerapkan pohon merah-hitam, pohon penyeimbang mandiri tingkat lanjut. Sebenarnya ada kasus di mana O (n) mencari dalam kasus yang lebih buruk tidak dapat diterima. Layanan web yang menyediakan dan antarmuka untuk menyimpan nilai pengguna tidak boleh menggunakan peta hash, karena pengguna jahat dapat secara efektif membuat DoS dengan menyimpan nilai yang dibuat khusus. Sistem kritis waktu yang sensitif juga mungkin tidak memungkinkan untuk pencarian O (n), kontrol lalu lintas udara dll. Meskipun secara umum Anda benar, gunakan peta hash secara default dan hanya alihkan versi pohon ketika Anda benar-benar membutuhkannya.
deft_code
14

Gunakan setel saat:

  1. Kami membutuhkan data yang dipesan (elemen yang berbeda).
  2. Kami harus mencetak / mengakses data (dalam urutan terurut).
  3. Kita membutuhkan pendahulu / penerus elemen.

Gunakan unordered_set saat:

  1. Kita perlu menyimpan satu set elemen yang berbeda dan tidak diperlukan pemesanan.
  2. Kami membutuhkan akses elemen tunggal yaitu tidak ada traversal.

Contoh:

set:

Input: 1, 8, 2, 5, 3, 9

Output: 1, 2, 3, 5, 8, 9

Unordered_set:

Input: 1, 8, 2, 5, 3, 9

Output: 9 3 1 8 2 5 (mungkin urutan ini, dipengaruhi oleh fungsi hash)

Terutama perbedaan:

masukkan deskripsi gambar di sini

Catatan: (dalam beberapa kasus setlebih mudah) misalnya menggunakan vectorsebagai kunci

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 

Alasan mengapa vector<int>bisa menjadi kunci setkarena vectormenimpa operator<.

Tetapi jika Anda menggunakan unordered_set<vector<int>>Anda harus membuat fungsi hash vector<int>, karena vektor tidak memiliki fungsi hash, jadi Anda harus mendefinisikan satu seperti:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}

Anda dapat melihat bahwa dalam beberapa kasus unordered_setlebih rumit.

Dikutip dari: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006

Jayhello
sumber
6

Karena std :: set adalah bagian dari Standard C ++ dan unordered_set tidak. C ++ 0x BUKAN standar, dan tidak juga Meningkatkan. Bagi banyak dari kita, portabilitas sangat penting, dan itu berarti berpegang teguh pada standar.


sumber
2
Jika saya memahaminya dengan benar, dia tidak bertanya mengapa orang saat ini masih menggunakan set. Dia memberi tahu dirinya sendiri tentang C ++ 0x.
Johannes Schaub - litb
2
Mungkin. Saya pikir semua orang tahu tabel hash dan pohon menyelesaikan masalah yang berbeda.
21
Nah, ini standar sekarang (hanya butuh beberapa tahun)
Clayton Hughes
6

Pertimbangkan algoritma Sweepline. Algoritma ini akan gagal total dengan tabel hash, tetapi bekerja dengan indah dengan pohon seimbang. Untuk memberi Anda contoh konkret dari algoritma sweepline pertimbangkan algoritma fortune. http://en.wikipedia.org/wiki/Fortune%27s_algorithm

ldog
sumber
1
Saya pikir referensi seperti itu terlalu kompleks mengingat pertanyaan itu. (Saya harus mencarinya)
hectorpal
3

Satu hal lagi, selain apa yang sudah disebutkan orang lain. Sementara kompleksitas amortisasi yang diharapkan untuk memasukkan elemen ke unordered_set adalah O (1), setiap sekarang dan kemudian akan membutuhkan O (n) karena tabel hash perlu direstrukturisasi (jumlah ember perlu diubah) - bahkan dengan fungsi hash 'baik'. Sama seperti memasukkan elemen ke vektor membutuhkan O (n) setiap saat karena array yang mendasarinya perlu dialokasikan kembali.

Memasukkan dalam set selalu membutuhkan paling banyak O (log n). Ini mungkin lebih disukai di beberapa aplikasi.

Blargle
sumber
3

Maafkan saya, satu hal lagi yang perlu diperhatikan tentang properti yang diurutkan:

Jika Anda ingin rentang data dalam wadah, misalnya: Anda menyimpan waktu dalam set , dan Anda ingin waktu dari 2013-01-01 hingga 2014-01-01.

Untuk unordered_set tidak mungkin.

Tentu saja, contoh ini akan lebih meyakinkan untuk kasus penggunaan antara peta dan unordered_map .

Spektral
sumber
3

g++ 6.4 stdlibc ++ memerintahkan vs patokan set tidak teratur

Saya membandingkan penerapan Linux C ++ yang dominan ini untuk melihat perbedaannya:

masukkan deskripsi gambar di sini

Rincian dan analisis benchmark lengkap telah diberikan di: Apa struktur data yang mendasari set STL di C ++? dan saya tidak akan mengulanginya di sini.

"BST" berarti "diuji dengan std::setdan" peta hash "berarti" diuji dengan std::unordered_set. "Heap" untuk std::priority_queueyang saya analisis di: Heap vs Binary Search Tree (BST)

Sebagai ringkasan cepat:

  • grafik dengan jelas menunjukkan bahwa dalam kondisi ini, penyisipan hashmap selalu jauh lebih cepat ketika ada lebih dari 100k item, dan perbedaannya bertambah ketika jumlah item meningkat

    Biaya peningkatan kecepatan ini adalah Anda tidak dapat melakukan traverse secara efisien.

  • kurva jelas menunjukkan bahwa dipesan std::setadalah berbasis BST dan std::unordered_setberbasis hashmap. Dalam jawaban referensi, saya selanjutnya mengonfirmasi bahwa dengan langkah GDB men-debug kode.

Pertanyaan serupa untuk mapvs unordered_map: Apakah ada keuntungan menggunakan peta di atas unordered_map jika ada kunci sepele?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
1

Tentu saja, saya akan mengatakan itu nyaman untuk memiliki sesuatu dalam suatu hubungan jika Anda ingin mengubahnya menjadi format yang berbeda.

Ada juga kemungkinan bahwa sementara satu lebih cepat diakses, waktu untuk membangun indeks atau memori yang digunakan saat membuat dan / atau mengaksesnya lebih besar.

Rushyo
sumber
+1, notasi Big Oh menyembunyikan faktor konstan, dan untuk ukuran masalah tipikal, sering faktor konstan yang paling penting.
j_random_hacker
1

Jika Anda ingin memiliki hal-hal yang diurutkan, maka Anda akan menggunakan set alih-alih unordered_set. unordered_set digunakan lebih dari set ketika memesan disimpan tidak masalah.

pungutan
sumber
1

Meskipun jawaban ini mungkin terlambat 10 tahun, ada baiknya menunjukkan bahwa std::unordered_setjuga memiliki kelemahan keamanan.

Jika fungsi hash dapat diprediksi (hal ini biasanya terjadi kecuali jika menerapkan tindakan balasan seperti garam acak), penyerang dapat mengolah data yang menghasilkan tabrakan hash dan menyebabkan semua penyisipan dan pencarian membutuhkan waktu O (n) .

Ini dapat digunakan untuk serangan penolakan layanan yang sangat efisien dan elegan.

Banyak (kebanyakan?) Implementasi bahasa yang menggunakan peta hash secara internal telah mengalami hal ini:

mic_e
sumber