Bisakah tabel hash benar-benar menjadi O (1)?

114

Tampaknya sudah menjadi pengetahuan umum bahwa tabel hash dapat mencapai O (1), tetapi itu tidak pernah masuk akal bagi saya. Bisakah seseorang menjelaskannya? Berikut dua situasi yang muncul di benak Anda:

A. Nilai int lebih kecil dari ukuran tabel hash. Oleh karena itu, nilainya adalah hashnya sendiri, jadi tidak ada tabel hash. Tetapi jika ada, itu akan menjadi O (1) dan tetap tidak efisien.

B. Anda harus menghitung hash dari nilai tersebut. Dalam situasi ini, urutannya adalah O (n) untuk ukuran data yang dicari. Pencariannya mungkin O (1) setelah Anda melakukan O (n) pekerjaan, tapi itu masih keluar ke O (n) di mata saya.

Dan kecuali Anda memiliki hash yang sempurna atau tabel hash yang besar, mungkin ada beberapa item per ember. Jadi, itu beralih ke pencarian linier kecil di beberapa titik.

Saya pikir tabel hash itu luar biasa, tapi saya tidak mendapatkan sebutan O (1) kecuali itu hanya seharusnya teoritis.

Artikel Wikipedia untuk tabel hash secara konsisten merujuk pada waktu pencarian yang konstan dan sama sekali mengabaikan biaya fungsi hash. Apakah itu tindakan yang adil?


Edit: Untuk meringkas apa yang saya pelajari:

  • Ini secara teknis benar karena fungsi hash tidak diperlukan untuk menggunakan semua informasi dalam kunci dan bisa jadi waktu yang konstan, dan karena tabel yang cukup besar dapat membawa tabrakan ke waktu yang hampir konstan.

  • Ini benar dalam praktiknya karena seiring waktu itu hanya berfungsi selama fungsi hash dan ukuran tabel dipilih untuk meminimalkan benturan, meskipun itu sering berarti tidak menggunakan fungsi hash waktu yang konstan.

ditarik ke depan
sumber
31
Ini diamortisasi O (1), bukan O (1).
kennytm
Ingat O () adalah batas untuk sejumlah besar operasi. Secara 'rata-rata' Anda tidak akan mengalami banyak tabrakan - tidak perlu ada operasi individu yang tidak memiliki tabrakan.
Martin Beckett
Bergantung pada implementasi string, string mungkin membawa nilai hash bersama mereka, jadi ini akan menjadi konstan. Intinya adalah, itu tidak relevan dengan kompleksitas pencarian hash.
Rich Remer
@kennytm Tentu, pencarian setelah Anda melakukan hash input diamortisasi O (1). Tetapi apakah biaya komputasi hash benar-benar dapat diabaikan? Misalkan kita melakukan hashing pada string - array karakter. Untuk menghasilkan hash, setiap karakter diiterasi, jadi hashing string adalah O (N) di mana N adalah panjang string. Begitulah cara itu didokumentasikan untuk C # dan ini adalah bagaimana hashCode()metode Java diimplementasikan untuk String. grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
spaaarky21
1
@ spaaarky21 N dalam O (N) yang Anda bicarakan adalah panjang string, yang berbeda dari n ukuran tabel hash. Jawaban Mark Byer sudah membahas ini.
kennytm

Jawaban:

65

Anda memiliki dua variabel di sini, m dan n, di mana m adalah panjang input dan n adalah jumlah item dalam hash.

Klaim performa pencarian O (1) membuat setidaknya dua asumsi:

  • Objek Anda dapat dibandingkan kesetaraan dalam waktu O (1).
  • Akan ada beberapa tabrakan hash.

Jika objek Anda berukuran variabel dan pemeriksaan kesetaraan memerlukan melihat semua bit maka kinerja akan menjadi O (m). Namun fungsi hash tidak harus O (m) - bisa jadi O (1). Tidak seperti hash kriptografi, fungsi hash untuk digunakan dalam kamus tidak harus melihat setiap bit dalam input untuk menghitung hash. Implementasi bebas untuk melihat hanya sejumlah bit tetap.

