เหตุใดการค้นหา hashtable (ไม่ชน) จึงเป็นจริง O (1)


10

คำเตือน: ฉันรู้ว่ามีคำถามที่คล้ายกันทำให้เกิดเสียงที่นี่แล้วและใน Stackoverflow แต่พวกเขาทั้งหมดเกี่ยวกับการชนซึ่งไม่ใช่สิ่งที่ฉันขอ

คำถามของฉันคือทำไมการค้นหาน้อยกว่าO(1)ในตอนแรก?

สมมติว่าฉันมี hashtable นี้:

Hash  Content
-------------
ghdjg Data1
hgdzs Data2
eruit Data3
xcnvb Data4
mkwer Data5
rtzww Data6

ตอนนี้ฉันกำลังมองหากุญแจสำคัญในการkที่ฟังก์ชั่นกัญชาให้h(k) h(k) = mkwerแต่การค้นหา "รู้" ว่าแฮชmkwerอยู่ที่อันดับ 5 อย่างไร เหตุใดจึงไม่ต้องเลื่อนผ่านปุ่มทั้งหมดO(n)เพื่อค้นหา แฮชไม่สามารถใช้ที่อยู่ฮาร์ดแวร์บางประเภทได้เพราะฉันสูญเสียความสามารถในการย้ายข้อมูล และเท่าที่ฉันรู้ hashtable ไม่ได้ถูกจัดเรียงไว้บนแฮช (แม้ว่าจะเป็นแล้วการค้นหาก็ต้องใช้O(log n))?

การรู้จักแฮชช่วยค้นหาสถานที่ที่ถูกต้องในตารางได้อย่างไร

คำตอบ:


24

ฟังก์ชั่นแฮชไม่ได้mkwerกลับสตริงบางอย่างเช่น มันส่งกลับตำแหน่งของรายการในอาร์เรย์โดยตรง ตัวอย่างเช่นหากตารางแฮชของคุณมีสิบรายการฟังก์ชันแฮชจะส่งคืนจำนวนเต็มในช่วง 0-9


1
ขอบคุณ :) ความผิดพลาดของฉันคือคิดถึงฟังก์ชั่นแฮชแฮชเช่น MD5 หรือ SHA แต่แน่นอนแฮชสามารถเป็นตำแหน่งจำนวนเต็มซึ่งฉันไม่ได้คิด ตอนนี้ฉันรู้ว่าจะต้องหาอะไรฉันก็พบตัวอย่างที่ดีได้อย่างรวดเร็ว: ฟังก์ชันแฮชของ PHP: github.com/php/php-src/blob/PHP-5.6.10/Zend/zend_hash.h#L237
Foo บาร์

13
@FooBar: MD5 และ SHA ยังคำนวณตัวเลขเดี่ยวจากอินพุตมันเป็นเรื่องธรรมดามากที่จะพูดถึงแฮชในรูปแบบเลขฐานสิบหก เช่นเดียวกับที่อยู่หน่วยความจำไม่ค่อยได้รับการพิจารณาเป็นทศนิยม
nperson325681

4
ยิ่งไปกว่านั้น MD5 ฯลฯ นั้นยาวเกินกว่าที่จะใช้เป็นดัชนีอาร์เรย์ได้โดยตรง เป็นไปได้ที่จะใช้แฮชบางส่วนเช่นลดบิตn
chirlu

6

ฟังก์ชั่นแฮนวนตำแหน่งอาร์เรย์จากสตริงที่กำหนด ถ้านี่คือแฮชที่สมบูรณ์แบบนั่นหมายความว่าไม่มีการชนกันอย่างแน่นอนอาเรย์ส่วนใหญ่น่าจะใหญ่กว่าจำนวนขององค์ประกอบอย่างน้อยสองเท่า

ตัวอย่างเช่นฉันจะให้แฮชที่แย่มากสำหรับตัวอักษรเพียงเพื่อลบล้างกลไก:
0) 1) สำหรับอักขระแต่ละตัวในสตริงใช้ค่า ascii ให้ลบ 'a' หากเป็นตัวพิมพ์เล็กให้ลบ 'A' ถ้าตัวพิมพ์ใหญ่ให้เพิ่มค่าเป็น x x = x m o d 52 2) จำนวนผลลัพธ์เช่น 15 คือดัชนีของอาร์เรย์ x=0;
x=xม.โอd52

แฮชแบบง่าย ๆ นี้ (จำกัด และมีแนวโน้มที่จะชนกัน) แตกต่างจากแฮชอื่น ๆ ในกลไกของการแฮชไม่ได้พิจารณาอินพุตที่ได้รับ ในรูปแบบที่ก้าวหน้ายิ่งขึ้นแฮชคือจำนวนที่ใหญ่กว่าปรับเป็นจำนวนองค์ประกอบ มีการสร้างแฮชที่สมบูรณ์แบบสำหรับอินพุตทั้งหมดเพื่อรับประกันว่าไม่มีการชน

นี่คือเนื่องจากการคำนวณแฮชจากสตริงขึ้นอยู่กับการคำนวณฟังก์ชันที่ซับซ้อน แต่ไม่ได้ขึ้นอยู่กับจำนวนองค์ประกอบO(1)

