ตารางแฮชสามารถเป็น O (1) ได้หรือไม่?


114

ดูเหมือนจะเป็นความรู้ทั่วไปที่ตารางแฮชสามารถบรรลุ O (1) ได้ แต่นั่นไม่เคยมีเหตุผลสำหรับฉัน ใครช่วยอธิบายหน่อยได้ไหม นี่คือสองสถานการณ์ที่อยู่ในใจ:

A. ค่าเป็น int ที่เล็กกว่าขนาดของตารางแฮช ดังนั้นค่าจึงเป็นแฮชของตัวเองดังนั้นจึงไม่มีตารางแฮช แต่ถ้ามีก็จะเป็น O (1) และยังไม่มีประสิทธิภาพ

B. คุณต้องคำนวณแฮชของค่า ในสถานการณ์นี้ลำดับคือ O (n) สำหรับขนาดของข้อมูลที่กำลังค้นหา การค้นหาอาจเป็น O (1) หลังจากที่คุณทำงาน O (n) แต่ก็ยังคงออกมาสู่ O (n) ในสายตาของฉัน

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

ฉันคิดว่าตารางแฮชนั้นยอดเยี่ยม แต่ฉันไม่ได้รับการกำหนด O (1) เว้นแต่จะเป็นเพียงทฤษฎีเท่านั้น

บทความของ Wikipedia สำหรับตารางแฮชอ้างอิงเวลาในการค้นหาคงที่อย่างสม่ำเสมอและไม่สนใจค่าใช้จ่ายของฟังก์ชันแฮชโดยสิ้นเชิง นั่นเป็นมาตรการที่ยุติธรรมจริงหรือ?


แก้ไข:เพื่อสรุปสิ่งที่ฉันเรียนรู้:

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

  • มันเป็นความจริงในทางปฏิบัติเพราะเมื่อเวลาผ่านไปมันจะใช้งานได้ตราบเท่าที่ฟังก์ชันแฮชและขนาดตารางถูกเลือกเพื่อลดการชนกันแม้ว่านั่นมักจะหมายถึงการไม่ใช้ฟังก์ชันแฮชเวลาคงที่


31
ตัดจำหน่าย O (1) ไม่ใช่ O (1)
kennytm

จำ O () เป็นขีด จำกัด สำหรับการดำเนินการจำนวนมาก โดยเฉลี่ยแล้วคุณจะไม่มีการชนกันหลายครั้ง - ไม่จำเป็นที่การทำงานของแต่ละบุคคลจะไม่มีการชน
Martin Beckett

ขึ้นอยู่กับการใช้งานสตริงสตริงอาจมีค่าแฮชด้วยดังนั้นค่านี้จะคงที่ ประเด็นคือมันไม่เกี่ยวข้องกับความซับซ้อนของการค้นหาแฮช
Rich Remer

@kennytm แน่นอนว่าการค้นหาเมื่อคุณแฮชอินพุตจะถูกตัดจำหน่าย O (1) แต่ค่าใช้จ่ายในการคำนวณแฮชนั้นน้อยมากหรือไม่? สมมติว่าเรากำลังแฮชสตริง - อาร์เรย์อักขระ ในการสร้างแฮชอักขระแต่ละตัวจะถูกวนซ้ำดังนั้นการแฮชสตริงคือ O (N) โดยที่ N คือความยาวของสตริง นั่นเป็นวิธีการจัดทำเอกสารสำหรับ C # และนี่คือวิธีhashCode()การใช้งานวิธีการของ Java สำหรับไฟล์String. grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/…
spaaarky21

1
@ spaaarky21 N ใน O (N) ที่คุณกำลังพูดถึงคือความยาวของสตริงซึ่งแตกต่างจาก n ขนาดของตารางแฮช คำตอบของ Mark Byer ได้กล่าวถึงเรื่องนี้แล้ว
kennytm

คำตอบ:


65

คุณมีตัวแปรสองตัวที่นี่ m และ n โดยที่ m คือความยาวของอินพุตและ n คือจำนวนรายการในแฮช

การอ้างสิทธิ์ประสิทธิภาพการค้นหา O (1) ทำให้เกิดสมมติฐานอย่างน้อยสองข้อ:

  • วัตถุของคุณสามารถเปรียบเทียบความเท่าเทียมกันได้ในเวลา O (1)
  • จะมีการชนกันของแฮชน้อย

