Saya telah melihat beberapa klaim menarik tentang hashmaps SO re Java dan O(1)
waktu pencarian mereka . Adakah yang bisa menjelaskan mengapa demikian? Kecuali jika hashmaps ini sangat berbeda dari algoritma hashing yang saya beli, pasti selalu ada dataset yang berisi collision.
Dalam hal ini, pencarian akan O(n)
lebih daripada O(1)
.
Dapatkah seseorang menjelaskan apakah mereka adalah O (1) dan, jika demikian, bagaimana mereka mencapai hal ini?
java
hashmap
big-o
time-complexity
paxdiablo
sumber
sumber
Jawaban:
Fitur khusus dari HashMap adalah bahwa tidak seperti, katakanlah, pohon seimbang, perilakunya adalah probabilistik. Dalam kasus-kasus ini, biasanya sangat membantu untuk membicarakan kompleksitas dalam hal kemungkinan suatu peristiwa terburuk terjadi. Untuk peta hash, itu tentu saja adalah kasus tabrakan sehubungan dengan seberapa penuh peta itu terjadi. Tabrakan cukup mudah diperkirakan.
Jadi peta hash dengan jumlah elemen yang kecil kemungkinan besar akan mengalami setidaknya satu tabrakan. Notasi O besar memungkinkan kita melakukan sesuatu yang lebih menarik. Perhatikan bahwa untuk sembarang, konstanta tetap k.
Kita dapat menggunakan fitur ini untuk meningkatkan kinerja peta hash. Kami malah bisa memikirkan kemungkinan paling banyak 2 tabrakan.
Ini jauh lebih rendah. Karena biaya penanganan satu tabrakan tambahan tidak relevan dengan kinerja Big O, kami telah menemukan cara untuk meningkatkan kinerja tanpa benar-benar mengubah algoritma! Kita dapat melakukan ini secara umum
Dan sekarang kita dapat mengabaikan sejumlah tabrakan yang sewenang-wenang dan berakhir dengan kemungkinan kecil semakin banyak tabrakan daripada yang kita perhitungkan. Anda bisa mendapatkan probabilitas ke tingkat kecil sewenang-wenang dengan memilih k yang benar, semua tanpa mengubah implementasi algoritma yang sebenarnya.
Kita membicarakan hal ini dengan mengatakan bahwa peta hash memiliki akses O (1) dengan probabilitas tinggi
sumber
Anda tampaknya mencampur perilaku kasus terburuk dengan rata-rata (diharapkan) runtime. Yang pertama memang O (n) untuk tabel hash secara umum (yaitu tidak menggunakan hashing sempurna) tetapi ini jarang relevan dalam praktek.
Setiap implementasi tabel hash yang dapat diandalkan, ditambah dengan hash setengah layak, memiliki kinerja pengambilan O (1) dengan faktor yang sangat kecil (2, pada kenyataannya) dalam kasus yang diharapkan, dalam margin varians yang sangat sempit.
sumber
Di Jawa, HashMap bekerja dengan menggunakan hashCode untuk menemukan ember. Setiap ember adalah daftar item yang berada di dalam ember itu. Item dipindai, menggunakan yang sama untuk perbandingan. Saat menambahkan item, HashMap diubah ukurannya setelah persentase beban tertentu tercapai.
Jadi, kadang-kadang harus dibandingkan dengan beberapa item, tetapi umumnya jauh lebih dekat dengan O (1) daripada O (n). Untuk tujuan praktis, hanya itu yang perlu Anda ketahui.
sumber
Ingat bahwa o (1) tidak berarti bahwa setiap pencarian hanya memeriksa satu item - itu berarti bahwa jumlah rata-rata barang yang diperiksa tetap konstan dengan jumlah item dalam wadah. Jadi jika diperlukan rata-rata 4 perbandingan untuk menemukan item dalam wadah dengan 100 item, itu juga harus mengambil rata-rata 4 perbandingan untuk menemukan item dalam wadah dengan 10.000 item, dan untuk jumlah item lainnya (selalu ada sedikit varians, terutama di sekitar titik-titik di mana tabel hash mengulangi, dan ketika ada sejumlah item yang sangat kecil).
Jadi tabrakan tidak mencegah wadah memiliki o (1) operasi, selama jumlah rata-rata kunci per ember tetap dalam batas yang tetap.
sumber
Saya tahu ini adalah pertanyaan lama, tetapi sebenarnya ada jawaban baru untuk itu.
Anda benar bahwa peta hash tidak benar-benar
O(1)
, secara tegas, karena karena jumlah elemen menjadi besar secara sewenang-wenang, akhirnya Anda tidak akan dapat mencari dalam waktu yang konstan (dan notasi-O didefinisikan dalam bentuk angka yang dapat menjadi besar secara sewenang-wenang).Tetapi itu tidak berarti bahwa kompleksitas waktu nyata adalah
O(n)
- karena tidak ada aturan yang mengatakan bahwa bucket harus diimplementasikan sebagai daftar linear.Faktanya, Java 8 mengimplementasikan bucket
TreeMaps
setelah mereka melampaui ambang batas, yang membuat waktu aktualO(log n)
.sumber
Jika jumlah ember (sebut saja b) dipertahankan konstan (kasing biasa), maka pencarian sebenarnya O (n).
Saat n bertambah besar, jumlah elemen di setiap bucket rata-rata n / b. Jika resolusi tabrakan dilakukan dengan salah satu cara yang biasa (daftar tertaut misalnya), maka pencarian adalah O (n / b) = O (n).
Notasi O adalah tentang apa yang terjadi ketika n menjadi lebih besar dan lebih besar. Ini bisa menyesatkan ketika diterapkan pada algoritma tertentu, dan tabel hash adalah contohnya. Kami memilih jumlah ember berdasarkan pada berapa banyak elemen yang kami harapkan untuk ditangani. Ketika n adalah tentang ukuran yang sama dengan b, maka pencarian kira-kira konstan-waktu, tetapi kita tidak dapat menyebutnya O (1) karena O didefinisikan dalam batasan sebagai n → ∞.
sumber
O(1+n/k)
di manak
jumlah ember.Jika implementasi set
k = n/alpha
maka itu adalahO(1+alpha) = O(1)
karenaalpha
adalah sebuah konstanta.sumber
Kami telah menetapkan bahwa deskripsi standar pencarian tabel hash menjadi O (1) mengacu pada waktu rata-rata yang diharapkan, bukan kinerja kasus terburuk yang ketat. Untuk tabel hash yang menyelesaikan tabrakan dengan chaining (seperti hashmap Java) ini secara teknis O (1 + α) dengan fungsi hash yang baik , di mana α adalah faktor beban tabel. Masih konstan selama jumlah objek yang Anda simpan tidak lebih dari faktor konstan yang lebih besar dari ukuran tabel.
Juga telah dijelaskan bahwa secara ketat dimungkinkan untuk membuat input yang membutuhkan pencarian O ( n ) untuk setiap fungsi hash deterministik. Tetapi juga menarik untuk mempertimbangkan waktu perkiraan terburuk , yang berbeda dari waktu pencarian rata-rata. Menggunakan rantai ini adalah O (1 + panjang rantai terpanjang), misalnya Θ (log n / log n ) ketika α = 1.
Jika Anda tertarik dengan cara-cara teoritis untuk mencapai waktu pencarian yang diharapkan, maka Anda dapat membaca tentang hashing sempurna dinamis yang menyelesaikan tabrakan secara rekursif dengan tabel hash lain!
sumber
Ini adalah O (1) hanya jika fungsi hashing Anda sangat bagus. Implementasi tabel hash Java tidak melindungi terhadap fungsi hash yang buruk.
Apakah Anda perlu menumbuhkan tabel saat Anda menambahkan item atau tidak tidak relevan dengan pertanyaan karena ini adalah tentang waktu pencarian.
sumber
Elemen di dalam HashMap disimpan sebagai array dari daftar tertaut (node), setiap daftar tertaut dalam array mewakili sebuah ember untuk nilai hash unik dari satu atau lebih kunci.
Saat menambahkan entri di HashMap, kode hash kunci digunakan untuk menentukan lokasi ember dalam array, sesuatu seperti:
Di sini & mewakili operator DAN bitwise.
Sebagai contoh:
100 & "ABC".hashCode() = 64 (location of the bucket for the key "ABC")
Selama mendapatkan operasi itu menggunakan cara yang sama untuk menentukan lokasi ember untuk kunci. Di bawah kasus terbaik setiap kunci memiliki kode hash unik dan menghasilkan ember unik untuk setiap kunci, dalam hal ini metode get menghabiskan waktu hanya untuk menentukan lokasi bucket dan mengambil nilai yang konstan O (1).
Dalam kasus terburuk, semua kunci memiliki kode hash yang sama dan disimpan dalam ember yang sama, ini menghasilkan melintasi seluruh daftar yang mengarah ke O (n).
Dalam kasus java 8, bucket Linked List diganti dengan TreeMap jika ukurannya tumbuh lebih dari 8, ini mengurangi efisiensi pencarian case terburuk ke O (log n).
sumber
Ini pada dasarnya berlaku untuk sebagian besar implementasi tabel hash di sebagian besar bahasa pemrograman, karena algoritma itu sendiri tidak benar-benar berubah.
Jika tidak ada tabrakan hadir dalam tabel, Anda hanya perlu melakukan pencarian tunggal, oleh karena itu waktu berjalan adalah O (1). Jika ada tabrakan, Anda harus melakukan lebih dari satu pencarian, yang menurunkan kinerja menuju O (n).
sumber
Itu tergantung pada algoritma yang Anda pilih untuk menghindari tabrakan. Jika implementasi Anda menggunakan rantai terpisah, maka skenario terburuk terjadi di mana setiap elemen data hash dengan nilai yang sama (misalnya, pilihan fungsi hash yang buruk). Dalam hal ini, pencarian data tidak berbeda dari pencarian linear pada daftar tertaut yaitu O (n). Namun, probabilitas kejadian itu dapat diabaikan dan pencarian kasus terbaik dan rata-rata tetap konstan yaitu O (1).
sumber
Di samping akademis, dari perspektif praktis, HashMaps harus diterima memiliki dampak kinerja yang tidak penting (kecuali jika profiler Anda memberi tahu Anda sebaliknya.)
sumber
Hanya dalam kasus teoretis, ketika kode hash selalu berbeda dan bucket untuk setiap kode hash juga berbeda, O (1) akan ada. Kalau tidak, itu adalah urutan konstan yaitu pada peningkatan hashmap, urutan pencariannya tetap konstan.
sumber
Tentu saja kinerja hashmap akan bergantung pada kualitas fungsi hashCode () untuk objek yang diberikan. Namun, jika fungsi diimplementasikan sedemikian rupa sehingga kemungkinan tabrakan sangat rendah, itu akan memiliki kinerja yang sangat baik (ini tidak sepenuhnya O (1) di setiap kasus tetapi dalam kebanyakan kasus).
Sebagai contoh implementasi default di Oracle JRE adalah dengan menggunakan nomor acak (yang disimpan dalam instance objek sehingga tidak berubah - tetapi juga menonaktifkan penguncian bias, tapi itu diskusi lain) sehingga kemungkinan tabrakan adalah sangat rendah.
sumber
hashCode % tableSize
yang berarti pasti akan ada tabrakan. Anda tidak dapat sepenuhnya menggunakan 32-bit. Itu semacam titik tabel hash ... Anda mengurangi ruang pengindeksan yang besar menjadi yang kecil.