Untuk cukup banyak item, jumlah item akan menjadi lebih besar dari jumlah kemungkinan hash dan kemudian Anda akan mendapatkan tabrakan yang menyebabkan kinerja meningkat di atas O (1), misalnya O (n) untuk traversal daftar tertaut sederhana (atau O (n * m) jika kedua asumsi salah).

Dalam prakteknya meskipun klaim O (1) sementara secara teknis salah, kira - kira benar untuk banyak situasi dunia nyata, dan khususnya situasi di mana asumsi di atas berlaku.

Mark Byers
sumber
4
Seperti halnya di atas, jika Anda menggunakan objek yang tidak dapat diubah sebagai kunci Anda misalnya String Java, setelah menghitung hash sekali, Anda dapat mengingatnya dan tidak perlu menghitungnya lagi. Di sisi lain, Anda biasanya tidak dapat mengandalkan hash untuk mengetahui apakah dua kunci sama setelah Anda menemukan bucket yang tepat, jadi untuk string Anda perlu melakukan O (m) traversal untuk mengetahui apakah keduanya sama.
JeremyP
1
@ JeremyP: Poin bagus tentang perbandingan persamaan O (m). Saya melewatkan itu - posting yang diperbarui. Terima kasih!
Mark Byers
2
The O(1)klaim benar jika Anda sedang hashing ints atau sesuatu yang lain yang cocok dalam kata mesin. Itulah yang diasumsikan oleh sebagian besar teori tentang hashing.
Thomas Ahle
Saya suka penjelasan Anda Mark, saya mengutipnya di artikel saya tentang tabel hash di meshfields.de/hash-tables
Steve K
3
Dalam "m adalah panjang masukan" - masukan terlalu kabur - ini mungkin berarti semua kunci & nilai dimasukkan, tetapi kemudian menjadi jelas (setidaknya bagi mereka yang sudah memahami topik) yang Anda maksud adalah kuncinya . Hanya menyarankan menggunakan "kunci" dalam jawaban untuk kejelasan. BTW - contoh konkret - Visual C ++ std::hashtombol tekstual menggabungkan 10 karakter yang ditempatkan secara merata di sepanjang teks ke dalam nilai hash, jadi O (1) terlepas dari panjang teks (tetapi secara masif lebih rentan benturan daripada GCC!). Secara terpisah, klaim O (1) memiliki asumsi lain (biasanya benar) bahwa m jauh lebih kecil dari n .
Tony Delroy
22

Anda harus menghitung hash, jadi urutannya adalah O (n) untuk ukuran data yang dicari. Pencariannya mungkin O (1) setelah Anda melakukan O (n) pekerjaan, tapi itu masih keluar ke O (n) di mata saya.

Apa? Untuk melakukan hash, satu elemen membutuhkan waktu yang konstan. Mengapa menjadi yang lain? Jika Anda memasukkan nelemen, ya, Anda harus menghitung nhash, dan itu membutuhkan waktu linier ... untuk mencari elemen, Anda menghitung satu hash dari apa yang Anda cari, lalu temukan bucket yang sesuai dengan itu . Anda tidak menghitung ulang hash dari semua yang sudah ada di tabel hash.

Dan, kecuali jika Anda memiliki hash yang sempurna atau tabel hash yang besar, mungkin ada beberapa item per keranjang sehingga itu berpindah ke pencarian linier kecil di beberapa titik.

Belum tentu. Bucket tidak harus berupa list atau array, bucket dapat berupa jenis container apa pun, seperti BST yang seimbang. Itu berarti O(log n)kasus terburuk. Namun inilah mengapa penting untuk memilih fungsi hashing yang baik untuk menghindari menempatkan terlalu banyak elemen ke dalam satu wadah. Seperti yang dikemukakan KennyTM, rata-rata, Anda tetap punya O(1)waktu, meski sesekali harus menggali melalui ember.