หากอ็อบเจ็กต์ของคุณมีขนาดตัวแปรและการตรวจสอบความเท่าเทียมกันต้องดูที่บิตทั้งหมดประสิทธิภาพจะกลายเป็น O (m) อย่างไรก็ตามฟังก์ชันแฮชไม่จำเป็นต้องเป็น O (m) - สามารถเป็น O (1) ได้ ฟังก์ชันแฮชสำหรับใช้ในพจนานุกรมไม่เหมือนกับการเข้ารหัสแฮชฟังก์ชันแฮชสำหรับใช้ในพจนานุกรมไม่จำเป็นต้องดูทุกบิตในอินพุตเพื่อคำนวณแฮช การใช้งานมีอิสระที่จะดูเฉพาะจำนวนบิตคงที่

สำหรับรายการจำนวนมากเพียงพอจำนวนรายการจะมากกว่าจำนวนแฮชที่เป็นไปได้จากนั้นคุณจะได้รับการชนกันทำให้ประสิทธิภาพสูงกว่า O (1) ตัวอย่างเช่น O (n) สำหรับการส่งผ่านรายการที่เชื่อมโยงอย่างง่าย (หรือ O (n * m) หากสมมติฐานทั้งสองเป็นเท็จ)

ในทางปฏิบัติแม้ว่าการอ้างสิทธิ์ O (1) ในขณะที่เป็นเท็จทางเทคนิคนั้นเป็นความจริงโดยประมาณสำหรับสถานการณ์ต่างๆในโลกแห่งความเป็นจริงและโดยเฉพาะอย่างยิ่งสถานการณ์ที่สมมติฐานข้างต้นมีอยู่


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

1
@JeremyP: จุดดีในการเปรียบเทียบความเท่าเทียมกันของ O (m) ฉันพลาดที่ - โพสต์ที่อัปเดต ขอบคุณ!
Mark Byers

2
การO(1)อ้างสิทธิ์จะเป็นจริงหากคุณมีการแฮ็กintหรืออย่างอื่นที่ตรงกับคำของเครื่อง นั่นคือสิ่งที่ทฤษฎีส่วนใหญ่เกี่ยวกับการแฮชถือว่า
Thomas Ahle

ฉันชอบคำอธิบายของคุณ Mark ฉันยกมาในบทความของฉันเกี่ยวกับตารางแฮชในmeshfields.de/hash-tables
Steve K

3
ใน"m คือความยาวของการป้อนข้อมูล" - การป้อนข้อมูลคลุมเครือเกินไป - มันอาจหมายถึงคีย์และค่าทั้งหมดถูกใส่ แต่ก็เป็นที่ชัดเจนต่อมา (อย่างน้อยให้กับผู้ที่ได้เข้าใจหัวข้อ) คุณหมายถึงสำคัญ เพียงแค่แนะนำให้ใช้ "คีย์" ในคำตอบเพื่อความชัดเจน BTW - ตัวอย่างที่เป็นรูปธรรม - std::hashคีย์ข้อความของVisual C ++ รวมอักขระ 10 ตัวที่เว้นระยะเท่า ๆ กันตามข้อความในค่าแฮชดังนั้นจึงเป็น O (1) โดยไม่คำนึงถึงความยาวของข้อความ (แต่มีแนวโน้มที่จะชนกันอย่างหนาแน่นมากกว่า GCC!) แยกการอ้างสิทธิ์ของ O (1) มีข้อสันนิษฐานอื่น (โดยปกติถูกต้อง) ว่าmน้อยกว่าnมาก
Tony Delroy

22

คุณต้องคำนวณแฮชดังนั้นลำดับคือ O (n) สำหรับขนาดของข้อมูลที่กำลังค้นหา การค้นหาอาจเป็น O (1) หลังจากที่คุณทำงาน O (n) แต่ก็ยังคงออกมาสู่ O (n) ในสายตาของฉัน

อะไร? ในการแฮชองค์ประกอบเดียวต้องใช้เวลาคงที่ จะเป็นอย่างอื่นไปทำไม หากคุณกำลังแทรกnองค์ประกอบใช่คุณต้องคำนวณnแฮชและต้องใช้เวลาเชิงเส้น ... ในการค้นหาองค์ประกอบคุณคำนวณแฮชเดียวของสิ่งที่คุณกำลังมองหาจากนั้นค้นหาที่เก็บข้อมูลที่เหมาะสม . คุณไม่ต้องคำนวณแฮชของทุกสิ่งที่มีอยู่แล้วในตารางแฮชซ้ำ

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