ในกรณีที่แฮชสมบูรณ์แบบเมื่อมีการเพิ่มองค์ประกอบใหม่จะทำการคำนวณใหม่กรณีที่ง่ายกว่าที่มีการชนกันเมื่อโหลดอาร์เรย์มีขนาดใหญ่ขึ้นขนาดอาร์เรย์ที่เพิ่มขึ้นฟังก์ชั่นจะใช้มอดูโลเอาท์พุทที่ใหญ่กว่าชั่วโมง(k)

Array เป็นหน่วยความจำที่ต่อเนื่องเพื่อให้ได้องค์ประกอบคุณต้องระบุที่อยู่ขององค์ประกอบแรก (เริ่มต้นอาร์เรย์) แล้วเพิ่มไปยังที่อยู่นี้n ( s i z e o f e l e m e n t )ดังนั้นคุณจึงมี เซลล์หน่วยความจำที่ชัดเจนn-เสื้อชั่วโมงn* * * *(sผมZอีโออีล.อีม.อีnเสื้อ)


1
และการค้นหารู้ได้อย่างไรว่าอยู่ในที่ใดที่ตารางแฮช มันไม่ได้สั่งหรือที่อยู่ฮาร์ดแวร์
Foo Bar

ชั่วโมง("xnโวลต์")=8

แต่ไม่ใช่ทุกดัชนีจะถูกเติม หากฉันมีข้อมูลแฮช 1, 4, 8, 90 และ 223 แล้วการค้นหาจะค้นหาสถานที่ที่ถูกต้องได้อย่างไร ในกรณีดัชนี thsi "90" อยู่ในตำแหน่งที่ 4 เพราะดัชนีส่วนใหญ่ไม่มีอยู่ และ hashtable ที่ว่างเปล่าไม่ได้มีขนาดไม่ จำกัด ที่มีตำแหน่งที่เป็นไปได้ทั้งหมด!
Foo Bar

HaHa(ชั่วโมง("xnโวลต์"))=Ha[90]

ฟังก์ชันแฮชจะไม่ส่งคืนดัชนีไปยังอาร์เรย์ แต่จะส่งกลับตัวเลขที่สามารถคาดเดาได้ซึ่งสามารถแมปเข้าในอาร์เรย์ได้ ปกติแล้วจะใช้ตัวดำเนินการโมดูลัสกับจำนวนของที่เก็บถังตารางแฮชเป็นตัวถูกดำเนินการอื่น
Christopher Schultz

3

หากต้องการขยายคำตอบของ David Richerby คำว่า " ฟังก์ชันแฮช " จะมีการใช้งานมากเกินไป บ่อยครั้งเมื่อเราพูดถึงฟังก์ชั่นแฮชเราคิดว่า MD5, SHA-1 หรือบางอย่างเช่น.hashCode()วิธีของ Java ซึ่งเปลี่ยนอินพุตให้เป็นตัวเลขเดียว อย่างไรก็ตามโดเมนของหมายเลขนี้ (เช่นคือค่าสูงสุด) นั้นไม่น่าจะมีขนาดเท่ากันกับ hashtable ที่คุณพยายามจัดเก็บข้อมูล (MD5 คือ 16 ไบต์ SHA-1 คือ 20 ไบต์และ.hashCode()เป็นint- 4 bytes)

ดังนั้นคำถามของคุณเกี่ยวกับขั้นตอนต่อไป - เมื่อเรามีฟังก์ชั่นแฮชที่สามารถแมปอินพุตแบบสุ่มกับตัวเลขเราจะใส่โครงสร้างข้อมูลที่มีขนาดเฉพาะได้อย่างไร ด้วยฟังก์ชั่นอื่นเรียกอีกอย่างว่า "ฟังก์ชั่นแฮช"!

ตัวอย่างเล็ก ๆ น้อย ๆ ของฟังก์ชั่นดังกล่าวเป็นแบบโมดูโล ; คุณสามารถแมปขนาดที่กำหนดเองไปยังดัชนีเฉพาะในอาเรย์ด้วยโมดูโลได้อย่างง่ายดาย สิ่งนี้ถูกนำเสนอใน CLRS ในฐานะ "วิธีการหาร":

kม.kม.

ชั่วโมง(k)=kม.

...

ม.ม.ม.=2พีชั่วโมง(k)พีk

~ รู้เบื้องต้นเกี่ยวกับอัลกอริทึม, §11.3.1 - CLRS

ม.

Java HashMapใช้รุ่นที่ปรับเปลี่ยนของวิธีการหารที่ทำขั้นตอนการประมวลผลล่วงหน้าเพื่อบัญชีสำหรับ.hashCode()การใช้งานที่อ่อนแอเพื่อให้สามารถใช้อาร์เรย์ที่มีขนาดกำลังไฟสอง คุณสามารถเห็นสิ่งที่เกิดขึ้นใน.getEntry()วิธีการ (ความเห็นเป็นของฉัน):

 // hash() transforms key.hashCode() to protect against bad hash functions
 int hash = (key == null) ? 0 : hash(key.hashCode());
 // indexOf() converts the resulting hash to a value between 0 and table.length-1
 for (Entry<K,V> e = table[indexFor(hash, table.length)];
     ...

Java 8 นำมาเขียนใหม่HashMapซึ่งเร็วยิ่งขึ้น แต่อ่านยากขึ้นเล็กน้อย แต่ใช้หลักการทั่วไปเดียวกันกับการค้นหาดัชนีอย่างไรก็ตาม

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.