Pertukaran tabel hash tentu saja adalah kompleksitas ruang. Anda menukar ruang dengan waktu, yang tampaknya merupakan kasus biasa dalam ilmu komputasi.


Anda menyebutkan menggunakan string sebagai kunci di salah satu komentar Anda yang lain. Anda khawatir tentang jumlah waktu yang diperlukan untuk menghitung hash string, karena terdiri dari beberapa karakter? Seperti yang ditunjukkan orang lain lagi, Anda tidak perlu melihat semua karakter untuk menghitung hash, meskipun itu mungkin menghasilkan hash yang lebih baik jika Anda melakukannya. Dalam hal ini, jika ada rata-rata mkarakter di kunci Anda, dan Anda menggunakan semuanya untuk menghitung hash Anda, maka saya kira Anda benar, pencarian itu akan dilakukan O(m). Jika m >> nkemudian Anda mungkin memiliki masalah. Anda mungkin akan lebih baik dengan BST dalam kasus itu. Atau pilih fungsi hashing yang lebih murah.

mpen
sumber
tabel hash tidak menggunakan BST. BST tidak membutuhkan nilai hash. Maps dan Sets dapat diimplementasikan sebagai BST.
Nick Dandoulakis
3
@Nick: Eh? Tidak ... BST tidak membutuhkan nilai hash ... itulah intinya. Kita mengasumsikan bahwa pada titik ini kita telah mengalami collision (hash yang sama ... atau setidaknya bucket yang sama), jadi kita perlu melihat sesuatu yang lain untuk menemukan elemen yang tepat, yaitu nilai sebenarnya.
mpen
oh, saya mengerti maksud Anda. Tapi saya tidak yakin bahwa mencampur BST dan hash sepadan dengan masalahnya. Mengapa tidak menggunakan BST saja?
Nick Dandoulakis
2
Saya hanya mengatakan bahwa Anda bisa menyingkirkan itu O(n)untuk tabrakan. Jika Anda sedang mengharapkan banyak dari tabrakan, maka Anda benar, mungkin lebih baik pergi dengan BST di tempat pertama.
mpen
1
@ spaaarky21 Benar, tetapi Ndalam hal ini adalah panjang string. Kita hanya perlu mencirikan satu string untuk menentukan 'keranjang' mana yang harus dimasuki - keranjang tidak bertambah dengan panjang peta hash .
mpen
5

Hash adalah ukuran tetap - mencari keranjang hash yang sesuai adalah operasi biaya tetap. Ini berarti O (1).

Menghitung hash tidak harus menjadi operasi yang mahal - kita tidak membicarakan fungsi hash kriptografi di sini. Tapi begitulah. Kalkulasi fungsi hash sendiri tidak bergantung pada jumlah n elemen; sementara itu mungkin tergantung pada ukuran data dalam sebuah elemen, ini bukan yang dimaksud dengan n . Jadi perhitungan hash tidak bergantung pada n dan juga O (1).

David M
sumber
3
mencari hash bucket adalah O (1). Tetapi menemukan kunci kanan, adalah prosedur O (n), di mana n bergantung pada jumlah tabrakan hash.
Nick Dandoulakis
1
Jadi dari 3 langkah, hitung hash, temukan ember, cari ember, langkah tengah konstan? Pencarian keranjang biasanya konstan. Menghitung hash biasanya lebih murah beberapa kali lipat daripada cara lain untuk menemukan bucket. Tetapi apakah itu benar-benar menambah waktu yang konstan? Dalam pencarian substring yang naif, Anda akan mengatakan O (n * m) untuk dua panjang, jadi mengapa panjang kunci diabaikan di sini?
diundur
menemukan kunci panjang tetap hanya O (n) hanya jika daftarnya didukung, tabel hash yang didukung pohon yang seimbang akan menjadi O (log (n))
jk.
@Jk Untuk fungsi hash yang baik, kasus terburuk selalu logn, lihat jawaban saya di stackoverflow.com/questions/4553624/hashmap-get-put-complexity/…
Thomas Ahle
Pada kasus terburuk, kompleksitas akan menjadi o (n) jika terjadi tabrakan
Saurabh Chandra Patel
3