ไม่จำเป็น. ที่เก็บข้อมูลไม่จำเป็นต้องเป็นรายการหรืออาร์เรย์ แต่อาจเป็นคอนเทนเนอร์ประเภทใดก็ได้เช่น BST ที่สมดุล นั่นหมายถึงO(log n)กรณีที่เลวร้ายที่สุด แต่นี่คือเหตุผลว่าทำไมการเลือกฟังก์ชันแฮชที่ดีจึงเป็นสิ่งสำคัญเพื่อหลีกเลี่ยงการใส่องค์ประกอบมากเกินไปในที่เก็บข้อมูลเดียว ดังที่ Kenny ™ชี้ให้เห็นโดยเฉลี่ยแล้วคุณจะยังคงมีO(1)เวลาแม้ว่าบางครั้งคุณจะต้องขุดเจาะถัง

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


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


ตารางแฮชไม่ใช้ BST BST ไม่ต้องการค่าแฮช แผนที่และชุดสามารถใช้เป็น BST ได้
Nick Dandoulakis

3
@ นิก: เอ๋? ไม่ ... BST ไม่ต้องการค่าแฮช ... นั่นคือประเด็น เราสมมติว่า ณ จุดนี้เรามีการชนกันแล้ว (แฮชเดียวกัน ... หรืออย่างน้อยก็ที่เก็บข้อมูลเดียวกัน) ดังนั้นเราต้องดูอย่างอื่นเพื่อหาองค์ประกอบที่ถูกต้องนั่นคือค่าที่แท้จริง
รอบ

ฉันเห็นประเด็นของคุณ แต่ฉันไม่แน่ใจว่าการผสม BST และแฮชนั้นคุ้มค่ากับปัญหา ทำไมไม่ใช้ BST?
Nick Dandoulakis

2
ฉันแค่บอกว่าคุณสามารถกำจัดสิ่งนั้นO(n)เพื่อการชนกันได้ หากคุณคาดหวังว่าจะมีการชนกันจำนวนมากแสดงว่าคุณคิดถูกแล้วอาจจะดีกว่าที่จะใช้ BST ในตอนแรก
เริ่ม

1
@ spaaarky21 ถูกต้อง แต่Nในกรณีนี้คือความยาวของสตริง เราจำเป็นต้องแฮชสตริงเพียงเส้นเดียวเพื่อกำหนดว่า 'ที่เก็บข้อมูล' ใดที่จะต้องเข้า - มันจะไม่เติบโตตามความยาวของแฮชแมป
mpen

5

แฮชมีขนาดคงที่ - การค้นหาที่เก็บแฮชที่เหมาะสมเป็นการดำเนินการแบบต้นทุนคงที่ ซึ่งหมายความว่าเป็น O (1)

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


3
การค้นหาที่เก็บแฮชคือ O (1) แต่การค้นหาคีย์ที่ถูกต้องเป็นขั้นตอน O (n) โดยที่ n ขึ้นอยู่กับจำนวนการชนกันของแฮช
Nick Dandoulakis

1
จาก 3 ขั้นตอนคำนวณแฮชค้นหาที่เก็บข้อมูลค้นหาที่เก็บข้อมูลขั้นตอนกลางคงที่? การค้นหาที่เก็บข้อมูลมักจะคงที่ การคำนวณแฮชมักจะมีคำสั่งขนาดที่ถูกกว่าวิธีอื่นในการค้นหาที่เก็บข้อมูล แต่นั่นบวกกับเวลาคงที่จริงหรือ? ในการค้นหาสตริงย่อยที่ไร้เดียงสาคุณจะพูดว่า O (n * m) สำหรับความยาวสองส่วนเหตุใดจึงไม่นำความยาวของคีย์มาที่นี่
วาดใน

การค้นหาคีย์ที่มีความยาวคงที่จะเป็นเพียง O (n) เฉพาะในกรณีที่มีการสำรองรายการไว้ตารางแฮชที่ได้รับการสนับสนุนจากต้นไม้ที่สมดุลจะเป็น O (log (n))
jk

