ตารางแฮชเก็บคู่คีย์-ค่าโดยใช้การแฮชเพื่อแปลงแต่ละคีย์ให้เป็นดัชนีของอาร์เรย์ วิธีนี้ทำให้ตารางกระโดดไปใกล้ตำแหน่งที่ถูกต้องได้เลย แทนที่จะต้องไล่ดูข้อมูลที่เก็บไว้ทีละรายการ
นั่นจึงเป็นเหตุผลว่าทำไมตารางแฮชมักมีเวลาใกล้เคียง สำหรับการค้นหา การแทรก และการลบบนกรณีเฉลี่ย แต่มีเงื่อนไขสำคัญคือ ฟังก์ชันแฮชต้องกระจายคีย์ได้ดีพอสมควร และตารางต้องมีกฎสำหรับจัดการการชนกันด้วย
ตารางแฮชทำอะไร
ตารางแฮชเก็บคู่คีย์-ค่า เช่น "name" -> "Ada" หรือ 42 -> "blue" โดยมีส่วนหลักอยู่ 2 ส่วน:
- อาร์เรย์ของช่องหรือบักเก็ต
- ฟังก์ชันแฮชที่แมปแต่ละคีย์ไปยังหนึ่งในช่องเหล่านั้น
ถ้าอาร์เรย์มี ช่อง คุณอาจมองว่าฟังก์ชันแฮชสร้างดัชนีตั้งแต่ ถึง
สำหรับตัวอย่างจำนวนเต็มขนาดเล็ก กฎอาจเป็น
ดังนั้นคีย์ จะไปที่ช่อง เพราะ
ฟังก์ชันแฮชจริงมักถูกออกแบบอย่างระมัดระวังกว่านี้ โดยเฉพาะสำหรับสตริงและชุดข้อมูลขนาดใหญ่ แต่แนวคิดหลักยังเหมือนเดิม คือคำนวณดัชนีอย่างรวดเร็ว แล้วตรวจสอบรายการที่เก็บอยู่ตรงนั้น
ทำไมการชนกันของแฮชจึงหลีกเลี่ยงไม่ได้
การชนกันเกิดขึ้นเมื่อคีย์ที่ต่างกันสองตัวถูกแมปไปยังช่องเดียวกัน
นี่เป็นเรื่องปกติ เพราะโดยทั่วไปตารางมีจำนวนช่องน้อยกว่าจำนวนคีย์ที่เป็นไปได้ทั้งหมด เมื่อใช้
คีย์ , และ จะถูกแมปไปที่ช่อง ทั้งหมด ดังนั้นแม้ฟังก์ชันจะทำงานตรงตามที่กำหนด ตารางก็ยังต้องแก้ปัญหาความขัดแย้งนี้อยู่ดี
ดังนั้นตารางแฮชไม่ได้รับประกันว่าจะมีช่องเฉพาะสำหรับทุกคีย์ สิ่งที่มันรับประกันคือวิธีลดขอบเขตการค้นหาให้เร็วขึ้น ตราบใดที่จัดการการชนกันได้ดี
ตัวอย่างทีละขั้น: หนึ่งตาราง หนึ่งการชนกัน
สมมติว่าตารางมี ช่อง และใช้
แทรกคีย์ , และ
ก่อนอื่น ไปที่ช่อง เพราะ
ถัดมา ก็แฮชไปที่ช่อง เช่นกัน จึงเกิดการชนกัน
จากนั้น ไปที่ช่อง ดังนั้นการแทรกครั้งนี้จึงไม่ชนกับใคร
รูปแบบที่สำคัญจะเหมือนเดิมเสมอ:
- แฮชคีย์
- ไปยังช่องที่ได้จากการแฮช
- ถ้ามีคีย์อื่นอยู่ตรงนั้นแล้ว ให้ใช้กฎจัดการการชนกัน
เมื่อเข้าใจรูปแบบนี้แล้ว กลยุทธ์หลักสองแบบในการจัดการการชนกันก็จะเข้าใจได้ง่ายขึ้น
Chaining กับ Open Addressing
Chaining เก็บบักเก็ตไว้ในแต่ละช่อง
ในแบบ chaining แต่ละช่องจะเก็บชุดข้อมูลขนาดเล็กหลายรายการ แทนที่จะเก็บได้เพียงรายการเดียว
ถ้า และ ต่างก็แฮชไปที่ช่อง บักเก็ต อาจเก็บ
ถ้าต้องการหาคีย์ ตารางก็จะกระโดดไปที่บักเก็ต แล้วเปรียบเทียบเฉพาะรายการในบักเก็ตนั้น
chaining เข้าใจได้ไม่ยากในเชิงแนวคิด และการลบมักทำได้ง่ายกว่าแบบ open addressing แต่ข้อแลกเปลี่ยนคือ ถ้าบักเก็ตยาวมาก การทำงานก็จะช้าลง
Open addressing อยู่ภายในอาร์เรย์ทั้งหมด
ในแบบ open addressing แต่ละช่องของอาร์เรย์เก็บข้อมูลได้มากที่สุดหนึ่งรายการเท่านั้น ถ้าช่องหลักเต็ม ตารางจะลองตรวจช่องอื่นตามกฎที่กำหนดไว้
กฎที่พบบ่อยอย่างหนึ่งคือ linear probing: ถ้าช่อง ถูกใช้งานอยู่แล้ว ก็ลอง จากนั้น แล้ว และวนกลับต้นอาร์เรย์หากจำเป็น
วิธีนี้ไม่ต้องมีลิสต์บักเก็ตแยกต่างหาก แต่ช่องที่ถูกเติมใกล้กันอาจสะสมจนกลายเป็นกลุ่มก้อน อีกประเด็นหนึ่งคือ การค้นหาและการลบต้องใช้กฎการ probing แบบเดียวกับที่ใช้ตอนแทรกข้อมูล
เมื่อใดที่การบอกว่า ถือว่าสมเหตุสมผล
ตารางแฮชมักถูกอธิบายว่ามีการค้นหา การแทรก และการลบเป็น โดยเฉลี่ย แต่ข้อความแบบกรณีเฉลี่ยนี้ขึ้นอยู่กับเงื่อนไขบางอย่าง:
- ฟังก์ชันแฮชกระจายคีย์ได้ดีพอสมควร
- load factor ถูกควบคุมให้อยู่ในระดับเหมาะสม
- กลยุทธ์จัดการการชนกันถูกนำไปใช้อย่างถูกต้อง
load factor คืออัตราส่วน
ถ้าตารางเต็มเกินไป การชนกันจะเกิดบ่อยขึ้น และประสิทธิภาพก็จะแย่ลง นั่นจึงเป็นเหตุผลว่าทำไมหลาย implementation จึงขยายขนาดอาร์เรย์เมื่อข้อมูลเริ่มแน่น
อย่างไรก็ตาม ประสิทธิภาพในกรณีเลวร้ายที่สุดก็ยังอาจลดลงเข้าใกล้ ได้ ถ้ามีคีย์จำนวนมากไปกองอยู่ในบริเวณเดียวกัน
ข้อผิดพลาดที่พบบ่อยเกี่ยวกับตารางแฮช
คิดว่าการชนกันแปลว่าฟังก์ชันแฮชล้มเหลว
ไม่ใช่ การชนกันเป็นสิ่งที่คาดไว้ได้ คำถามจริงคือ ตารางจัดการกับมันได้มีประสิทธิภาพหรือไม่
มองว่า เป็นจริงเสมอโดยไม่มีเงื่อนไข
มักเป็นคำอธิบายสำหรับกรณีเฉลี่ย ไม่ใช่คำสัญญาสำหรับทุกอินพุตและทุกช่วงเวลา
สับสนระหว่างการแฮชในที่นี้กับการเข้ารหัส
ฟังก์ชันแฮชในบริบทของโครงสร้างข้อมูลพื้นฐานมีเป้าหมายหลักคือการทำดัชนีอย่างรวดเร็ว ไม่ใช่การปกปิดข้อมูล ทั้งสองอย่างนี้มีเป้าหมายต่างกัน
ลืมเปรียบเทียบคีย์จริง
คีย์สองตัวอาจให้ผลแฮชเหมือนกันได้ หลังจากไปถึงบักเก็ตหรือจุด probing ที่ถูกต้องแล้ว ตารางก็ยังต้องตรวจสอบว่าคีย์ตรงกันจริงหรือไม่
ตารางแฮชถูกใช้เมื่อใด
ตารางแฮชถูกใช้ทุกครั้งที่การค้นหาด้วยคีย์อย่างรวดเร็วเป็นเรื่องสำคัญ ตัวอย่างที่พบบ่อยได้แก่ dictionary, symbol table, cache, indexing และการนับความถี่ของข้อมูล
มันเหมาะมากเมื่อการค้นหาแบบตรงตัวด้วยคีย์สำคัญกว่าการเก็บข้อมูลให้เรียงลำดับ ถ้าคุณต้องการการไล่ดูข้อมูลตามลำดับ การค้นหาเป็นช่วง หรือหาค่าที่ใกล้ที่สุด โครงสร้างข้อมูลแบบอื่นอาจเหมาะกว่า
ลองทำตัวอย่างการชนกันที่คล้ายกัน
ใช้อาร์เรย์ขนาดเล็กที่มี ช่อง และใช้ แทรกคีย์สักสองสามตัว เช่น , , และ จากนั้นจัดการการชนกันหนึ่งครั้งด้วย chaining และอีกครั้งด้วย linear probing
แบบฝึกหัดเดียวนี้ช่วยให้เห็นแนวคิดหลักได้ชัดเจนอย่างรวดเร็ว: การชนกันเป็นสิ่งที่หลีกเลี่ยงไม่ได้ และกฎจัดการการชนกันจะเปลี่ยนพฤติกรรมของตาราง ถ้าคุณอยากต่อยอด ลองเปลี่ยนขนาดตารางแล้วดูว่ารูปแบบการชนกันเปลี่ยนไปอย่างไร
ต้องการความช่วยเหลือในการแก้โจทย์?
อัปโหลดคำถามของคุณแล้วรับคำตอบแบบทีละขั้นตอนที่ผ่านการตรวจสอบในไม่กี่วินาที
เปิด GPAI Solver →