!"#
แผนการสอนประจําบทเรียน รายชือ่ อาจารยผจู ดั ทํา สมชาย ประสิทธิจ์ ตู ระกูล หัวขอของเนื้อหา ตอนที่ 12.1 แนวคิดและฟงกชนั แฮช (1 คาบ) เรือ่ งที่ 12.1.1 แนวคิด เรือ่ งที่ 12.1.2 ฟงกชันแฮช ตอนที่ 12.2 การชนและการแกไขปญหาการชน (2 คาบ) เรือ่ งที่ 12.2.1 ปฏิทรรศนวนั เกิด เรือ่ งที่ 12.2.2 การแฮชแบบเปด เรือ่ งที่ 12.2.3 การแฮชแบบปด แนวคิด 1. ตารางแฮชเปนโครงสรางที่มีประสิทธิภาพสําหรับการจัดเก็บขอมูลเพื่อการคนหา การเพิม่ และการลบขอมูล โดยไมมีการดําเนินการที่เกี่ยวกับอันดับของขอมูล 2. ตารางแฮชอาศัยฟงกชันการคํานวณเลขที่อยูของขอมูล ที่เรียกวาฟงกชันแฮช ซึ่งมีหนาที่ แปลงคียของขอมูลไปเปนเลขที่อยูในชองในตารางแฮช 3. ฟงกชันแฮชที่ดีจะใชสามารถคํานวณเลขที่อยูไดรวดเร็ว มีพฤติกรรมกระจายอยางสม่ําเสมอ และแปลงคียไปเปนเลขที่อยูของตารางที่มีขนาดเล็ก 4. การชนคือปญหาที่คียสองคียที่ตางกัน เมื่อนําไปผานฟงกชันแฮชหนึ่งแลวไดเลขที่อยูเดียวกัน ในตารางแฮช 5. ปฏิทรรศนวนั เกิดเปนเหตุการณทแ่ี สดงใหเห็นวา ถึงแมฟงกชันจะกระจายอยางสม่ําเสมอ ก็มี โอกาสทีจ่ ะเกิดการชนของขอมูลได แมกับปริมาณขอมูลที่ไมมากนัก 6. การแกไขปญหาการชนมีแบบเปดและแบบปด 7. การแฮชแบบเปดอาศัยการจัดเก็บขอมูลที่ชนไวในรายการโยงเดียวกัน 8. การแฮชแบบปดอาศัยการหาชองวางในตารางชองใหมเพื่อจัดเก็บขอมูล เมื่อขอมูลถูกแฮชไป ชนกับขอมูลเดิมที่เก็บในตาราง
!"$ 9. วิธีการหาชองวางใหมในการแฮชแบบปดมีแบบ linear probing, quadratic probing, double hashing เปนตน วัตถุประสงค หลังจากศึกษาบทเรียนที่ 12 แลว นักศึกษาสามารถ 1. 2. 3. 4.
เขาใจแนวคิดการจัดเก็บขอมูลในตารางแฮช เขาใจจุดประสงคและสามารถออกแบบฟงกชันแฮชได เขาใจและเปรียบเทียบวิธีการแกไขปญหาการชนของขอมูลตางๆ เขียนโปรแกรมสรางและใชการดําเนินการตางๆ ของการแฮชได
กิจกรรมการเรียนการสอน กิจกรรมทีน่ กั ศึกษาตองทําสําหรับการเรียนการสอน ไดแก 1. ศึกษาเอกสารชุดวิชา/โฮมเพจชุดวิชา ตอนที่ 12.1 และตอนที่ 12.2 2. ทํากิจกรรมของบทเรียนที่ 12 3. ทําแบบประเมินผลของบทเรียนที่ 12 เอกสารประกอบการสอน 1. เอกสารชุดวิชา สื่อการสอน 1. โฮมเพจชุดวิชา 2. สไลดประกอบการบรรยาย (Powerpoint) 3. โปรแกรมคอมพิวเตอร ประเมินผล 1. ประเมินผลจากกิจกรรมที่ทํา 2. ประเมินผลจากคําถามทายบทเรียน
!"% ตอนที่ 12.1 แนวคิดและฟงกชนั แฮช หัวเรื่อง เรือ่ งที่ 12.1.1 แนวคิด เรือ่ งที่ 12.1.2 ฟงกชันแฮช แนวคิด 1. ตารางแฮชเปนโครงสรางที่มีประสิทธิภาพสําหรับการจัดเก็บขอมูลเพื่อการคนหา การเพิม่ และการลบขอมูล โดยไมมีการดําเนินการที่เกี่ยวกับอันดับของขอมูล 2. ตารางแฮชอาศัยฟงกชันการคํานวณเลขที่อยูของขอมูล ที่เรียกวาฟงกชันแฮช ซึ่งมีหนาที่ แปลงคียของขอมูลไปเปนเลขที่อยูในชองในตารางแฮช 3. ฟงกชันแฮชที่ดีจะใชสามารถคํานวณเลขที่อยูไดรวดเร็ว มีพฤติกรรมกระจายอยางสม่ําเสมอ และแปลงคียไปเปนเลขที่อยูของตารางที่มีขนาดเล็ก วัตถุประสงค หลังจากที่ศึกษาตอนที่ 12.1 แลว นักศึกษาสามารถ 1. เขาใจแนวคิดการจัดเก็บขอมูลในตารางแฮช 2. เขาใจจุดประสงคและสามารถออกแบบฟงกชันแฮชได เรื่องที่ 12.1.1 แนวคิด จะวาไปแลวตนไมคนหาแบบทวิภาคนั้น เปนโครงสรางขอมูลทีร่ องรับความตองการสารพัดรูปแบบที่ มีประสิทธิภาพดี ไมวาจะเปนการเพิ่ม ลบ คนหา คนหาตัวนอยสุด ตัวมากสุด ตัวถัดไป หรือแมกระทั่งไลเรียง ขอมูลจากนอยไปมาก ก็กระทําไดอยางมีประสิทธิภาพ แตถาเราจํากัดความตองการเหลือแคการเพิ่ม ลบ และ คนหา โดยไมมกี ารดําเนินการทีเ่ กีย่ วของกับอันดับของขอมูลเลย เราสามารถใชโครงสรางขอมูลอีกแบบหนึ่ง ที่เรียกวาตารางแฮช (hash table) ซึ่งมีประสิทธิภาพที่เหนือกวา แนวคิดของตารางแฮชนี้ถูกนําเสนอโดย Dumey ในป ค.ศ. 1956 ! สําหรับปญหาตารางสัญลักษณ (symbol table) ที่ใชเก็บชื่อตัวแปร และชื่อฟงกชัน ตางๆ ของโปรแกรมระหวางการแปล (compile) โปรแกรมคอมพิวเตอร สมมติวาเราตองการเก็บขอมูลของนักศึกษาจํานวน 700 คน โดยใชรหัสประจําตัวเปนคียในการคน หา (กําหนดใหรหัสประจําตัวเปนตัวเลข 10 หลัก) ถาเราใชตนไมคนหาแบบทวิภาคจัดเก็บก็จะไดตนไมซึ่งมี ความสูงอยางนอย log2 700 = 9 แสดงวากรณีดที ส่ี ดุ (นัน่ คือไดตน ไมทไ่ี ดดลุ สูง 9) การเพิม่ ลบ และคน หาในตนไมนี้มีการวิ่งผานโหนดตางๆ ในตนไมไมเกิน 9+1 = 10 โหนด ประสิทธิภาพการทํางานถูกกําหนด !
A. L. Dumey, Computers and Automation 5 (1956) 6–9.
!!& ดวยความสูงของตนไม โครงสรางของตนไมนั้นจํากัดใหตนไมตองสูงอยางนอย log2 n จะทําใหเตี้ยกวานี้ก็ คงทําไมได 2 ถาเรามีความทะเยอทะยานตองการใหประสิทธิภาพของการเพิม่ ลบ และคนหาดีกกวานี้ จะทําอยาง ไร เชนตองการใชเวลาคงตัวเสมอในการเพิม่ ลบ และคนหาขอมูล โดยไมขึ้นกับจํานวนขอมูล (จะ ทะเยอทะยานเกินไปหรือเปลา ขอใหลองคิดดูเลนๆ ตอนนีก้ อ น) ใหสังเกตวาโครงสรางของตนไมนั้นบังคับ การเขาถึงขอมูลวาจะตองกระทําผานเปนดานๆ จากรากวิง่ เลือ้ ยลงมาเรือ่ ยๆ จึงเปนสาเหตุใหใชเวลาไมคงตัว ถาเราเปลี่ยนมาใชตาราง (ซึง่ ก็คอื แถวลําดับหรืออะเรยนน่ั เอง) เก็บขอมูล แลวอาศัยการคํานวณหาเลขทีอ่ ยู ของขอมูลในตารางเอาเลย คือใชสูตรหรือฟงกชันพิเศษ h(x) ทีส่ ามารถเปลีย่ น x ซึ่งคือคียของขอมูลไปเปน เลขที่อยูของขอมูลในตาราง ปญหาที่ทาทายก็คือจะหาฟงกชันนี้ไดอยางไร ซึง่ สามารถคํานวณหาเลขทีอ่ ยูไ ด ในเวลาคงตัว โดยฟงกชันนี้ตองเปนแบบหนึ่งตอหนึ่งทั่วถึง (bijective function) นัน่ คือถาคียต า งกัน เลขทีอ่ ยู ที่ไดจากฟงกชันนี้ตองตางกันดวย จะวาไปแลวฟงกชนั คํานวณเลขทีอ่ ยูท เ่ี ราตองการนัน้ หาไดไมยากเลย เชนฟงกชัน h(x) = x เปน ฟงกชันที่หาเลขที่อยูไดตามที่ตองการ h(x) = x ใหความหมายวาใหใชคาของคียมาเปนตัวระบุเลขที่อยูของ คียนั้น เชนขอมูลของนักศึกษารหัส 4230010121 ก็ใหจัดเก็บไวที่ชองที่ 4230010121 ของตาราง สูตรนีง้ า ย แตสรางปญหามากมายตามมา เพราะเราตองจองตารางขนาดมหึมาเพื่อเก็บขอมูลของนักศึกษาเพียง 700 คน (รหัสประจําตัวเปนเลขฐานสิบขนาดสิบหลัก ก็ตองจองตารางขนาด 1010 ชอง !!!) เพื่อใหแนวคิดการใชฟงกชันคํานวณเลขที่อยูของขอมูลเปนไปไดในทางปฏิบัติ จึงตองจํากัดใหตา รางที่เก็บขอมูลนั้นตองมีขนาด “ไมใหญมาก” ความจริงนาจะเขียนวา “ตารางมีขนาดเทากับจํานวนขอมูล” แตไมกลา เพราะจะบีบกันขนาดนี้อาจหาฟงกชันที่ตองการยาก ลองมาดูตัวอยางรหัสประจําตัวของนักเรียน สัก 10 คนที่เราสุมตัวอยางมา (จาก 700 คน) ขางลางนี้ 3933324521, 3930852021, 3931458621, 3932799821, 3930127721 3931053021, 3930883421, 3931835521, 3931569621, 3931960221 เห็นไดชัดวามีตัวเลขหลายๆ หลักในรหัสประจําตัวที่เหมือนกัน (ความจริงนักศึกษาทั้ง 150 คนนี้ เปนนักศึกษาคณะวิศวะฯ ซึง่ เขาเรียนในป 2539 ทุกคนมีตัวเลข 5 หลักที่ถูกตัดออกนี้เหมือนกันหมด) เพียง แคตัดทิ้งไปไมนํามาพิจารณา ก็จะไดฟงกชัน h(x) ซึง่ ตองการตารางขนาดเล็กกวา เชนเราตัดสองหลักขวาที่ เปน 21 และ 3 หลักซายทีเ่ ปน 393 ออก ไดเปนฟงกชนั ขางลางนี้
h(x) = x / 100 mod 105 ก็จะเหลือรหัสเพียง 5 หลัก จึงตองใชตารางขนาดหนึ่งแสนชอง ถึงแมวาจะมีขนาดเล็กกวา 1010 ชองมากมาย แตกย็ งั ถือวาสิน้ เปลืองเหลือเกิน จองไวแสน ใชแคเจ็ดรอย แลวเราจะหาฟงกชันอื่นที่ตองการขนาดตารางที่
2
อาจทําใหเตี้ยลงไดถาเปลี่ยนเปนตนไมคนหาแบบ m ภาค นั่นคือหนึ่งโหนดมีลูกไดไมเกิน m ลูก แตอยางไรก็ตามตนไมกย็ งั สูง เปน O( log m ) อยูด ี
!!" นอยกวาไดหรือไม ถาดูจากนักศึกษา 10 คนที่เราสุมตัวอยางมาจะเห็นไดวาหลักจากตัดเลขออก 5 ตัวแลว จะไดรหัสตางๆ ดังนี้ 33245, 08520, 14586, 27998, 01277, 10530, 08834, 18355, 15696, 19602 ถาเราสนใจเลข 3 หลักทางขวาก็จะเห็นไดวาไมเหมือนกันเลย แตเราคงดวนสรุปวาใหตดั ออกอีก 2 หลักทาง ซาย แลวใชสามหลักที่เหลือเปนเลขที่อยู ก็หายจะเร็วเกินไป เพราะนีเ่ ปนเพียง 10 คนจาก 700 เราจะไป ทราบไดอยางไรวาจะไดคาที่ไมซ้ํากัน (ถาไดก็ดีเพราะจะใชตารางแคหันชองเพื่อเก็บขอมูล 700 คน ซึ่งก็ถือ วาคุมทีเดียว กับการที่เราสามารถหาตําแหนงของขอมูลไดในเวลาคงตัว) ถาเราทราบรหัสของนักศึกษาทัง้ 700 คน ก็อาจนํามาพิจารณาหาสูตรที่คํานวณเลขที่อยูซึ่งใชขนาด ของตารางที่ไมใหญมากได (แตคงเหนื่อยมากๆ ถาดูดวยตา เขียนโปรแกรมชวยก็อาจเปนไปได) แตประเด็น ของการเก็บขอมูลอยูตรงที่วาเรารูเพียงชวงที่เปนไปไดของคีย แตเราไมทราบคาของคียตางๆ ในชุดขอมูลที่ จะมาจัดเก็บ เชนเราเพียงแตรวู า รหัสประจําตัวเปนเลข 10 หลัก เมือ่ ตัดเลขซ้าํ ออกแลวเหลือ 5 หลัก มีคาเปน ไปไดตั้งแต 00000 ถึง 99999 แตเราไมทราบหรอกวา หมายเลขใดในชวงนี้จะถูกจัดเก็บ ปญหาจึงมีอยูวาจะ ออกแบบฟงกชันคํานวณเลขที่อยูไดอยางไร แฮชชิง (hashing) ใชแนวคิดของการใชฟงกชันคํานวณเลขที่อยูที่ไดเสนอมา เพียงแตคราวนี้ อนุญาตใหฟงกชันคํานวณเลขที่อยูนี้เปนแบบ many-to-one ได นั่นคือคียสองคียที่ตางๆ กันมีเลขที่อยูเหมือน กันได (เรียกวาเกิด “ชน” กันของคีย) อานแลวก็คงเอะใจวา การอนุญาตเชนนีย้ อ มสรางปญหาแนนอนเพราะ หนึ่งชองในตารางนั้นเก็บขอมูลไดแคตัวเดียว แลวจะทําอยางไร แฮชชิงอาศัยความจริงที่วาถากําหนดใหตา รางเก็บขอมูลไมใหญมาก การออกแบบฟงกชนั คํานวณเลขทีอ่ ยูเ พือ่ ไมใหเกิดปญหาการชนดังกลาวเลยเปน เรือ่ งยาก (ถาเราไมรชู ดุ ขอมูลกอน) และคงเปนไปไมไดดวย (จะไดนาํ เสนอในรายละเอียดตอไป) จึงควรให ความสนใจวา จะออกแบบฟงกชันคํานวณเลขที่อยูนี้อยางไรจึงจะบรรเทาหรือลดจํานวนการชนใหนอยๆ และ เมือ่ เกิดการชนจะแกปญ หาอยางไร ซึง่ จะไดนาํ เสนอตอไป จุดเดนของแฮชชิงอยูตรงที่วา เราสามารถควบ คุมเวลาการทํางานของการเพิม่ ลบ และคนหา ใหอยูภายในขอบเขตที่ตองการได โดยใชเนื้อที่หนวยความจํา แลกกับเวลาการทํางาน ซึง่ เปนปรากฏการณทไ่ี มเกิดขึน้ กับการจัดเก็บขอมูลแบบรายการหรือตนไม เรื่องที่ 12.1.2 ฟงกชันแฮช แฮชชิงใชแนวคิดของการใชฟงกชันคํานวณเลขที่อยูที่เรียกวาฟงกชันแฮช (hash function) ซึ่งมี พฤติกรรมที่แลดู “มั่วๆ” “สุมๆ” “เละๆ” หมายความวาเมื่อดูจากคาของ h(x) แลวแทบจะดูไมเห็นความ สัมพันธอะไรเลยกับ x ซึ่งเปนคีย (พฤติกรรมเชนนี้เองซึ่งเปนที่มาของคําวา “hash” นักศึกษาที่ยังไมรูวาคํา ภาษาอังกฤษสัน้ ๆ นีแ้ ปลวาอะไรก็อยากใหเปดพจนานุกรมตอนนีเ้ ลย) มาดูความหมายอยางเปนทางการกันสักหนอย กําหนดใหคียตางๆ เปนจํานวนเต็มในชวง [0, U–1] และ m คือขนาดของตารางซึ่งเรียกวาตารางแฮช (hash table) ฟงกชันแฮชมีหนาที่แปลงจํานวนเต็มในชวง [0, U–1] เปนจํานวนเต็มในชวง [0, m–1] ฟงกชันแฮชที่เราตองการจะตองมีคุณสมบัติดังนี้ 1. คํานวณเลขทีอ่ ยูไ ดรวดเร็ว ในเวลาคงตัว (โดยทั่วไปใชเวลาแปรตามขนาดของคีย ไมใชจํานวน คีย)
!!! 2. ลดจํานวนการชน (ไมมีการชนเลยไดยิ่งดี) แตเนื่องจากจํานวนการชนจะมีมากมีนอยก็ขึ้นกับ เซตของคียตางๆ ทีถ่ กู เลือกมาเก็บ จึงตีความแบบรวมๆ วาโอกาสที่แตละคียจะไปตกอยูใน ชองใดในตารางมีเทาๆ กัน (เรียกคุณสมบัตเิ ชนนีว้ า simple uniform hashing) ขอขยายความในเรื่องของ simple uniform hashing ถา P(x) คือความนาจะเปนที่คีย x จะถูกเลือก มาจากเซตของคียที่เปนไปไดทั้งหมด {0,1,2,...,U -1} และ s! คือเซตของคียทั้งหมดซึ่งเมื่อถูกแฮชแลวจะไป ตกที่ชอง j นัน่ คือ s! = { x | h(x) = j } ฟงกชันแฮช h(x) จะเปน simple uniform hashing เมื่อ $ # #" = # %!
! "
สําหรับ j = 0, 1, ..., m –1
คิดกรณีงา ยๆ และใชตัวอยางประกอบก็แลวกัน สมมติวาใชรหัสประจําตัว 10 หลักเปนคีย คาของ คียท เ่ี ปนไปไดกค็ อื 0000000000 ถึง 9999999999 (นัน่ คือ U = 1010) กําหนดใหทุกๆ คาของคียเหลานี้มีสิทธิ์ ถูกเลือกมาเก็บไดเทาๆ กัน หมายความวา P(x) มีคาเทากัน (ซึง่ ก็คอื เทากับ 1/1010) ถาเรานําคียท เ่ี ปนไปได ทุกๆ คีย (คือลุยตั้งแตคีย 0000000000 ถึง 9999999999) มาผาน h(x) ไดผลซึง่ ระบุเลขทีอ่ ยูข องตาราง จะได วาแตละชองในตารางจะมีคียที่อยากมาอยูเปนจํานวนเทาๆ กัน หมายความวาการ “โปรย” คียตางๆ ลงตา รางดวย h(x) นัน้ กระจายอยางสม่าํ เสมอดีมาก สมมติตอวาถาชุดของคียที่มาเก็บมี n ตัว โดยที่ n < m และ n << U (อานเครือ่ งหมาย << นี้วานอยกวามากๆ) เชน m = 1000, n = 700 คีย 700 ตัวที่ถูกเลือกมาจาก 1010 คาทีเ่ ปนไปไดนน้ั เมื่อนํามาโปรยลงตารางแฮชโดยใช h(x) ก็นา จะชนกันนอย (หรือวาไมชนกันเลย ?) ขาวรายก็คือวาในทางปฏิบัติเรามักไมรูคา P(x) ทําใหการออกแบบฟงกชันแฮชที่ใหไดผลกระจาย อยางสม่าํ เสนอตามทีต่ อ งการนัน้ เห็นจะเปนไปไดยาก อยางไรก็ตามมีกลวิธีตางๆที่ใชสําหรับออกแบบ ฟงกชันแฮชที่ไดดีในทางปฏิบัติ ดังนี้ 3 1. การวิเคราะหเลขโดด (digit analysis) : กลวิธนี ก้ี ค็ อื การคัดเลือกเลขโดดบางหลักของคียม า พิจารณา ดังตัวอยางที่ไดนําเสนอมาในตอนตน (กรณีตดั สองหลักขวาทีเ่ ปน 21 และ 3 หลัก ซายทีเ่ ปน 393 ออกจากรหสประจําตัวนักศึกษาทีน่ าํ เสนอในหนาที่ 220 ) โดยเราตองมีความ มั่นใจวาสิ่งที่ตัดไปนั้นไมมีผลทําใหเกิดความเอนเอียงในการกระจายของคีย 2. การพับ (folding) : กลวิธนี บ้ี างครัง้ ก็เรียกกันวาเปนการบีบอัดคีย โดยใชแนวคิดในการแบงคีย ออกเปนสวนๆ แลวนําสวนตางๆ เหลานีม้ า "รวม" กัน การรวมในที่นี้อาจหมายถึงการนําสวน ตางๆ ที่ไดจากการแบงมาบวกกัน หรือ exclusive or กันเปนตน ลักษณะการแบงแลวรวมเขน กันแลดูคลายการนําคียยาวๆ มาพับหลายๆ ทบใหมีขนาดเล็กลง ตัวอยางเชนเราจะแบงคีย ออกเปนสวนๆ สวนละ 3 หลัก แลวนําทุกๆสวนมาบวกกัน ถา x = 2217365 ก็จะไดเปน 221+736+5 = 962
3
ตองบอกตรงนี้เลยวากลวิธีการออกแบบฟงกชันใหแลดู "เละๆ" "มัว่ ๆ" เหลานี้ใชไดดีในทางปฏิบัติ มีการวิเคราะหเชิง คณิตศาสตรกนั มากมายแตกต็ ง้ั อยูบ นสมมติฐานตางๆ นาๆ ทีจ่ ะไมขอกลาวในรายละเอียดในวิชานี้
!!' 3. กลางกําลังสอง (midsquare) : กลวิธนี ส้ี รางความ "มั่ว" ใหกับคียอยางเห็นไดชัด นัน่ คือเรานํา คีย x มายกกําลังสอง แลวตัด k ทางซายและทางขวาทิ้ง เหลือแตตรงกลางนํามาใชเปนผลลัพธ เชน x = 2217365 ยกกําลังสองได 4916707543225 ให k = 4 ก็คอื ตัดสีห่ ลักขวาและสีห่ ลัก ซายทิ้งไป นัน่ คือ 4916707543225 ไดเปน 70754 ดังนั้นสามารถบรรยายฟงกชันไดในรูปแบบ h(x) = (x2 div 10k) mod 10k 4. การหาร (division) : กลวิธีนี้ใชการหารคียดวยคาคงตัว p แลวเอาเศษเปนผลลัพธนน่ั คือ h(x) = x mod p ถาจองตารางใหมีขนาดเปน p ดวย ก็จะสบายใจไดวา x mod p ตองไดผลตกอยูใน ชวงของตารางที่จองไวแนนอน (เพราะ x mod p ตองไดผลในชวง [0, p-1] ) โดยทั่วไปมักหลีก เลี่ยงไมใช p ทีเ่ ปนจํานวนในรูปแบบ 2k เพราะนัน่ หมายความเศษของการหารก็คอื k บิตทาง ขวาของคีย หรือในกรณีที่คียมีความหมายเปนเลขฐานสิบ ก็มกั หลีกเลีย่ ง p ในรูปแบบ 10k ซึง่ ผลการหารคือ k หลักทางขวาเชนกัน ทั้งนี้เพราะวาเรานาจะใชทุกๆ หลักของคียมาพิจารณา (ตรงนี้อยูใตสมมติฐานวาทุกหลักมีความหมายที่เราไมตัดทิ้ง) คา p ที่ดีที่มักใชกันก็คือ p ทีเ่ ปน จํานวนเฉพาะซึ่งมีคาไมใกลกับจํานวนที่อยูในรูปแบบ 2k 5. การคูณ (multiplication) : กลวิธีนี้อาศัยการคูณคียดวยจํานวนจริง z ที่มีคาระหวาง [0,1) จาก นัน้ จึงนําไปคูณกับจํานวนเต็ม m ซึ่งแทนขนาดของตารางแฮช บรรยายไดดงั นี้ h(x) = trunc( m * fract( x * z ) ) ผลลัพธที่ไดจะตกอยูในชวง 0 ถึง m -1 ไดมกี ารศึกษากันวาคา z ที่ใหผลดีก็คือ ( 5 -1)/2 ≈ 0.618 ผูออกแบบฟงกชันแฮชสามารถใชกลวิธีตางๆ ขางตนหลายๆ รูปแบบผสมผสานกันก็ได แตอยาลืม วาการคํานวณของฟงกชันแฮชจะตองกระทําไดรวดเร็ว และผลที่ไดตองอยูในชวง 0 ถึง m -1 โดยที่ m คือ ขนาดของตาราง ไดมีการศึกษาวิเคราะหรูปแบบของฟงกชันแฮชกันอยางมากมาย ถึงแมเราจะเลือกใช ฟงกชันแฮชแบบใดก็ตาม ก็ยอมตองมีชุดของขอมูลที่ทําใหเกิดการชนมากมายได (เพราะถาเรารูว า ฟงกชนั แฮชเปนอยางไร และตองการแกลงฟงกชันแฮชนั้น ก็ยอมหาขอมูลที่แกลงใหเกิดการชนไดแนๆ) วิธีหนึ่งที่ หลีกเลี่ยงปญหาดังกลาวก็คือการปองกันการใชฟงกชันที่มีรูปแบบไมตายตัว โดยที่ใชกันไดดีในทางปฏิบัติ เปนดังนี้ h(x) = ((ax + b) mod p ) mod m k เปนจํานวนเต็มในชวง [0, U–1] m เปนขนาดของตารางแฮช p เปนจํานวนเฉพาะที่มีคาในชวง [U, 2U ) a และ b เปนคาคงตัวโดยที่ 0 < a < p และ 0 ≤ b < p จุดที่อนุญาตใหเราปองกันไมใหมีใครมา เดาฟงกชันแฮชไดก็คือคาของ a และ b โดยเรากําหนดให a และ b มีคา เปนจํานวนสุม โดยตอนเริ่มตนการ ใชงานตารางแฮชก็สุมคาทั้งสอง จากนั้นก็ใชคานั้นไปตลอดการทํางานของตารางแฮชนั้น ดวยกลวิธีแบบนี้ทํา ใหใครก็ตามที่ตองการใสขอมูลขาเขาที่แกลงใหการชนบอยๆ จะไดทํางานชาๆ นัน้ กระทําไมได เพราะคา a
!!( และ b ที่ใชเปลี่ยนทุกครั้งที่โปรแกรมเริ่มทํางาน โปรแกรม) 4
(แตพอตั้งคาแลวก็เปนคาคงตัวตลอดการทํางานของ
สําหรับกรณีที่คียของขอมูลไมใชตัวเลข เชนชื่อคน ชือ่ เครือ่ งคอมพิวเตอร ชือ่ โดเมนในอินเตอรเนต เปนตน ก็สามารถแปลงเปนตัวเลขไดโดยการมองสตริงของตัวอักษรเสมือนตัวเลขฐานสูงๆ เชนฐาน 26 (กรณี เปนสตริงของตัวอักษรภาษาอังกฤษ) ตัวอยางเชน ‘BARNEY’ เมือ่ มองเปนฐาน 26 ก็จะไดเปน 5 ord(‘B’)*265 + ord(‘A’)*264 + ord(‘R’)*263 + ord(‘N’)*262 + ord(‘E’)*261 + ord(‘Y’)*260 = 66*265 + 65*264 + 82*263 + 78*262 + 69*261 + 89*260 = 815370099 นักศึกษาเห็นวิธีแปลงขางบนนี้คงรูสึกวายุงยาก ตองมีการยกกําลังมากมายซ้ําซาก เสียเวลา อีกทัง้ ถาสตริงมีความยาวมาก คงไดผลลัพธที่มีขนาดใหญเกินขนาดของจํานวนเต็มที่รับไดในภาษาปาสกาล เรา สามารถจัดรูปแบบการคํานวณใหกระทัดรัดขึน้ ไดดงั นี้ 1. เลือกพิจารณาฐานในรูปแบบทีง่ า ยตอการคํานวณเชนฐาน 32 (เนือ่ งจากการคูณจํานวนใดดวย 32 ก็คอื การเลือ่ นบิตไปทางซาย 5 บิต) 2. ใช Horner’s rule ในการจัดรูปแบบใหม ซึ่งอาศัยการแยกตัวประกอบตัวคูณตางๆ (ซึง่ ก็คอื เลข ฐาน) ออกเปนวงเล็บซอนวงเล็บ (ซึ่งงายตอการเขียนโปรแกรมดวยวงวน) ตัวอยางเชนสตริง ‘BABY’ ถาใช Horner’s rule แปลงเปนจํานวนเต็มโดยมองเปนฐาน 32 จะไดเปน ( ( ord(‘B’) * 26 + ord(‘A’) ) * 26 + ord(‘B’) ) * 26 + ord(‘Y’) 3. ใชกลวิธีการหารเอาแตเศษ ( mod ) ดวยขนาดของตารางแฮชภายในแตละวงเล็บระหวางการ คํานวณดวย Horner’s rule เพื่อปองกันมิใหผลลัพธมีคาใหญเกินไป และก็ไมไดทําใหผลลัพธ ของการคํานวณแตกตางไปจากการไมใช Horner’s rule แลวคอยมา mod ตอนสุดทายแต อยางไร (จากความรูในเรื่อง congruence ที่ขอจะไมอธิบายในที่นี้) ดวยวิธีทั้งสามขางตนเขียนไดเปนฟงกชันการแปลงสตริงเปนตัวเลขดังนี้ FUNCTION Hash( Key : String ) : integer; VAR H, I, Len : integer; BEGIN Len := Length( Key ); H:= ord( key[1] ); FOR I := 2 to len DO H := ( H * 32 + ord( x[i] ) ) MOD TableSize; Hash := H; END;
4
5
รูปแบบของฟงกชนั แฮชดังกลาว ซึง่ อนุญาตใหเราเปลีย่ นคาของ a และ b ไดมากมายนีเ้ รียกวาการแฮชเชิงจักรภพ (universal hashing) จึงจะไมขออธิบายและวิเคราะหในที่นี้ ในภาษาปาสกาล ord จะคืนคา ASCII ของตัวอักษรทีไ่ ดรบั เชนรหัส ASCII ของ ‘B’ คือ 42H = 6510
!!) ตอนที่ 12.2 การชนและการแกไขปญหาการชน หัวเรื่อง เรือ่ งที่ 12.2.1 ปฏิทรรศนวนั เกิด เรือ่ งที่ 12.2.2 การแฮชแบบเปด เรือ่ งที่ 12.2.3 การแฮชแบบปด แนวคิด 1. การชนคือปญหาที่คียสองคียที่ตางกัน เมื่อนําไปผานฟงกชันแฮชหนึ่งแลวไดเลขที่อยูเดียวกัน ในตารางแฮช 2. ปฏิทรรศนวนั เกิดเปนเหตุการณทแ่ี สดงใหเห็นวา ถึงแมฟงกชันจะกระจายอยางสม่ําเสมอ ก็มี โอกาสทีจ่ ะเกิดการชนของขอมูลได แมกับปริมาณขอมูลที่ไมมากนัก 3. การแกไขปญหาการชนมีแบบเปดและแบบปด 4. การแฮชแบบเปดอาศัยการจัดเก็บขอมูลที่ชนไวในรายการโยงเดียวกัน 5. การแฮชแบบปดอาศัยการหาชองวางในตารางชองใหมเพื่อจัดเก็บขอมูล เมื่อขอมูลถูกแฮชไป ชนกับขอมูลเดิมที่เก็บในตาราง 6. วิธีการหาชองวางใหมในการแฮชแบบปดมีแบบ linear probing, quadratic probing, double hashing เปนตน วัตถุประสงค หลังจากที่ศึกษาตอนที่ 12.2 แลว นักศึกษาสามารถ 1. เขาใจและเปรียบเทียบวิธีการแกไขปญหาการชนของขอมูลตางๆ 2. เขียนโปรแกรมสรางและใชการดําเนินการตางๆ ของการแฮชได เรื่องที่ 12.2.1 ปฏิทรรศนวันเกิด ตัวอยางที่ผานมาในเรื่องที่ 12.1.1 เราใชรหัสประจําตัวเปนคียของนักศึกษา แลวพยายามหาฟงกชัน คํานวณเลขที่อยูจากรหัสประจําตัว คราวนี้จะขอใชวิธีใหม คือจะขอใชวนั /เดือนเกิดของนักศึกษาเปนตัวระบุ เลขที่อยูของขอมูลนักศึกษาในตาราง นัน่ คือใครเกิดวันที่ 1 มกราคม ก็จะถูกเก็บไวในตารางชองที่ 0 เกิดวันที่ 2 มกราคม ก็จะถูกเก็บไวในตารางชองที่ 1 ... เกิดวันที่ 31 ธันวาคม ก็จะถูกเก็บไวในตารางชองที่ 365 ถาทํา เชนนี้ยอมแสดงวาตองจองตารางขนาด 366 ชอง สมมติวาเราตองการเก็บขอมูลของนักศึกษาจํานวน 40 คน ก็เรียกไดวาจองไวเกินตั้งเกือบสิบเทาของจํานวนขอมูล ซึ่งอาจรูสึกไมคอยคุม แตถา เราสามารถเพิม่ ลบ และ คนหาขอมูลไดรวดเร็วมาก ก็นับวาคุมทีเดียว
!!* เหตุผลทีเ่ ลือกวันเดือนเกิดใหเปนตัวเลขระบุเลขทีอ่ ยูใ นตาราง ก็เพราะรูส กึ วาวันเดือนของปนา จะมี ลักษณะกระจายอยางสม่าํ เสมอ มีพฤติกรรมสุมพอสมควร นาจะใชเปนฟงกชันแฮชของ "คน" ได คือเราแทบ ประกันอะไรไมไดเลยวาคนสองคนทีม่ วี นั เดือนเกิดเหมือนกัน จะมีลกั ษณะอะไรเหมือนกันบางจากขอมูล ประจํานักศึกษา ฟงกชนั คํานวณเลขทีอ่ ยูซ ง่ึ มีลกั ษณะคลายกับตัวเลขสุม เปนคุณสมบัตทิ เ่ี ราตองการ ถาเราตองการจัดเก็บขอมูลนักศึกษาเกิน 366 คน ยอมตองเกิดปญหาการชนแนๆ แตในที่นี้สมมติ วาเราตองการเก็บขอมูลเพียง 40 คนจาก 366 ชองที่มีใหจัดเก็บ ก็นา จะรูส กึ ปลอดภัยมากๆ เลยวาไมชน แนๆ มาดูตัวอยางกันดีกวา ภาพประกอบ 1 แสดงผังการกระจายวันเดือนเกิดของคน 40 คน 6 พบวามีคน สองคูท เ่ี กิดวันเดียวกัน (นัน่ คือเกิดการชนกันทีส่ องตําแหนงในตาราง) ตรงนี้เห็นผลแลวคงขัดกับความรูสึก ครั้งแรกที่บอกวาการที่คน 40 คนจะมีวนั เกิดซ้าํ กันสักสองคนนัน้ มีโอกาสนอยมาก
#Hits
2
1
0 Day of Year
ภาพประกอบ 12.1 ตัวอยางผังการกระจายวันเดือนเกิดของนักศึกษาจํานวน 41 คน เราจะมาหาคําตอบวาตองมีคนอยางนอยสักกี่คนในหองๆ หนึ่ง จึงจะทําใหโอกาสที่มีคนสองคนใน หองนัน้ มีวนั เดือนเกิดเดียวกันมีเกิน 50% กําหนดใหวนั เดือนเกิดของคนนัน้ นับวาเปนฟงกชนั ทีก่ ระจายดี มากๆ นั่นหมายความวาคนๆ หนึ่งมีโอกาสที่จะมีวันเดือนเกิดตามที่กําหนดใหเปน 1/366 ถากําหนดใหมีคน m คนในหอง 1. พิจารณาคนที่ 1 ความนาจะเปนทีไ่ มมวี นั เดือนเกิดซ้าํ กันยอมเปน 1 2. พิจารณาคนที่ 1, 2 ความนาจะเปนทีไ่ มมวี นั เดือนเกิดซ้าํ กันยอมเปน (365/366) 3. พิจารณาคนที่ 1, 2, 3 ความนาจะเปนทีไ่ มมวี นั เดือนเกิดซ้าํ กันยอมเปน (365/366)(364/366) 4. ... 5. พิจารณาคนที่ 1, 2, 3, ..., m ความนาจะเปนทีไ่ มมวี นั เดือนเกิดซ้าํ กันยอมเปน -'**.!/"0 '** '*) '*( '**+⋅+'**+⋅+'**+⋅+,,,++⋅+ '** +
6
ผูเขียนไดขอมูลนี้จากการไลถามวันเกิดนักศึกษาในหองขณะสอนหัวขอ hashing ในป 2543 (รวมวันเดือนเกิดของผูเ ขียนดวย)
!!# คําถามก็คือวา m ตองมีคาอยางนอยเทาไร จึงทําใหคาขางบบนี้นอยกวา 0.5 เขียนโปรแกรม คอมพิวเตอรลองแปรคา m ดูก็จะพบวา m ตองมีคาอยางนอย 23 หมายความวาถาหองๆ หนึ่งมีคนอยางนอย 24 คน ก็มโี อกาสเกินครึง่ ทีจ่ ะมีสองคนในหองนีท้ ม่ี วี นั เดือนเกิดเหมือนกัน เหตุการณเชนนีเ้ รียกวาปฏิทรรศน วันเกิด (Birthday Paradox) ซึ่งออกจะขัดกับความรูสึกของคนทั่วไปที่คิดวานาจะตองมีจํานวนคนมากกวานี้ ปฏิทรรศนวันเกิดนั้นบอกเราวาถึงแมฟงกชันแฮชที่ใชจะดี (เทียบกับวันเดือนเกิดของคน ซึง่ คาดวา นาจะกระจายดี) และขอมูลมีไมมาก ก็มีโอกาสที่จะเกิดการชน (นั่นคือเพียงแค 24 คน ก็มโี อกาสเกินครึง่ วาจะ มีวนั เดือนเกิดซ้าํ กัน) ดังนั้นจึงจําเปนตองหาวิธีการแกไขปญหาการชนของขอมูลวาจะตองทําอยางไร วิธีแก ไขแบงไดเปนสองวิธีหลักคือ การแฮชแบบเปด (open hashing) และการแฮชแบบปด (closed hashing) เรื่องที่ 12.2.3 การแฮชแบบปด การแฮชแบบปด (closed hashing) บางทีเรียกกันวา open addressing มีแนวคิดในการจัดเก็บขอ มูลทั้งหมดในตารางแฮช เมื่อมีการชนเกิดขึ้น ก็จะแกไขปญหาดวยการหาชองวางชองอื่นในตาราง เมือ่ เปรียบ เทียบกับการแฮชแบบเปดนั้นก็จะพบวาไมมีการจองเนื้อที่หนวยความจําใหม (ดวยคําสัง่ new) และไมตอ งเสีย เนื้อที่ที่เก็บตัวชี้ในรายการโยง (เพราะไมไดใช) แตตองเสียเวลาหาชองวางใหมในตาราง เมื่อขอมูลถูกแฮชครั้งแรกจะได h(x) เปนเลขที่อยูของตารางที่ตองการเก็บขอมูล ถาไมวาง (เกิดการ ชน) ก็จะไปดูชอ ง h1(x) ถาไมวางอีกก็ไปดูชอง h2(x) กระทําเชนนีไ้ ปเรือ่ ยๆ จนกวาจะพบชองวาง จะขอเรียก ชองแรกที่ไปดูวา h0(x) นัน่ คือ h0(x) = h(x) ดังนัน้ hi(x) คือเลขที่อยูของตารางที่สนใจสําหรับคีย x หลังการ ชนครั้งที่ i นิยามให hi(x) = ( h(x) + S(x, i) ) mod m โดยที่ S(x, i) คือฟงกชันในการคํานวณระยะหางจากชองแรกที่ถูกแฮช h(x) ที่สนใจดู 7 ของคีย x หลังการชน ครั้งที่ i โดยทั่วไปก็หวังวาจะไดลําดับของการดูชองตางๆ h0(x), h1(x), h2(x),... ที่มีจํานวนไมมากนัก จะได เร็วๆ จํานวน probe จะนอยก็เมือ่ ตารางไม “แนน” หมายความวาคา load factor λ$ซึง่ คือสัดสวนของจํานวน ขอมูลที่เก็บในตารางกับขนาดของตารางมีคาไมสูง สําหรับการแฮชแบบปดนั้น ขอมูลทั้งหมดถูกเก็บตามชอง ตางๆ ในตารางแฮช ดังนัน้ %$ ≤$ λ$ ≤$ ! โดยทั่วไปจะรักษาใหตารางแฮชมี λ$ ≤$ %&' จะไดไมแนน สงผลให ทํางานไดรวดเร็ว แตแนนอนวาก็สน้ิ เปลืองเนือ้ ที่ (จอง 100 คาดวาใชแค 50) แตนเ่ี ปนคุณสมบัตเิ ดนของตา รางแฮชที่ใชเนื้อที่แลกกับเวลาการทํางาน เราจะนําเสนอฟงกชนั S(x, i) สามรูปแบบพื้นฐานที่ใชกันทั่วไป Linear Probing วิธีนี้กําหนดให S(x, i) = i เปนฟงกชันงายๆ บอกวาหลังการชนครั้งที่ i ก็ใหพิจารณาดูชองที่หางจาก ชองแรกที่แฮชมา ไปอีก i ชอง (ตามดวยการมอดุโลขนาดของตารางดวย) เขียนไดเปน hi(x) = ( h(x) + i ) mod m 7
การพิจารณาดูวา ชองหนึง่ ๆ ในตารางนัน้ เรียวาการ probe ชองนัน้
!!$ หรือตีความไดอีกแบบหนึ่งวาเปนการดูชองถัดไป (จากชองที่แลว) นัน่ คือ hi(x) = ( hi -1(x) + 1 ) mod m ตัวอยางเชน กําหนดใหตารางแฮชมี 11 ชอง ใช h(x) = x mod 11 เปนฟงกชันแฮช เราตองการเพิ่มขอ มูล 58, 70, 17, 36, และ 4 ตามลําดับ จะไดการเปลี่ยนแปลงของตารางแฮชดังภาพประกอบ 12.2 การเพิ่มขอมูลในตารางแฮชที่ใช linear probing
!!% 0
1
2
4
เพิม่ 58 (1 probe)
3 58
5
6
เพิม่ 70 (1 probe)
58
70
เพิม่ 17 (1 probe)
58
70
เพิม่ 36 (3 probes)
58
70
36
17
เพิม่ 4 (3 probes)
58
70
36
17
7
8
9
10
17
4
ภาพประกอบ 12.2 การเพิ่มขอมูลในตารางแฮชที่ใช linear probing การเพิม่ 58, 70, และ 17 ใชเพียงหนึ่ง probe ตอหนึ่งขอมูล เนื่องจากหลังการแฮชครั้งแรกแลวพบชองวางทัน ที แตพอเพิ่ม 36 ซึ่งถูกแฮชไปที่ชองหมายเลข 3 พบวาชนก็ตอง probe ตอที่ ชอง 4 ซึง่ ก็ชนอีกก็ probe ตอ ที่ชอง 5 จึงจะพบวาวาง (สําหรับการเพิม่ 4 นัน้ ขอใหนักศึกษาลองไลดูวาไดตามที่แสดงไวหรือไม) ขอดีของ linear probing ก็อยูตรงที่งายดี มีชองวางในตารางเหลืออยูก็ตอง probe พบแนๆ 8 แตขอเสีย ก็คือเกิดสภาวะที่เรียกวาขอมูลเกาะกลุมกัน ยิ่งเกาะกลุมกันมาก ยิ่งทําใหมีพฤติกรรมการ probe ที่ เปนแบบลําดับนั้นยาวขึ้นเรื่อยๆ ลองดูภาพประกอบ 12.3 ตารางแฮชที่ใช linear probing จะเขาใจมากขึ้น รูปนี้แสดงตารางแฮชที่มีการเพิ่มขอมูลไปจํานวนหนึ่ง ขอมูลตางๆ ในตารางถูก แสดงดวยเสนสีดาํ เสนสีดาํ ทีเ่ กาะกลุม กันจนเปนสีเ่ หลีย่ มสีดาํ ขนาดความกวางแตกตางกัน แสดงใหเห็นถึง จํานวนขอมูลที่อยูติดกันในตาราง ชองสีขาวก็คือสวนของตารางที่วาง ถาจะถามตรงนี้วาการเพิ่มขอมูลตัวตอ ไป จะมีโอกาสไปถูกเก็บที่ที่ใดมากที่สุด ถาฟงกชันแฮชกระจายดี ขอมูลตัวถัดไปนั้นจะมีโอกาสถูกแฮชไปที่ ชองใดๆ ดวยโอกาสเทาๆ กัน ขอใหดูกลุมขอมูลที่เกาะกลุมกันใหญสุดในตาราง (ชองสีดาํ ชุดทีส่ องจากทาง ซาย) ไมวาขอมูลใหมจะถูกแฮชมาชนกับขอมูลตัวใดในกลุมนี้ ก็จะตองถูกจับใหไปเก็บที่ชองถัดไปทางขวาที่ วางของกลุมนี้ (ดวยพฤติกรรมของ linear probing) สงผลใหกลุมนี้มีขนาดโตขึ้น เนื่องจากกลุมนี้มีขนาดใหญ สุดในตาราง ก็ยอมเปนกลุมที่มีโอกาสโตขึ้นมากที่สุด เราเรียกลักษณะการเกาะกลุม และการโตของกลุม เชนนี้ วา primary clustering (บางที่เรียกวา cookie monster effect ขอใหนักศึกษาลองคิดดูวาทําไมถึงเรียกเชนนี้)
ภาพประกอบ 12.3 ตารางแฮชที่ใช linear probing
8
อีกทัง้ เนือ่ งจากลําดับการ probe เปนการดูชอ งถัดไปเรือ่ ยๆ สงผลใหการเขาถึงขอมูลในหนวยความจํานัน้ รวดเร็วมาก ดวย สถาปตยกรรมการจัดการหนวยความจําเปน cache หลายๆ ระดับของคอมพิวเตอรในปจจุบนั
!'& Quadratic Probing เพื่อขจัดปญหาการเกาะกลุมที่พบใน linear probing วิธีนี้กําหนดให S(x, i) = i2 บอกวาหลังการชน ครั้งที่ i ก็ใหพิจารณาดูชองที่หางจากชองแรกที่แฮชมา ไปอีก i2 ชอง (ตามดวยการมอดุโลขนาดของตาราง ดวย) เขียนไดเปน hi(x) = ( h(x) + i2 ) mod m หรือตีความในกรณีที่คิดจากชองที่ probe ชองที่แลว ไดเปน 9 hi(x) = ( hi -1(x) + (2i – 1) ) mod m ตัวอยางเชน กําหนดใหตารางแฮชมี 11 ชอง ใช h(x) = x mod 11 เปนฟงกชันแฮช เราตองการเพิ่มขอมูล 58, 70, 17, 36, และ 4 ตามลําดับ จะไดการเปลี่ยนแปลงของตารางแฮชดัง ภาพประกอบ 5 0 1 2 3 4 5 6 7 8 9 10 เพิม่ 58 (1 probe) 58 เพิม่ 70 (1 probe)
58
70
เพิม่ 17 (1 probe)
58
70
17
เพิม่ 36 (3 probes)
58
70
17
36
เพิม่ 4 (2 probes)
58
70
17
36
4
ภาพประกอบ 12.4 การเพิ่มขอมูลในตารางแฮชที่ใช quadratic probing การเพิม่ 58, 70, และ 17 ใชเพียงหนึ่ง probe ตอหนึ่งขอมูล เนื่องจากหลังการแฮชครั้งแรกแลวพบ ชองวางทันที แตพอเพิ่ม 36 ซึ่งถูกแฮชไปที่ชองหมายเลข 3 พบวาชนก็ตอง probe ตอที่ ชอง 3+12 = 4 ซึง่ ก็ ชนอีกก็ probe ตอที่ชอง 3+22 = 7 จึงจะพบวาวาง การเพิม่ 4 ก็อาศัยอีกสอง probe ก็พบชองวางดังภาพ ประกอบ หลายคนอาจจะรูสึกวา quadratic probing ไมเห็นมีอะไรดีเลย ภาพประกอบ 12.2 การเพิ่มขอมูลในตา รางแฮชที่ใช linear probing และภาพประกอบ 12.4 การเพิ่มขอมูลในตารางแฮชที่ใช quadratic probing
9
จากลําดับการ probe ของ quadratic probing h(x), h(x) + 1, h(x) + 4, h(x) + 9, h(x) + 16, ... ดูดีๆ จะพบวาชองใหมท่ี probe หาง จากชองที่แลวเปน +1, +3, +5, +7, ... ขอใหนักศึกษาพิสูจนใหเห็นจริงเชิงคณิตศาสตรกันเอง
!'" นั้นเพิ่มขอมูลชุดเดียวกัน ผลสุดทายก็ไดขอ มูลทีเ่ กาะกลุม เปนกอนเดียวกันเหมือนกัน ขอใหเขาใจ ดวยวาการเกาะกลุม กันของกรณีการใช quadratic probing นั้นไมเปนปญหาใหญเทาของกรณี liner probing เพราะวาการ probe นัน้ เปนแบบ “กระโดดๆ” ซึง่ probe หลุดออกจากกลุม ไดเร็วกวาแบบ linear probing มาก จะขอเพิ่มขอมูลตอดวยการเพิ่ม 11 และ 24 ซึง่ เพิม่ ไดสบายๆ ดังแสดงในภาพประกอบ 12.5 การ probe หาชองวางไมพบดวย quadratic probing ตามดวยการเพิ่ม 35 ซึ่งจะมีลําดับการ probe ดังนี้ 2, 3, 6, 0, 7, 5, 5, 7, 0, 6, 3, 2, 3, 6, 0, 7, 5, ... หมายความวามีการ probe เพียงแค 6 ชอง (ซึ่งแรงเงาสีทึบใหเห็นในภาพประกอบ 6) ซ้าํ ๆ กันในตาราง และก็เผอิญเปนชองที่มีขอมูลแลวทั้งสิ้น แสดงใหเห็นวา quadratic probing นัน้ ไมสามารถ probe หาชองวาง ไดทั้งๆ ที่ตารางแฮชยังไมเต็ม 0 1 2 3 4 5 6 7 8 9 10 เพิม่ 11 (1 probe)
11
24
58
70
4
17
36
เพิม่ 23 (1 probe)
11
24
58
70
4
17
36
24 58 70
4
17
36
เพิม่ 35 (? probes) 11
ภาพประกอบ 12.5 การ probe หาชองวางไมพบดวย quadratic probing เหตุการณที่แสดงใหเห็นขางบนนี้อาจทําใหเราดวนสรุปวาไมควรใช quadratic probing เลย แตตอง รีบบอกตอนนีเ้ ลยวา เราสามารถปองกันไมใหเกิดปญหานี้ไดโดยการควบคุมให load factor ของตารางมีคา ต่ํากวา 0.5 และกําหนดใหตารางมีขนาดเปนจํานวนเฉพาะ ดวยขอกําหนดสองขอนี้ เราสามารถพิสจู นไดวา ถามีชองวางในตาราง quadratic probing ตองหาพบแนๆ ดังนี้ กําหนดให m คือขนาดของตาราง a และ b เปนครัง้ ที่ probe โดยที่ 0 ≤ b < a ≤ m/2 เราจะแสดงใหเห็น จริงวา ha(x) ไมเทากับ hb(x) เลย หมายความวาการ probe ตั้งแตครั้งที่ 0 จนถึงครั้งที่ m/2 นั้นไมซ้ําชองกัน เลยในตาราง แสดงวาถามีชองวางเกิน m/2 ชอง ก็ยอมตอง probe พบหนึ่งในชองวางนั้นแนๆ เราอาศัย การพิสูจนดวยขอขัดแยง โดยสมมติใหมี ha(x) = hb(x) นัน่ คือมี ( h(x) + a2 ) mod m = ( h(x) + b2 ) mod m โดยที่ 0 ≤ b < a ≤ m/2 เขียนใหมในรูปของสมภาค (congruence) เปน h(x) + a2 ≡ h(x) + b2 (mod m) ตัด h(x) ออกทัง้ สองขาง ได a( ≡ b( (mod m) เขียนใหมได (a–b)(a+b) ≡ 0 (mod m) ซึง่ สมภาคนีจ้ ะเปนจริงไดมไี ดสามกรณีคอื m หาร (a–b) ลงตัว หรือ m หาร (a+b) ลงตัว หรือ m หาร (a–b)(a+b) ลงตัว จากเงือ่ นไข 0 ≤ b < a ≤ m/2 แสดงวา (a–b) และ (a+b) มีคามากกวา 0 แตนอยกวา m ดังนัน้ m หาร (a–b) และ (a+b) ไมลงตัวแนๆ
!'! สําหรับกรณีสดุ ทายนัน้ ก็ไมมที างเปนจริงได เนื่องจากเรากําหนดให m เปนจํานวนเฉพาะ จึงแยกตัวประกอบ เปน (a–b) กับ (a+b) ไมได สรุปไดวาไมมื ha(x) = hb(x) สําหรับ 0 ≤ b < a ≤ m/2 quadratic probing มีพฤติกรรมการ probe ที่ใหผลดีมากในทางปฏิบัติ ขจัดปญหา primary clustering ที่เกิดใน linear probing แตก็ยังมีปญหาในตัวเองอยูบางตรงที่วาฟงกชัน S(x, i) = i2 นั้นมีระยะ กระโดดซึ่งขึ้นกับหมายเลขขงครั้งที่ชน ไมไดขน้ึ กับคาของคียเ ลย ดังนั้นชุดขอมูลที่มี h(x) เหมือนกันจะชนกัน และกันไปเรือ่ ยๆ ตลอดการ probe เสมือนกับวาชุดขอมูลทีแ่ ฮชไปทีท่ เ่ี ดียวก็เกาะกลุม กันนัน่ เอง แตเปนการ เกาะกลุมที่เห็นไดยากดวยตา เนื่องจากไมจําเปนตองอยูติดกันในตาราง แตเปนชุดขอมูลที่มีลําดับการ probe เหมือนกัน เรียกการเกาะกลุม ในลักษณะเชนนีว้ า secondary clustering Double Hashing ฟงกชัน S(x, i) ซึ่งใชคํานวณหาระยะกระโดดในการ probe ทีไ่ ดนาํ เสนอมาของ linear probing และ quadratic probing นั้นมีคาขึ้นกับหมายเลขครั้งที่ชนเทานั้น จึงเกิดปญหา secondary clustering ตามที่ได นําเสนอมา เพื่อขจัดปญหาดังกลาว เราสามารถออกแบบให S(x, i) มีคาแปรตามคาของคียดวย double hashing กําหนดให S(x, i) = i * เขียนไดเปน hi(x) = ( h(x) + i * g(x) ) mod m หรือตีความในกรณีที่คิดจากชองที่ probe ชองที่แลว ไดเปน hi(x) = ( hi -1(x) + g(x) ) mod m หมายความวาการ probe ครั้งตอไปจะหางจากตําแหนงปจจุบันไป g(x) ชอง โดยพฤติกรรมของ g(x) ก็คลายฟงกชันแฮช กลาวคือถึงแมคียสองคียที่ตางกันจะมีเลขที่อยูที่แฮชไปเหมือนกัน แตเมือ่ ผาน g(x) ซึ่งคํานวณระยะกระโดด ก็มีโอกาสสูงที่ไดระยะที่ไมเหมือนกัน จึงขจัดปญหา secondary clustering ได ตัวอยางเชน กําหนดใหตารางแฮชมี 11 ชอง ใช h(x) = x mod 11 เปนฟงกชันแฮช และ g(x) = 7 - (x mod 7) เราตองการเพิ่มขอมูล 58, 70, 17, 36, และ 4 ตามลําดับ จะไดการเปลีย่ นแปลง ของตารางแฮชดังภาพประกอบ 12.6 การเพิ่มขอมูลในตารางแฮชที่ใช double hashing
0
1
2
4
เพิม่ 58 (1 probe)
3 58
5
6
เพิม่ 70 (1 probe)
58
70
เพิม่ 17 (1 probe)
58
70
17
เพิม่ 36 (2 probes)
58
70
17
เพิม่ 4 (2 probes)
58
70
17
7
8
9
36 4
36
10
!'' ภาพประกอบ 12.6 การเพิ่มขอมูลในตารางแฮชที่ใช double hashing การเพิม่ 58, 70, และ 17 ใชเพียงหนึ่ง probe ตอหนึ่งขอมูล เนื่องจากหลังการแฮชครั้งแรกแลวพบ ชองวางทันที แตพอเพิ่ม 36 ซึ่งถูกแฮชไปที่ชองหมายเลข 3 พบวาชนก็ตอง probe ตอโดยกระโดดไปอีก 7 (36 mod 7) = 6 ชองไดที่ชอง 9 พบวาวางก็ใสขอมูลได สําหรับการเพิม่ 4 ที่ถูกแฮชไปชอง 4 พบวาไมวาง คาของ g(4) = 7 - (4 mod 7) = 3 แสดงวากระโดดไปอีก 3 ชองไดชอง 7 วางก็ใสขอมูลได ประเด็นทีต่ อ งระวังก็คอื เราจะมั่นใจไดอยางไรวา ถามีชองวางในตารางแลว g(x) จะใหคาซึ่งเมื่อ กระโดดในระยะเทากับ g(x) แลวจะ probe พบชองวางในตาราง ตัวอยางเชนถาตารางมีขนาด 8 ชอง (m=8) และคํานวณไดคาของ g(x) เปน 4 จะพบวาการ probe ดวยการกระโดดขามทีละ 4 ชอง จะ probe อยูแค 2 ชองในตารางเทานั้น (ทั้งนี้เพราะวา 8/4 = 2) เพือ่ หลีกเลีย่ งปญหาดังกลาว และสรางความมั่นใจสามารถ probe ไดครบทุกชองแนๆ จะตองกําหนดใหขนาดของตารางเปนจํานวนเฉพาะ เพราะวาคาของ g(x) ในชวง ตั้งแต 1 ถึง m-1 นั้นไมมีตัวใดหาร m ลงตัวแนๆ ถา m เปนจํานวนเฉพาะ การเพิ่มและการคนหาขอมูล การเพิม่ และการคนหาขอมูลนัน้ มีขน้ั ตอนการทํางานคลายๆ กัน กลาวคือเริม่ ดวยการหาเลขทีอ่ ยูเ ริม่ ตนที่จะ probe ดวยฟงกชันแฮช ถาเปนการเพิม่ ขอมูลก็ตอ ง probe จนพบชองที่วางไวใสขอมูล ถาเปนการ คนหาก็ตอง probe จนพบขอมูลที่ตองการ โดยเมื่อใด probe พบชองวางก็แสดงวาขอมูลที่ตองการคนหาไมมี อยูในตาราง ดังนั้นขอมูลแตละชองในตารางแฮชนั้นจะตองมีสถานะกํากับวาเปนชองที่วางหรือไมดวย การลบขอมูล การลบขอมูลในตารางของการแฮชแบบปดนั้นจะมีประเด็นที่ตองระวังเปนพิเศษวาจะลบอยางไร เรา ลบขอมูลออกจากตารางดวยการตั้งสภาวะของชองที่ถูกลบใหเปนชองวางไมไดตารางแถวบนในภาพ ประกอบ 12.7 การลบขอมูลในตารางแฮช เปนผลมาจากการเพิ่มขอมูลในตารางแฮชในภาพประกอบ 12.2 การเพิ่มขอมูลในตารางแฮชที่ใช linear probing ซึง่ ใน linear probing โดยมี h(x) = x mod 11 ถาเราตองการลบ 17 ออกจากตารางก็ตองคนหา 17 กอน โดย h(17) = 17 mod 11 = 6 ก็พบ 17 ในชองที่ 6 พอดี ถาเราลบ 17 ดวยการลางชองที่ 6 ใหมี สภาวะวาง (การลบแบบที่ 1 แถวกลางในภาพประกอบ 12.7 การลบขอมูลในตารางแฮช ) จะทําใหการคนหา 4 ไดผลเปนคนหาไมพบ เนื่องจาก h(4) = 4 mod 11 = 4 พบวา probe ชองที่ 4, 5 ก็ไมใช 4 probe ตอที่ชองที่ 6 ก็พบวาวาง ก็เลยสรุปวาหาไมพบในตาราง ดังนั้นการลบจะใชวิธีการ ระบุใหชองในตารางที่จะถูกลบเปนสภาวะวางไมได จะตองเพิ่มสภาวะของชองในตารางเปนอีกหนึ่ง
!'( แบบคือ "เคยเปนขอมูล แตถกู ลบไปแลว" ดงแสดงในแถวลางของภาพประกอบ 12.7 การลบขอมูล ในตารางแฮช ดังนัน้ ขบวนการ probe จะตองดําเนินการตอถึงแมวาจะพบชองที่มีสภาวะถูกลบ 0 1 2 3 4 5 6 7 8 9 58 70 36 17 4 ลบ 17 (แบบที่ 1)
58
70
36
ลบ 17 (แบบที่ 2)
58
70
36
10
4 17
4
ภาพประกอบ 12.7 การลบขอมูลในตารางแฮช โปรแกรมสําหรับการแฮชแบบ double hashing ก็ไดเวลามาดูตวั โปรแกรมกันอยางละเอียดกัน หลังจากที่ไดเขาใจแนวคิดการทํางานของการแฮช แบบปด จะขอนําเสนอเฉพาะแบบ double hashing เทานัน้ เราเริม่ ดวยการประกาศประเภทขอมูลของตา รางแฮชในภาษาปาสกาลดังนี้ TYPE CellStatus = ( Active, Empty, Deleted ); HashCell = RECORD element : ElementType; status : CellStatus; END; Position = integer; HashTable = RECORD CurrentSize : integer; Table : ARRAY[ 0..TableSize-1 ] OF HashCell; END;
เปน record ซึ่งประกอบดวยสมาชิกตัวแรกคือ currentSize ซึ่งเก็บจํานวนขอมูลที่ ไดถูกจัดเก็บไวในตาราง (currentSize นี้มีไวคํานวณ load factor ของตารางแฮช) และสมาชิกอีกตัวหนึ่ง คือ table ซึง่ เปนแถวลําดับมีขนาดเทากับ TableSize เริ่มตั้งแตชองที่ 0 ถึง TableSize-1 แตละชองเก็บ ขอมูลประเภท HashCell ซึง่ เปน record ประกอบดวยขอมูลที่จะจัดเก็บ (element) สภาวะของชอง (status) ซึ่งมีไดสามสภาวะ (CellStatus) คือ Active แทนกรณีมีขอมูลเก็บอยู Empty แทนกรณีเเปน ชองวาง และ Deleted แทนกรณีเปนชองที่เคยมีขอมูลแตถูกลบไปแลว การสรางตารางแฮชตอนเริม่ จึงตองมีการลางตารางใหทกุ ๆ ชองในตารางมีสภาวะเปนชองวางทั้ง หมดดังนี้ HashTable
PROCEDURE InitializeTable( VAR H : HashTable ); VAR I : integer; BEGIN FOR I:=0 TO TableSize-1 DO
!') H.table[i].status := Empty; END;
การคนหาขอมูลรับขอมูลที่ตองการคนมาผานฟงกชันแฮช แลวเริม่ ดําเนินการ probe แบบ quadratic probing ไปเรือ่ ยๆ จนกวาจะพบขอมูลที่ตองการ หรือจนกวาจะพบชองวางซึ่งสรุปไดวาหาไมพบ ชองถัดไปที่ตอง probe อาศัยนิยาม hi(x) = ( hi-1(x) + g(x) ) mod TableSize เปนสูตรในการคํานวณ เขียนได เปนฟงกชนั ดังนี้ FUNCTION Find( VAR H : HashTable; Key : ElementType ) : Position; VAR P : Position; BEGIN p := findPos( H, Key ); IF (H.table[p].status = Active) AND (H.table[p].element = key) THEN Find := p ELSE Find := -1; END; FUNCTION FindPos( VAR H : HashTable; Key : ElementType ) : Position; VAR I, P, Step : integer; BEGIN I := 0; P := Hash( Key ); Step := Hash2( Key ); { Hash2(x) is g(x) } WHILE (I < TableSize) AND (H.table[p].status <> Empty) AND (H.table[p].element <> Key) DO BEGIN I := I + 1; P := ( P + Step ) MOD TableSize; END; FindPos := p; END;
จะคืนคา -1 เมื่อไมพบขอมูลที่ตองการในตาราง โดยมีการเรียกใชฟงกชัน findPos ซึง่ เปน ฟงกชันหลักในการคน findPos ซึ่งคืนตําแหนงในตารางที่ทําใหหยุด probe (findPos จะหยุดการ probe เมือ่ ได probe มาครบทั้งตารางแลว หรือ probe พบชองวาง หรือ probe พบชองที่มีขอมูลที่ตองการคน) จึง เปนหนาทีข่ อง find ที่หลังจากเรียก findPos แลวตองตรวจสอบกอนวาพบขอมูลในชองที่เปนขอมูลหรือไม หรือวาคนไมพบ การเพิ่มก็อาศัยการหาขอมูล ซึ่งคาดหวังวาจะตองหาไมพบ แลวนําขอมูลใหมใสเขาไปในชองนั้น ตามดวยการเพิ่มคาของ currentSize อีกหนึง่ เขียนเปนฟงกชันดวยการเรียกใช findPos (ซึง่ เขียนกอน หนานี้) ไดดงั นี้ find
PROCEDURE Insert( VAR H : HashTable; Key : ElementType ); VAR P : Position; BEGIN P := FindPos( H, Key ); IF H.table[p].status <> Active THEN BEGIN H.table[p].element := Key;
!'* H.table[p].status := Active; H.currentSize := H.currentSize + 1; END; END;
การลบก็อาศัยการหาขอมูลดวย เปน Deleted ดังนี้
findPos
อีกเชนกัน เมื่อหาพบก็เพียงแตตั้งคาของ
status
ให
PROCEDURE Remove( VAR H : HashTable; Key : ElementType ); VAR P : Position; BEGIN P := findPos( H, key ); IF (H.table[p].status = Active) AND (H.table[p].element = key) THEN BEGIN H.table[p].status := Deleted; END; END;
Rehashing ตัวโปรแกรมที่นําเสนอในหัวขอที่แลวนั้น เราไมไดควบคุมขนาดของ λ (load factor) ของตารางแต อยางใด แตในทางปฏิบัตินั้นมักคุมให λ < 0.5 ตารางจะไดไมแนนมาก การเพิม่ ลบ และคนหาก็จะทํางานได รวดเร็ว โดยเราสามารถคุมคาของ λ ไดในฟงกชันการเพิ่มขอมูล โดยถาเมื่อใดการเพิ่มขอมูลทําให λ มีคาเกิน ก็ตอ งจัดการอะไรบางอยาง เราอาจจะทําแคแสดงขอความเตือนผูใชถึงสภาพที่เกิดขึ้น หรือถาจะใหดี ก็ควร จัดการขยายตาราง (โดยทั่วไปมักขยายเปนสองเทาของตารางเดิม) และยายขอมูลจากตารางเกามาแฮชลงตา รางใหม เพือ่ เปนการลดคาของ λ ลง ภาพประกอบ 12.8 การขยายตารางจาก 11 ชองเปน 23 ชอง แลวแฮช ขอมูลทั้งหมดใหม แสดงสถานะการณทต่ี อ จากภาพประกอบ 12.6 การเพิ่มขอมูลในตารางแฮชที่ใช double hashing เมื่อมีการเพิ่มขอมูลใหมคือ 64 จะทําใหมีจํานวนขอมูลเปน 6 ตัว λ = 0.55 จึงทําการจองตาราง ใหมมีขนาดเปนสองเทาของตารางเดิมคือ 2*11 = 22 แตเนื่องจากเราตองการใหขนาดของตารางเปนจํานวน เฉพาะ จึงตองหาจํานวนเฉพาะตัวที่มีคามากกวา 22 ซึง่ ก็คอื 23 จึงจองตารางใหมขนาด 23 ชอง จากนั้นวิ่งไล ในตารางเกาทีละชองเริ่มอจากชอง 0 ไปเรือ่ ยๆ ชองใดเปนขอมูล ก็นําไปเพิ่มลงในตารางใหม โดยใชฟงกชัน แฮชใหม (ซึ่งโดยทั่วไปก็จะใชฟงกชันแฮชที่คลายของเดิม เพียงแตปดทายดวยการ mod ดวยขนาดของตา รางใหม ในทีน่ ค้ี อื ก็ h(x) = x mod 23) ดังนั้นลําดับการเพิ่มขอมูลในตารางใหมก็คือ 58, 70, 36, 17, 4 และ 64 จะไดขอมูลในตารางแฮชใหมดังแสดงในแถวลางของภาพประกอบ 12.8 การขยายตารางจาก 11 ชอง เปน 23 ชอง แลวแฮชขอมูลทั้งหมดใหม ซึ่งมี λ = 0.26 0 1 2 3 4 5 6 7 8 9 10 58 70 17 4 36 64
!'# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 70 4 58 36 17 64 ภาพประกอบ 12.8 การขยายตารางจาก 11 ชองเปน 23 ชอง แลวแฮชขอมูลทั้งหมดใหม ขั้นตอนการสรางตารางแฮชใหมนั้นกระทําไดไมยุงยากเลยสําหรับภาษาคอมพิวเตอรที่มีคุณสมบัติที่ รองรับการขยายขนาดของแถวลําดับให (เชน C หรือ Java) แตสําหรับภาษาปาสกาลนั้นจะไมมีคุณสมบัตินี้ เนื่องจากขนาดของแถวลําดับจะตองระบุไวลวงหนากอนการแปลตัวโปรแกรม (แตถาใช Turbo Pascal ก็ สามารถใชฟงกชัน getmem ชวยในการจองขนาดตารางใหมได และตองปรับเปลี่ยนโปรแกรมที่ไดเขียนมา เล็กนอย ซึง่ ขอจะไมนาํ เสนอในทีน่ ้ี) กิจกรรม 12.1 ปญหาการหารสามคูณสอง : วากันวาจํานวนเต็มใดๆ สามารถหาไดดว ยการเริม่ จากเลข 1 แลวหาร สาม (ปดเศษทิ้ง) และ/หรือคูณสอง ไปเรือ่ ยๆ เชน 10 = 1×2×2×2×2/3×2 เปนตน โปรแกรมขางลางนี้วิธีหา คําตอบของปญหานี้ อยากใหนักศีกษาทําความเขาใจกับการทํางานหลักของโปรแกรมนี้กอน จากนัน้ เติม ฟงกชัน hash, insert และ find ที่ใชจัดเก็บขอมูลดวยตารางแฮชเพื่อใหโปรแกรมใชงานไดจริง
!'$ PROGRAM M2d3; CONST MaxDepth = 100; TYPE Solution = ARRAY[1..MaxDepth] of String; VAR N X I H
: : : :
integer; Solution; Integer; HashTable;
FUNCTION M2D3_DFS( VAR H : HashTable; VAR X : Solution; K, N, S : integer ) : boolean; VAR S1 : integer; Found : boolean; BEGIN IF K = MaxDepth THEN Found := false ELSE BEGIN Insert( H, S ); IF ( S = N ) THEN found := true ELSE BEGIN found := false; S1 := S div 3; IF find( H, S1 ) = -1 THEN BEGIN X[K+1] := '/3'; found := M2D3_DFS( T, X, K+1, N, S1 ); END; S1 := 2*S; IF (NOT found) AND (find( H, S1 ) = -1) THEN BEGIN X[K+1] := 'x2'; found := M2D3_DFS( T, X, K+1, N, S1 ); END; END; END; M2D3_DFS := found; END; BEGIN {M2d3} write('Enter a number : '); readln( N ); FOR I := 1 TO Maxdepth DO x[I] := ''; t := nil; IF M2D3_DFS( T, X, 0, N, 1 ) THEN BEGIN I := 1; WHILE X[I] <> '' DO BEGIN write(X[I]); I := I+1; END; writeln; END ELSE writeln('fail');
END.