@Jk สำหรับฟังก์ชันแฮชที่ดีกรณีที่เลวร้ายที่สุดคือเสมอlognดูคำตอบของฉันที่stackoverflow.com/questions/4553624/hashmap-get-put-complexity/…
Thomas Ahle

ความซับซ้อนของกรณีที่เลวร้ายที่สุดจะเป็น o (n) ในกรณีของการชนกัน
Saurabh Chandra Patel

3

การแฮชคือ O (1) เฉพาะในกรณีที่มีจำนวนคีย์คงที่ในตารางเท่านั้นและมีการตั้งสมมติฐานอื่น ๆ แต่ในกรณีเช่นนี้ก็มีข้อดี

ถ้าคีย์ของคุณมีการแทนค่า n-bit ฟังก์ชันแฮชของคุณสามารถใช้ 1, 2, ... n ของบิตเหล่านี้ได้ คิดถึงฟังก์ชันแฮชที่ใช้ 1 บิต การประเมินผลเป็น O (1) อย่างแน่นอน แต่คุณแบ่งพื้นที่คีย์ออกเป็น 2 เท่านั้นดังนั้นคุณจึงแมปคีย์ได้มากถึง 2 ^ (n-1) ลงในถังเดียวกัน การใช้การค้นหา BST จะใช้เวลาถึง n-1 ขั้นตอนเพื่อค้นหาคีย์เฉพาะหากเกือบเต็ม

คุณสามารถขยายสิ่งนี้เพื่อดูว่าหากฟังก์ชันแฮชของคุณใช้ K บิตขนาดถังขยะของคุณคือ 2 ^ (nk)

ดังนั้นฟังก์ชันแฮช K-bit ==> ไม่เกิน 2 ^ K ถังขยะที่มีประสิทธิภาพ ==> มากถึง 2 ^ (nK) คีย์ n-bit ต่อ bin ==> (nK) ขั้นตอน (BST) เพื่อแก้ไขการชนกัน จริงๆแล้วฟังก์ชันแฮชส่วนใหญ่ "มีประสิทธิภาพ" น้อยกว่ามากและต้องการ / ใช้มากกว่า K บิตเพื่อสร้างถังขยะ 2 ^ k ดังนั้นแม้จะมองในแง่ดี

คุณสามารถดูได้ด้วยวิธีนี้ - คุณจะต้อง ~ n ขั้นตอนเพื่อให้สามารถแยกแยะคีย์คู่ของ n บิตโดยไม่ซ้ำกันได้ในกรณีที่เลวร้ายที่สุด ไม่มีทางที่จะหลีกเลี่ยงข้อ จำกัด ของทฤษฎีข้อมูลนี้ตารางแฮชหรือไม่

อย่างไรก็ตามนี่ไม่ใช่วิธี / เมื่อคุณใช้ตารางแฮช!

การวิเคราะห์ความซับซ้อนจะถือว่าสำหรับคีย์ n-bit คุณสามารถมี O (2 ^ n) คีย์ในตารางได้ (เช่น 1/4 ของคีย์ที่เป็นไปได้ทั้งหมด) แต่ส่วนใหญ่ถ้าไม่ใช่ตลอดเวลาที่เราใช้ตารางแฮชเราจะมีจำนวนคีย์ n-bit ในตารางเท่านั้น หากคุณต้องการเพียงจำนวนคีย์คงที่ในตารางให้พูดว่า C คือจำนวนสูงสุดของคุณคุณสามารถสร้างตารางแฮชของถังขยะ O (C) ซึ่งรับประกันการชนกันคงที่ที่คาดไว้ (ด้วยฟังก์ชันแฮชที่ดี) และฟังก์ชันแฮชโดยใช้ ~ logC ของ n บิตในคีย์ จากนั้นทุกแบบสอบถามคือ O (logC) = O (1) นี่คือวิธีที่ผู้คนอ้างว่า "การเข้าถึงตารางแฮชเป็น O (1)" /