Hashing adalah O (1) hanya jika hanya ada jumlah kunci yang konstan dalam tabel dan beberapa asumsi lain dibuat. Tetapi dalam kasus seperti itu, itu memiliki keuntungan.

Jika kunci Anda memiliki representasi n-bit, fungsi hash Anda dapat menggunakan 1, 2, ... n dari bit ini. Berpikir tentang fungsi hash yang menggunakan 1 bit. Evaluasi pasti O (1). Tetapi Anda hanya mempartisi ruang kunci menjadi 2. Jadi, Anda memetakan sebanyak 2 ^ (n-1) kunci ke dalam bin yang sama. menggunakan pencarian BST ini membutuhkan hingga n-1 langkah untuk menemukan kunci tertentu jika hampir penuh.

Anda dapat memperluas ini untuk melihat bahwa jika fungsi hash Anda menggunakan K bit, ukuran bin Anda adalah 2 ^ (nk).

jadi fungsi hash K-bit ==> tidak lebih dari 2 ^ K tempat sampah efektif ==> hingga 2 ^ (nK) kunci n-bit per bin ==> (nK) langkah (BST) untuk menyelesaikan tabrakan. Sebenarnya kebanyakan fungsi hash kurang "efektif" dan membutuhkan / menggunakan lebih dari K bit untuk menghasilkan 2 ^ k bin. Jadi ini pun optimis.

Anda dapat melihatnya dengan cara ini - Anda memerlukan ~ n langkah untuk dapat secara unik membedakan sepasang kunci dari n bit dalam kasus terburuk. Benar-benar tidak ada cara untuk menyiasati batasan teori informasi ini, tabel hash atau tidak.

Namun, ini BUKAN bagaimana / kapan Anda menggunakan tabel hash!

Analisis kompleksitas mengasumsikan bahwa untuk kunci n-bit, Anda dapat memiliki kunci O (2 ^ n) dalam tabel (misalnya 1/4 dari semua kunci yang mungkin). Tetapi sebagian besar jika tidak semua waktu kita menggunakan tabel hash, kita hanya memiliki sejumlah kunci n-bit yang konstan di tabel. Jika Anda hanya menginginkan sejumlah kunci konstan dalam tabel, katakanlah C adalah angka maksimum Anda, maka Anda dapat membentuk tabel hash dari O (C) bin, yang menjamin tumbukan konstan yang diharapkan (dengan fungsi hash yang baik); dan fungsi hash menggunakan ~ logC dari n bit kunci. Maka setiap kueri adalah O (logC) = O (1). Ini adalah cara orang mengklaim "akses tabel hash adalah O (1)" /

Ada beberapa batasan di sini - pertama, mengatakan Anda tidak membutuhkan semua bit mungkin hanya trik penagihan. Pertama, Anda tidak dapat benar-benar meneruskan nilai kunci ke fungsi hash, karena itu akan memindahkan n bit dalam memori yaitu O (n). Jadi, Anda perlu melakukan misal referensi lewat. Tetapi Anda masih perlu menyimpannya di suatu tempat yang merupakan operasi O (n); Anda hanya tidak menagihnya ke hashing; tugas komputasi Anda secara keseluruhan tidak dapat menghindari ini. Kedua, Anda melakukan hashing, menemukan bin, dan menemukan lebih dari 1 kunci; biaya Anda tergantung pada metode resolusi Anda - jika Anda melakukan perbandingan berdasarkan (BST atau Daftar), Anda akan memiliki operasi O (n) (kunci ingat adalah n-bit); jika Anda melakukan hash ke-2, Anda memiliki masalah yang sama jika hash kedua bertabrakan.

Pertimbangkan alternatifnya, misalnya BST, dalam kasus ini. ada tombol C, jadi BST yang seimbang akan menjadi O (logC) secara mendalam, jadi pencarian mengambil langkah O (logC). Namun perbandingan dalam kasus ini akan menjadi operasi O (n) ... jadi tampaknya hashing adalah pilihan yang lebih baik dalam kasus ini.