มีการตรวจจับสองสามข้อที่นี่อันดับแรกการบอกว่าคุณไม่ต้องการบิตทั้งหมดอาจเป็นเพียงเคล็ดลับการเรียกเก็บเงินเท่านั้น อันดับแรกคุณไม่สามารถส่งผ่านค่าคีย์ไปยังฟังก์ชันแฮชได้เพราะนั่นจะเป็นการย้าย n บิตในหน่วยความจำซึ่งก็คือ O (n) ดังนั้นคุณต้องทำเช่นการผ่านการอ้างอิง แต่คุณยังต้องเก็บไว้ที่ไหนสักแห่งซึ่งเป็นการดำเนินการ O (n) คุณไม่ต้องเรียกเก็บเงินจากการแฮช งานคำนวณโดยรวมของคุณไม่สามารถหลีกเลี่ยงสิ่งนี้ได้ อย่างที่สองคุณทำการแฮชค้นหาถังและพบมากกว่า 1 คีย์ ค่าใช้จ่ายของคุณขึ้นอยู่กับวิธีการแก้ปัญหาของคุณ - หากคุณทำการเปรียบเทียบตาม (BST หรือรายการ) คุณจะมีการดำเนินการ O (n) (คีย์การเรียกคืนคือ n-bit) หากคุณทำแฮชครั้งที่ 2 แสดงว่าคุณมีปัญหาเดียวกันหากแฮชครั้งที่ 2 มีการชนกัน

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


1

TL; DR: ตารางแฮชรับประกันO(1)ว่าจะเกิดกรณีเลวร้ายที่สุดหากคุณเลือกฟังก์ชันแฮชของคุณอย่างสม่ำเสมอโดยการสุ่มจากฟังก์ชันแฮชสากล กรณีที่เลวร้ายที่สุดที่คาดไว้ไม่เหมือนกับกรณีทั่วไป

ข้อจำกัดความรับผิดชอบ:ฉันไม่ได้พิสูจน์อย่างเป็นทางการว่าตารางแฮชO(1)นั้นเป็นอย่างไรลองดูวิดีโอนี้จาก coursera [ 1 ] ฉันยังไม่ได้พูดคุยเกี่ยวกับลักษณะการตัดจำหน่ายของตารางแฮช นั่นคือมุมฉากของการอภิปรายเกี่ยวกับการแฮชและการชนกัน

ฉันเห็นความสับสนอย่างมากเกี่ยวกับหัวข้อนี้ในคำตอบและความคิดเห็นอื่น ๆ และจะพยายามแก้ไขบางส่วนในคำตอบยาว ๆ นี้

การให้เหตุผลเกี่ยวกับกรณีที่เลวร้ายที่สุด

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

พิจารณา pseudocode ต่อไปนี้ของgetวิธีการของตารางแฮช ที่นี่ฉันสมมติว่าเราจัดการการชนกันโดยการผูกมัดดังนั้นแต่ละรายการของตารางจึงเป็นรายการ(key,value)คู่ที่เชื่อมโยงกัน นอกจากนี้เรายังถือว่าจำนวนถังmได้รับการแก้ไข แต่O(n)ที่nเป็นจำนวนขององค์ประกอบในการป้อนข้อมูล

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

ในฐานะที่เป็นคำตอบอื่น ๆ ที่มีออกแหลมวิ่งนี้เฉลี่ยและกรณีที่เลวร้ายO(1) O(n)เราสามารถร่างบทพิสูจน์เล็ก ๆ น้อย ๆ ได้โดยการท้าทายที่นี่ ความท้าทายมีดังนี้:

(1) คุณให้อัลกอริทึมตารางแฮชของคุณแก่ฝ่ายตรงข้าม

(2) ฝ่ายตรงข้ามสามารถศึกษาและเตรียมตัวได้นานเท่าที่เขาต้องการ

(3) ในที่สุดฝ่ายตรงข้ามจะให้ข้อมูลขนาดnเพื่อให้คุณแทรกในตารางของคุณ

คำถามคือตารางแฮชของคุณอยู่บนอินพุตของฝ่ายตรงข้ามเร็วแค่ไหน?

จากขั้นตอนที่ (1) ฝ่ายตรงข้ามรู้ฟังก์ชันแฮชของคุณ ในขั้นตอนที่ (2) ฝ่ายตรงข้ามสามารถสร้างรายการnองค์ประกอบที่เหมือนกันhash modulo mได้โดยการสุ่มคำนวณแฮชขององค์ประกอบจำนวนมาก จากนั้นใน (3) พวกเขาสามารถให้รายการนั้นแก่คุณได้ แต่ดูเถิดเนื่องจากnองค์ประกอบทั้งหมดแฮชไปยังที่เก็บข้อมูลเดียวกันอัลกอริทึมของคุณจะใช้O(n)เวลาในการสำรวจรายการที่เชื่อมโยงในที่เก็บข้อมูลนั้น O(n)กี่ครั้งที่เราลองใหม่อีกครั้งความท้าทายไม่มีศัตรูชนะเสมอและที่ว่าไม่ดีขั้นตอนวิธีการของคุณคือกรณีที่เลวร้ายที่สุด

การแฮชเป็นอย่างไร O (1)?

สิ่งที่ทำให้เราผิดหวังในความท้าทายก่อนหน้านี้คือฝ่ายตรงข้ามรู้จักฟังก์ชันแฮชของเราเป็นอย่างดีและสามารถใช้ความรู้นั้นเพื่อสร้างข้อมูลที่แย่ที่สุดเท่าที่จะเป็นไปได้ จะเกิดอะไรขึ้นถ้าแทนที่จะใช้ฟังก์ชันแฮชคงที่เสมอเรามีชุดของฟังก์ชันแฮชHซึ่งอัลกอริทึมสามารถสุ่มเลือกจากรันไทม์ได้? ในกรณีที่คุณอยากรู้อยากเห็นHเรียกว่าฟังก์ชันแฮชสากล [ 3 ] เอาล่ะเรามาลองเพิ่มการสุ่มกันดีกว่า

ก่อนอื่นสมมติว่าตารางแฮชของเรามีเมล็ดพันธุ์rด้วยและrกำหนดให้เป็นตัวเลขสุ่มในเวลาก่อสร้าง เรากำหนดครั้งเดียวแล้วแก้ไขสำหรับอินสแตนซ์ตารางแฮชนั้น ตอนนี้เรามาดูรหัสเทียมของเราอีกครั้ง

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

ถ้าเราพยายามท้าทายอีกครั้งหนึ่ง: จากขั้นตอนที่ (1) ฝ่ายตรงข้ามสามารถรู้ทุกฟังก์ชันแฮชที่เรามีในแต่ตอนนี้ฟังก์ชั่นที่เฉพาะเจาะจงกัญชาเราขึ้นอยู่กับการใช้งานH rมูลค่าของrมันเป็นส่วนตัวสำหรับโครงสร้างของเราฝ่ายตรงข้ามไม่สามารถตรวจสอบได้ที่รันไทม์หรือคาดการณ์ล่วงหน้าดังนั้นเขาจึงไม่สามารถสร้างรายการที่ไม่ดีสำหรับเราได้เสมอไป สมมติว่าในขั้นตอนที่ (2) ฝ่ายตรงข้ามเลือกหนึ่งฟังก์ชั่นhashในการHที่สุ่มจากนั้นเขาก็ฝีมือรายการnชนภายใต้hash modulo mและส่งว่าสำหรับขั้นตอน (3) นิ้วข้ามว่าที่รันไทม์H[r]จะเหมือนกันhashที่พวกเขาเลือก

นี่คือทางออกที่ร้ายแรงสำหรับปฏิปักษ์รายการที่เขาสร้างขึ้นมาภายใต้ collides hashแต่จะเป็นเพียงการป้อนข้อมูลแบบสุ่มภายใต้กัญชาฟังก์ชันอื่น ๆ Hใน ถ้าเขาชนะเดิมพันนี้เวลาทำงานของเราจะเป็นกรณีที่เลวร้ายO(n)เหมือนก่อน แต่ถ้าเขาสูญเสียแล้วดีเราก็จะได้รับการป้อนข้อมูลแบบสุ่มซึ่งจะมีค่าเฉลี่ยของO(1)เวลา และแน่นอนที่สุดครั้งที่ฝ่ายตรงข้ามจะแพ้เขาชนะเพียงครั้งเดียวในทุก|H|ความท้าทายและเราสามารถสร้างราย|H|ใหญ่ได้มาก