Eugene D
sumber
1

TL; DR: Tabel hash menjamin O(1)perkiraan waktu kasus terburuk jika Anda memilih fungsi hash secara seragam secara acak dari keluarga universal fungsi hash. Kasus terburuk yang diharapkan tidak sama dengan kasus rata-rata.

Penafian: Saya tidak secara resmi membuktikan tabel hash O(1), untuk itu lihat video dari coursera ini [ 1 ]. Saya juga tidak membahas aspek amortisasi dari tabel hash. Itu ortogonal untuk diskusi tentang hashing dan tabrakan.

Saya melihat banyak sekali kebingungan seputar topik ini di jawaban dan komentar lain, dan akan mencoba memperbaiki beberapa di antaranya dalam jawaban panjang ini.

Penalaran tentang kasus terburuk

Ada berbagai jenis analisis kasus terburuk. Analisis yang paling banyak dijawab sejauh ini bukanlah kasus terburuk, melainkan kasus rata - rata [ 2 ]. Analisis kasus rata-rata cenderung lebih praktis. Mungkin algoritme Anda memiliki satu masukan kasus terburuk yang buruk, tetapi sebenarnya berfungsi dengan baik untuk semua masukan lain yang memungkinkan. Intinya adalah waktu proses Anda bergantung pada kumpulan data tempat Anda menjalankan.

Pertimbangkan pseudocode berikut dari getmetode tabel hash. Di sini saya mengasumsikan kami menangani tabrakan dengan merangkai, jadi setiap entri tabel adalah daftar (key,value)pasangan yang ditautkan . Kami juga mengasumsikan jumlah kotak mtetap tetapi O(n), di mana njumlah elemen dalam input.

function get(a: Table with m buckets, k: Key being looked up)
  bucket <- compute hash(k) modulo m
  for each (key,value) in a[bucket]
    return value if k == key
  return not_found

Seperti yang ditunjukkan oleh jawaban lain, ini berjalan dalam O(1)kasus rata - rata dan terburuk O(n). Kita bisa membuat sketsa kecil bukti demi tantangan di sini. Tantangannya adalah sebagai berikut:

(1) Anda memberikan algoritma tabel hash Anda kepada musuh.

(2) Musuh dapat mempelajarinya dan mempersiapkan selama dia mau.

(3) Akhirnya musuh memberi Anda masukan ukuran nuntuk Anda masukkan ke dalam tabel Anda.

Pertanyaannya adalah: seberapa cepat tabel hash Anda pada input musuh?

Dari langkah (1) musuh mengetahui fungsi hash Anda; selama langkah (2) musuh dapat membuat daftar nelemen yang sama hash modulo m, misalnya dengan menghitung hash dari sekelompok elemen secara acak; dan kemudian di (3) mereka dapat memberi Anda daftar itu. Tapi lihatlah, karena semua nelemen memiliki hash ke keranjang yang sama, algoritme Anda akan membutuhkan O(n)waktu untuk melintasi daftar tertaut di keranjang itu. Tidak peduli berapa kali kami mencoba ulang tantangan, musuh selalu menang, dan seberapa buruk algoritme Anda, kasus terburuk O(n).

Kenapa hashing adalah O (1)?

Apa yang membuat kami tersingkir di tantangan sebelumnya adalah bahwa musuh mengetahui fungsi hash kami dengan sangat baik, dan dapat menggunakan pengetahuan itu untuk membuat masukan yang paling buruk. Bagaimana jika alih-alih selalu menggunakan satu fungsi hash tetap, kami sebenarnya memiliki sekumpulan fungsi hash H, yang dapat dipilih algoritme secara acak saat runtime? Jika Anda penasaran, Hdisebut keluarga universal fungsi hash [ 3 ]. Baiklah, mari kita coba menambahkan beberapa keacakan untuk ini.