เปรียบเทียบผลลัพธ์นี้กับอัลกอริทึมก่อนหน้าซึ่งฝ่ายตรงข้ามชนะการท้าทายเสมอ การโบกมือที่นี่เล็กน้อย แต่เนื่องจากส่วนใหญ่แล้วฝ่ายตรงข้ามจะล้มเหลวและนี่เป็นเรื่องจริงสำหรับกลยุทธ์ที่เป็นไปได้ทั้งหมดที่ฝ่ายตรงข้ามสามารถลองได้ตามมาว่าแม้ว่าจะเป็นกรณีO(n)ที่เลวร้ายที่สุด แต่ในความเป็นจริงO(1)แล้วกรณีที่เลวร้ายที่สุดที่คาดไว้ก็คือ


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


0

มีการตั้งค่าสองแบบที่คุณจะได้รับO (1)ครั้งที่เลวร้ายที่สุด

  1. หากการตั้งค่าของคุณเป็นแบบคงที่การแฮช FKS จะทำให้คุณได้รับการรับประกันO (1) ในกรณีที่เลวร้ายที่สุด แต่ตามที่คุณระบุการตั้งค่าของคุณจะไม่คงที่
  2. หากคุณใช้การแฮชของ Cuckoo การค้นหาและการลบจะเป็น กรณีที่เลวร้ายที่สุดO (1)แต่การแทรกจะเป็นเพียงO (1) ที่คาดหวัง การแฮชของนกกาเหว่าจะทำงานได้ดีหากคุณมีขอบเขตบนของจำนวนเม็ดมีดทั้งหมดและตั้งค่าขนาดโต๊ะให้ใหญ่ขึ้นประมาณ 25%

คัดลอกมาจากที่นี่


0

ดูเหมือนว่าจากการอภิปรายที่นี่ว่าถ้า X เป็นเพดานของ (# ขององค์ประกอบในตาราง / # ของถังขยะ) คำตอบที่ดีกว่าคือ O (log (X)) โดยถือว่าการใช้การค้นหา bin มีประสิทธิภาพ


0

A. ค่าเป็น int ที่เล็กกว่าขนาดของตารางแฮช ดังนั้นค่าจึงเป็นแฮชของตัวเองดังนั้นจึงไม่มีตารางแฮช แต่ถ้ามีก็จะเป็น O (1) และยังไม่มีประสิทธิภาพ

นี่เป็นกรณีที่คุณสามารถแมปคีย์กับที่เก็บข้อมูลที่แตกต่างกันได้เล็กน้อยดังนั้นอาร์เรย์จึงเป็นตัวเลือกโครงสร้างข้อมูลที่ดีกว่าตารางแฮช อย่างไรก็ตามความไร้ประสิทธิภาพไม่ได้เติบโตขึ้นตามขนาดโต๊ะ

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

B. คุณต้องคำนวณแฮชของค่า ในสถานการณ์นี้ลำดับคือ O (n) สำหรับขนาดของข้อมูลที่กำลังค้นหา การค้นหาอาจเป็น O (1) หลังจากที่คุณทำงาน O (n) แต่ก็ยังคงออกมาสู่ O (n) ในสายตาของฉัน

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

พิจารณาสมุดโทรศัพท์: คุณอาจมีชื่อที่ค่อนข้างยาว แต่ไม่ว่าหนังสือเล่มนี้จะมี 100 ชื่อหรือ 10 ล้านชื่อความยาวของชื่อโดยเฉลี่ยจะค่อนข้างสอดคล้องกันและกรณีที่เลวร้ายที่สุดในประวัติศาสตร์ ...

สถิติโลกของกินเนสส์สำหรับชื่อที่ยาวที่สุดที่ใคร ๆ เคยตั้งโดย 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 ผู้อาวุโส

... wcบอกผมว่า 215 ตัวอักษร - ที่ไม่ยากบนผูกไว้กับความยาวของคีย์ แต่เราไม่จำเป็นต้องกังวลเกี่ยวกับการมีอย่างหนาแน่นมากขึ้น

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

นอกจากนี้ยังสามารถสร้างแฮชจากข้อมูลสำคัญในขนาดคงที่ ตัวอย่างเช่น Visual C ++ ของ Microsoft มาพร้อมกับการใช้งาน Standard Library std::hash<std::string>ซึ่งจะสร้างแฮชที่รวมเพียงสิบไบต์โดยเว้นระยะเท่า ๆ กันตามสตริงดังนั้นหากสตริงแตกต่างกันไปตามดัชนีอื่น ๆ เท่านั้นคุณจะได้รับการชนกัน (และในทางปฏิบัติพฤติกรรมที่ไม่ใช่ O (1) ในด้านการค้นหาหลังการชนกัน) แต่เวลาในการสร้างแฮชจะมีขอบเขตบนที่ยาก

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

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

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

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

แบบนี้คิดถึงประเด็น โครงสร้างข้อมูลที่เชื่อมโยงทุกประเภทในที่สุดจะต้องดำเนินการกับทุกส่วนของคีย์ในบางครั้ง (บางครั้งความไม่เท่าเทียมกันอาจถูกกำหนดจากเพียงส่วนหนึ่งของคีย์ แต่โดยทั่วไปความเท่าเทียมกันจะต้องมีการพิจารณาทุกบิต) อย่างน้อยที่สุดก็สามารถแฮชคีย์ได้ครั้งเดียวและเก็บค่าแฮชและหากใช้ฟังก์ชันแฮชที่แข็งแกร่งเพียงพอเช่น MD5 64 บิตอาจเพิกเฉยต่อความเป็นไปได้ที่คีย์สองปุ่มจะแฮชเป็นค่าเดียวกัน (บริษัท ฉันทำงานเพื่อทำอย่างนั้นสำหรับฐานข้อมูลแบบกระจาย: เวลาในการสร้างแฮชยังไม่สำคัญเมื่อเทียบกับการส่งผ่านเครือข่าย WAN) ดังนั้นจึงไม่มีประเด็นมากเกินไปเกี่ยวกับค่าใช้จ่ายในการประมวลผลคีย์นั่นคือโดยธรรมชาติในการจัดเก็บคีย์โดยไม่คำนึงถึงโครงสร้างข้อมูลและตามที่กล่าวไว้ข้างต้น - doesn '

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

ความยาวถังโดยเฉลี่ยที่ปรับสภาพบนที่เก็บข้อมูลที่ไม่ว่างเปล่าเป็นการวัดประสิทธิภาพที่ดีกว่า มันคือ / (1-e ^ {- a}) [โดยที่ a คือ load factor, e คือ 2.71828 ... ]

ดังนั้นปัจจัยการโหลดเพียงอย่างเดียวจะกำหนดจำนวนคีย์ที่ชนกันโดยเฉลี่ยที่คุณต้องค้นหาระหว่างการดำเนินการแทรก / ลบ / ค้นหา สำหรับการผูกมัดแบบแยกส่วนมันไม่เพียงเข้าใกล้ค่าคงที่เมื่อตัวประกอบโหลดต่ำ แต่มันคงที่เสมอ สำหรับการระบุที่อยู่แบบเปิดแม้ว่าการอ้างสิทธิ์ของคุณจะมีความถูกต้อง: องค์ประกอบที่ชนกันบางส่วนจะถูกเปลี่ยนเส้นทางไปยังที่เก็บข้อมูลอื่นและสามารถรบกวนการทำงานของคีย์อื่น ๆ ได้ดังนั้นที่ปัจจัยการรับน้ำหนักที่สูงขึ้น (โดยเฉพาะ> .8 หรือ .9) ความยาวของโซ่ชนกันจะแย่ลงอย่างมาก

มันเป็นความจริงในทางปฏิบัติเพราะเมื่อเวลาผ่านไปมันจะใช้งานได้ตราบเท่าที่ฟังก์ชันแฮชและขนาดตารางถูกเลือกเพื่อลดการชนกันแม้ว่านั่นมักจะหมายถึงการไม่ใช้ฟังก์ชันแฮชเวลาคงที่

ขนาดตารางควรส่งผลให้เกิดปัจจัยการโหลดที่มีเหตุผลเนื่องจากสามารถเลือกการแฮชแบบปิดหรือการผูกโซ่แยกกันได้ แต่ถ้าฟังก์ชันแฮชอ่อนไปหน่อยและคีย์ไม่สุ่มมากการมีบัคเก็ตจำนวนเฉพาะมักจะช่วยลด การชนกันด้วยเช่นกัน ( hash-value % table-sizeจากนั้นก็ล้อมรอบการเปลี่ยนแปลงเป็นบิตลำดับที่สูงหรือสองในค่าแฮชเท่านั้นที่ยังคงแก้ไขให้ที่เก็บข้อมูลกระจายแบบสุ่มหลอกไปตามส่วนต่างๆของตารางแฮช)

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