Pertama, misalkan tabel hash kami juga menyertakan benih r, dan rditetapkan ke nomor acak pada waktu konstruksi. Kami menetapkannya sekali dan kemudian diperbaiki untuk contoh tabel hash itu. Sekarang mari kita lihat kembali pseudocode kita.

function get(a: Table with m buckets and seed r, k: Key being looked up)
  rHash <- H[r]
  bucket <- compute rHash(k) modulo m
  for each (key,value) in a[bucket]
    return value if k == key
  return not_found

Jika kita mencoba tantangannya sekali lagi: dari langkah (1) musuh dapat mengetahui semua fungsi hash yang kita miliki H, tetapi sekarang fungsi hash spesifik yang kita gunakan bergantung r. Nilai dari rbersifat pribadi untuk struktur kita, musuh tidak dapat memeriksanya pada waktu proses, atau memprediksinya sebelumnya, jadi dia tidak dapat membuat daftar yang selalu buruk bagi kita. Mari kita asumsikan bahwa pada langkah (2) musuh memilih satu fungsi hashsecara Hacak, dia kemudian membuat daftar ntabrakan di bawah hash modulo m, dan mengirimkannya untuk langkah (3), menyilangkan jari bahwa pada waktu proses H[r]akan sama dengan yang hashmereka pilih.

Ini adalah taruhan serius bagi musuh, daftar yang dia buat bertabrakan hash, tetapi hanya akan menjadi input acak di bawah fungsi hash lainnya H. Jika dia memenangkan taruhan ini, run time kami akan menjadi kasus terburuk O(n)seperti sebelumnya, tetapi jika dia kalah maka kami hanya diberi input acak yang membutuhkan O(1)waktu rata-rata . Dan memang seringkali musuh akan kalah, dia hanya menang sekali setiap |H|tantangan, dan kita bisa |H|menjadi sangat besar.

Bandingkan hasil ini dengan algoritme sebelumnya di mana musuh selalu memenangkan tantangan. Sedikit melambaikan tangan di sini, tetapi karena sebagian besar waktu musuh akan gagal, dan ini berlaku untuk semua kemungkinan strategi yang dapat dicoba oleh musuh, hal ini mengikuti bahwa meskipun kasus terburuk adalah O(n), kasus terburuk yang diharapkan sebenarnya O(1).


Sekali lagi, ini bukan bukti resmi. Jaminan yang kami dapatkan dari analisis kasus terburuk yang diharapkan ini adalah bahwa waktu proses kami sekarang tidak bergantung pada input spesifik apa pun . Ini adalah jaminan yang benar-benar acak, berbeda dengan analisis kasus rata-rata di mana kami menunjukkan bahwa musuh yang termotivasi dapat dengan mudah membuat masukan yang buruk.

Edman
sumber
0

Ada dua pengaturan di mana Anda bisa mendapatkan O (1) waktu terburuk.

  1. Jika pengaturan Anda statis, hashing FKS akan memberi Anda jaminan O (1) kasus terburuk . Tapi seperti yang Anda tunjukkan, pengaturan Anda tidak statis.
  2. Jika Anda menggunakan hashing Cuckoo, maka kueri dan penghapusan adalah O (1) kasus terburuk, tetapi penyisipan hanya diharapkan O (1) . Cuckoo hashing bekerja cukup baik jika Anda memiliki batas atas pada jumlah total sisipan, dan mengatur ukuran tabel menjadi sekitar 25% lebih besar.

Disalin dari sini

ChaosPredictor
sumber
0

Tampaknya berdasarkan diskusi di sini, bahwa jika X adalah batas atas (# elemen dalam tabel / # tempat sampah), maka jawaban yang lebih baik adalah O (log (X)) dengan asumsi implementasi pencarian bin yang efisien.

nak
sumber
0

A. Nilai int lebih kecil dari ukuran tabel hash. Oleh karena itu, nilainya adalah hashnya sendiri, jadi tidak ada tabel hash. Tetapi jika ada, itu akan menjadi O (1) dan tetap tidak efisien.

Ini adalah kasus di mana Anda dapat dengan mudah memetakan kunci ke bucket yang berbeda, sehingga array tampaknya merupakan pilihan struktur data yang lebih baik daripada tabel hash. Namun, inefisiensi tidak bertambah dengan ukuran tabel.

(Anda mungkin masih menggunakan tabel hash karena Anda tidak mempercayai int untuk tetap lebih kecil dari ukuran tabel saat program berkembang, Anda ingin membuat kode berpotensi dapat digunakan kembali ketika hubungan itu tidak berlaku, atau Anda tidak ingin orang membaca / memelihara kode harus menyia-nyiakan upaya mental untuk memahami dan memelihara hubungan).

B. Anda harus menghitung hash dari nilai tersebut. Dalam situasi ini, urutannya adalah O (n) untuk ukuran data yang dicari. Pencariannya mungkin O (1) setelah Anda melakukan O (n) pekerjaan, tapi itu masih keluar ke O (n) di mata saya.

Kita perlu membedakan antara ukuran kunci (misalnya dalam byte), dan ukuran jumlah kunci yang disimpan dalam tabel hash. Klaim bahwa tabel hash menyediakan operasi O (1) berarti bahwa operasi (sisipkan / hapus / temukan) tidak cenderung melambat lebih jauh karena jumlah kunci meningkat dari ratusan menjadi ribuan menjadi jutaan menjadi milyaran (setidaknya tidak jika semua data diakses / diperbarui dalam penyimpanan yang sama cepatnya, baik itu RAM atau disk - efek cache mungkin ikut bermain tetapi bahkan biaya kehilangan cache kasus terburuk cenderung menjadi kelipatan konstan dari kasus terbaik hit).

Pertimbangkan sebuah buku telepon: Anda mungkin memiliki nama di sana yang cukup panjang, tetapi apakah buku tersebut memiliki 100 nama, atau 10 juta, panjang nama rata-rata akan cukup konsisten, dan kasus terburuk dalam sejarah ...

Rekor dunia Guinness untuk Nama Terpanjang yang digunakan oleh siapa pun yang pernah ditetapkan oleh Adolph Blaine Charles David Earl Frederick Gerald Hubert Irvin John Kenneth Lloyd Martin Nero Oliver Paul Quincy Randolph Sherman Thomas Uncas Victor William Xerxes Yancy Wolfeschlegelsteinhausenbergerdorff, Senior

... wcmemberitahu saya itu 215 karakter - itu bukan keras atas terikat dengan panjang kunci, tapi kami tidak perlu khawatir tentang ada menjadi besar-besaran lagi.

Itu berlaku untuk sebagian besar tabel hash dunia nyata: rata-rata panjang kunci cenderung tidak bertambah seiring dengan jumlah kunci yang digunakan. Ada pengecualian, misalnya rutinitas pembuatan kunci mungkin mengembalikan string yang menyematkan bilangan bulat yang bertambah, tetapi meskipun demikian setiap kali Anda menambah jumlah kunci dengan urutan besarnya, Anda hanya menambah panjang kunci sebanyak 1 karakter: itu tidak signifikan.

Ini juga memungkinkan untuk membuat hash dari sejumlah data kunci berukuran tetap. Misalnya, Microsoft Visual C ++ dikirimkan dengan implementasi Pustaka Standar std::hash<std::string>yang membuat hash yang menggabungkan hanya sepuluh byte yang ditempatkan secara merata di sepanjang string, jadi jika string hanya bervariasi pada indeks lain Anda mendapatkan tabrakan (dan karenanya dalam praktiknya perilaku non-O (1) di sisi pencarian pasca-tabrakan), tetapi waktu untuk membuat hash memiliki batas atas yang sulit.

Dan kecuali Anda memiliki hash yang sempurna atau tabel hash yang besar, mungkin ada beberapa item per ember. Jadi, itu beralih ke pencarian linier kecil di beberapa titik.

Secara umum benar, tetapi hal yang mengagumkan tentang tabel hash adalah bahwa jumlah kunci yang dikunjungi selama "pencarian linier kecil" tersebut - untuk pendekatan rantai terpisah untuk tabrakan - fungsi dari faktor beban tabel hash (rasio kunci terhadap keranjang).

Misalnya, dengan faktor beban 1,0 ada rata-rata ~ 1,58 untuk panjang pencarian linier tersebut, terlepas dari jumlah kuncinya (lihat jawaban saya di sini ). Untuk hashing tertutup , ini sedikit lebih rumit, tetapi tidak lebih buruk jika faktor beban tidak terlalu tinggi.

Ini secara teknis benar karena fungsi hash tidak diperlukan untuk menggunakan semua informasi dalam kunci dan bisa jadi waktu yang konstan, dan karena tabel yang cukup besar dapat membawa tabrakan ke waktu yang hampir konstan.

Jenis ini melenceng. Setiap jenis struktur data asosiatif pada akhirnya harus melakukan operasi di setiap bagian kunci kadang-kadang (ketidaksetaraan kadang-kadang dapat ditentukan hanya dari sebagian kunci, tetapi kesetaraan umumnya mengharuskan setiap bit dipertimbangkan). Minimal, dapat melakukan hash kunci satu kali dan menyimpan nilai hash, dan jika menggunakan fungsi hash yang cukup kuat - misalnya MD5 64-bit - ia mungkin secara praktis mengabaikan kemungkinan dua kunci yang memiliki hash ke nilai yang sama (perusahaan Saya bekerja untuk melakukan persis seperti itu untuk database terdistribusi: waktu pembuatan hash masih tidak signifikan dibandingkan dengan transmisi jaringan seluruh WAN). Jadi, tidak ada gunanya terobsesi dengan biaya untuk memproses kunci: itu melekat dalam menyimpan kunci terlepas dari struktur datanya, dan seperti yang dikatakan di atas - tidak.

Adapun tabel hash yang cukup besar membawa tabrakan ke bawah, itu juga meleset. Untuk rangkaian terpisah, Anda masih memiliki panjang rantai tabrakan rata-rata yang konstan pada faktor beban apa pun - hanya lebih tinggi jika faktor beban lebih tinggi, dan hubungan tersebut non-linier. Komentar pengguna SO Hans atas jawaban saya juga ditautkan di atas :

panjang bucket rata-rata yang dikondisikan pada bucket tidak kosong adalah ukuran efisiensi yang lebih baik. Ini adalah a / (1-e ^ {- a}) [di mana a adalah faktor beban, e adalah 2,71828 ...]

Jadi, faktor beban saja yang menentukan jumlah rata-rata kunci yang bertabrakan yang harus Anda cari selama operasi sisipkan / hapus / temukan. Untuk rangkaian terpisah, tidak hanya mendekati konstan ketika faktor beban rendah - itu selalu konstan. Untuk pengalamatan terbuka, meskipun klaim Anda memiliki beberapa validitas: beberapa elemen yang bertabrakan dialihkan ke bucket alternatif dan kemudian dapat mengganggu operasi pada kunci lain, jadi pada faktor beban yang lebih tinggi (terutama> .8 atau .9) panjang rantai tabrakan menjadi lebih buruk secara dramatis.

Ini benar dalam praktiknya karena seiring waktu itu hanya berfungsi selama fungsi hash dan ukuran tabel dipilih untuk meminimalkan benturan, meskipun itu sering berarti tidak menggunakan fungsi hash waktu yang konstan.

Nah, ukuran tabel harus menghasilkan faktor beban yang waras mengingat pilihan hashing dekat atau rangkaian terpisah, tetapi juga jika fungsi hash agak lemah dan kuncinya tidak terlalu acak, memiliki bilangan prima bucket sering membantu mengurangi tabrakan juga ( hash-value % table-sizekemudian membungkus sedemikian rupa sehingga perubahan hanya ke satu atau dua orde tinggi dalam nilai hash masih menyelesaikan ke keranjang yang tersebar secara semu secara acak di berbagai bagian tabel hash).

Tony Delroy
sumber