!"#
แผนการสอนประจําบทเรียน รายชือ่ อาจารยผจู ดั ทํา สมชาย ประสิทธิจ์ ตู ระกูล หัวขอของเนื้อหา ตอนที่ 11.1 นิยามและการดําเนินการตางๆ (2 คาบ) เรือ่ งที่ 11.1.1 นิยาม เรือ่ งที่ 11.1.2 การคนหา เรือ่ งที่ 11.1.3 การคนหาขอมูลที่มีคานอยที่สุด เรือ่ งที่ 11.1.4 การคนหาขอมูลที่มีคามากที่สุด เรือ่ งที่ 11.1.5 การเพิ่มขอมูล เรือ่ งที่ 11.1.6 การลบขอมูลที่มีคามากที่สุด เรือ่ งที่ 11.1.7 การลบขอมูล ตอนที่ 11.2 ความลึกของโหนด และการเรียงลําดับขอมูลแบบ Tree Sort (1 คาบ) เรือ่ งที่ 11.2.1 ความลึกของโหนด เรือ่ งที่ 11.2.2 การเรียงลําดับขอมูลแบบตนไม แนวคิด 1. ตนไมคน หาแบบทวิภาคเปนโครงสรางพืน้ ฐานในการจัดเก็บขอมูล เพือ่ การคนหาอยางมีประ สิทธิภาพ 2. การดําเนินการกับตนไมคนหาแบบทวิภาคสามารถใชแนวคิดการเรียกแบบเวียนเกิด 3. เวลาในการคนหา เพิม่ ลบ ตางเปน O(h) 4. ลําดับของขอมูลที่ถูกเพิ่มมีผลตอรูปรางของตนไมคนหาแบบทวิภาค 5. ความลึกของโหนดโดยเฉลี่ยในตนไมคนหาแบบทวิภาคที่ไดจากการเพิ่มขอมูลเชิงสุมเปน O(log n) 6. การแวะผานแบบตามลําดับในตนไมคนหาแบบทวิภาค จะไดลําดับขอมูลที่ถูกแวะเรียงจากนอย ไปหามาก
!"$ 7. การเรียงลําดับขอมูลแบบ Tree sort อาศัยการสรางตนไมคนหาแบบทวิภาค กับการแวะผาน แบบตามลําดับ เพื่อใหไดลําดับของชุดขอมูลเรียงจากนอยไปมาก วัตถุประสงค หลังจากศึกษาบทเรียนที่ 11 แลว นักศึกษาสามารถ 1. เขาใจคุณลักษณะเฉพาะของตนไมคนหาแบบทวิภาค 2. เขาใจโปรแกรมการดําเนินการตางๆ กับขอมูลในตนไมคนหาแบบทวิภาคได 3. วิเคราะหประสิทธิภาพเชิงเวลาของการดําเนินการตางๆ ในตนไมคนหาแบบทวิภาคได กิจกรรมการเรียนการสอน กิจกรรมทีน่ กั ศึกษาตองทําสําหรับการเรียนการสอน ไดแก 1. ศึกษาเอกสารชุดวิชา/โฮมเพจชุดวิชา ตอนที่ 11.1 และตอนที่ 11.2 2. ทํากิจกรรมของบทเรียนที่ 11 3. ทําแบบประเมินผลของบทเรียนที่ 11 เอกสารประกอบการสอน 1. เอกสารชุดวิชา สื่อการสอน 1. โฮมเพจชุดวิชา 2. สไลดประกอบการบรรยาย (Powerpoint) 3. โปรแกรมคอมพิวเตอร ประเมินผล 1. ประเมินผลจากกิจกรรมที่ทํา 2. ประเมินผลจากคําถามทายบทเรียน
!"% ตอนที่ 11.1 นิยามและการดําเนินการตางๆ หัวเรื่อง เรือ่ งที่ 11.1.1 นิยาม เรือ่ งที่ 11.1.2 การคนหา เรือ่ งที่ 11.1.3 การคนหาขอมูลที่มีคานอยที่สุด เรือ่ งที่ 11.1.4 การคนหาขอมูลที่มีคามากที่สุด เรือ่ งที่ 11.1.5 การเพิ่มขอมูล เรือ่ งที่ 11.1.6 การลบขอมูลที่มีคามากที่สุด เรือ่ งที่ 11.1.7 การลบขอมูล แนวคิด 1. ตนไมคน หาแบบทวิภาคเปนโครงสรางพืน้ ฐานในการจัดเก็บขอมูล เพือ่ การคนหาอยางมีประ สิทธิภาพ 2. การดําเนินการกับตนไมคนหาแบบทวิภาคสามารถใชแนวคิดการเรียกแบบเวียนเกิด 3. เวลาในการคนหา เพิม่ ลบ ตางเปน O(h) 4. ลําดับของขอมูลที่ถูกเพิ่มมีผลตอรูปรางของตนไมคนหาแบบทวิภาค วัตถุประสงค หลังจากที่ศึกษาตอนที่ 11.1 แลว นักศึกษาสามารถ 1. เขาใจคุณลักษณะเฉพาะของตนไมคนหาแบบทวิภาค 2. เขาใจโปรแกรมการดําเนินการตางๆ กับขอมูลในตนไมคนหาแบบทวิภาคได เรื่องที่ 11.1.1 นิยาม เราสามารถนําแนวคิดของโครงสรางแบบตนไมมาจัดเก็บชุดของขอมูล โดยกําหนดใหขอมูลถูกเก็บ ในโหนดตางๆ ของตนไม และกําหนดกฏเกณฑในการจัดเก็บเพือ่ อํานวยความสะดวกในการคน เพิม่ และลบ ขอมูล เราเรียกตนไมเพื่อจัดเก็บชุดขอมูลในลักณะนี้วาตนไมคนหา (search tree) ตนไมคน หาแบบพืน้ ฐาน ที่สุดที่จะไดนําเสนอกันในรายละเอียดในหัวขอนี้ก็คือตนไมคนหาแบบทวิภาค (binary search tree) ซึง่ ก็คอื ตนไมแบบทวิภาคนั่นเอง เพียงแตเราเพมกฏเกณฑในการเก็บขอมูลตามโหนดตางๆ ดังนี้ • ขอมูลในโหนดลางตางๆ ทางซายของโหนดใด ตองมีคานอยกวาขอมูลที่โหนดนั้น และ • ขอมูลในโหนดลางตางๆ ทางขวาของโหนดใด ตองมีคามากกวาขอมูลที่โหนดนั้น
!"& หรืออาจกลาวในอีกลักษณะหนึง่ วา ณ โหนด x ใดๆ ในตนไม ขอมูลในตนไมยอยทางซายของ x ตองมีคานอยกวาขอมูลที่ x และขอมูลในตนไมยอยทางขวาของ x ตองมีคามากกวาขอมูลที่ x (จะขอสนใจ เฉพาะกรณีทช่ี ดุ ขอมูลทีจ่ ดั เก็บบนีม้ ขี อ มูลมซาํ้ กันเลย) ขอใหเขาใจดวยวาดวยชุดขอมูลหนึ่งๆ นั้น เราสามารถจัดเก็บไดหลากหลายรูปแบบ ภาพประกอบ 11.1 ตัวอยางตนไมคนหาแบบทวิภาคที่เก็บชุดขอมูล 1,2,3,4,5,6 แสดงตัวอยางโครงสรางหลากหลายรูปแบบที่จัดเก็บชุดขอมูล 1,2,3,4,5,6 (ทีแ่ สดงในรูปนัน้ เปน เพียงจํานวนนอยที่แสดงเปนเพียวตัวอยางเทานั้น) แตละรูปแบบตางก็จัดเก็บขอมูลตามกฏเกณฑที่ไดกําหนด ไว !
#
! "
& #
" $
" !
% $
&
# %
$ &
%
ภาพประกอบ 11.1 ตัวอยางตนไมคนหาแบบทวิภาคที่เก็บชุดขอมูล 1,2,3,4,5,6 เนื่องจากตนไมคนหาแบบทวิภาคมีโครงสรางเหมือนกับตนไมแบบทวิภาคทุกประการ จะตางกันก็ ตรงกฏเกณฑการจัดเก็บขอมูล ดังนั้นการบรรยายประเภทขอมูลของโหนดในตนไมในภาษาปาสกาลก็กระทํา ในลักษณะเดียวกันกับที่เคยเขียนมาในเรื่องของตนไมแบบทวิภาค ตางกันก็ตรงทีช่ อ่ื เทานัน้ เองดังนี้ TYPE Tree'() = ^TreeNode; TreeNode = RECORD Element : integer; Left : TreePtr; Right : TreePtr; END; BinarySearchTree = TreePtr;
ในหัวขอนี้ทั้งหัวขอ เพือ่ ใหการนําเสนอในรายละเอียดของโปรแกรมนัน้ อานไดงา ย จะขอถือวาขอมูลที่เราจะ จัดเก็บนัน้ เปนตัวเลขจํานวนเต็ม ดังนั้นสมาชิกของโหนดสวนที่ชื่อ element ซึ่งคือตัวขอมูลที่ใชในการคนหา นั้นจะเปนขอมูลแบบ integer ดังที่เขียนไวขางบนนี้ ทั้งนี้เนื่องจากการเปรียบเทียบขอมูลจะกระทําไดงาย ดวยเครื่องหมาย <, >, และ = ไดเลย เรื่องที่ 11.1.2 การคนหา ภาพประกอบ 11.2 ตัวอยางตนไมคนแบบทวิภาค
!"' แสดงตัวอยางตนไมคนหาแบบทวิภาค จะเห็นไดวาการคนหาขอมูล a ในตนไมนน้ั เมื่อเริ่มที่ราก หาก a ไมใชขอมูลที่ราก และมีคานอยกวาขอมูลที่ราก ก็ควรจะคนหาตอในตนไมยอยทางซายของราก (ไม ตองไปสนใจตนไมยอยทางขวา เพราะตองมากกวา a แนๆ) และในทางกลับกัน หาก a ไมใชขอมูลที่ราก และ มีคามากกวาขอมูลที่ราก ก็ควรจะคนหาตอในตนไมยอยทางขวาของราก (ไมตองไปสนใจตนไมยอยทางซาย เพราะตองนอยกวา a แนๆ) จึงเห็นไดวาเราสามารถขจัดขอมูลจํานวนหนึ่งออกจากการพิจารณาได สงผลให คนหาขอมูลไดรวดเร็วกวาการคนหาขอมูลซึ่งจัดเก็บในรายการ การคนหาขอมูลตอในตนไมยอยก็กระทําใน ลักษณะเดียวกัน เมื่อใดที่คนหาลงไปเรื่อยจนเหลือแตตนไมยอยที่เปนตนไมวาง ก็สรุปไดวาไมพบขอมูลที่ ตองการคนหาในตนไมนั้น !* %
"*
# "
!# +
"# !+
#+ #!
!!
ภาพประกอบ 11.2 ตัวอยางตนไมคนแบบทวิภาค จากตัวอยางตนไมคนหาแบบทวิภาคในภาพประกอบ 11.2 ตัวอยางตนไมคนแบบทวิภาค หากเราตองการคนหา 7 จะพบวาตองผานขั้นตอนตอไปนี้ • คนหา 7 ในตนไมที่มี 19 เปนราก
พบวา 7 < 19 จึงตองไปคนหาตอทางซายของ 19
• คนหา 7 ในตนไมที่มี 5 เปนราก พบวา 7 > 5 จึงตองไปคนหาตอทางขวาของ 5 • คนหา 7 ในตนไมที่มี 13 เปนราก
พบวา 7 < 13 จึงตองไปคนหาตอทางซายของ 13
• คนหา 7 ในตนไมที่มี 7 เปนราก พบวา 7 = 7 แสดงวาพบขอมูลแลว แตถาตองการคนหา 20 ในตนไมในภาพประกอบ 11.2 ตัวอยางตนไมคนแบบทวิภาค จะพบวาตองผานขั้นตอนตอไปนี้ • คนหา 20 ในตนไมที่มี 19 เปนราก
พบวา 20 > 19 จึงตองไปคนหาตอทางขวาของ 19
• คนหา 20 ในตนไมที่มี 29 เปนราก
พบวา 20 < 29 จึงตองไปคนหาตอทางซายของ 29
• คนหา 20 ในตนไมที่มี 23 เปนราก
พบวา 20 < 23 จึงตองไปคนหาตอทางซายของ 23
• พบวาตนไมยอยทางซายของ 23 เปนตนไมวา ง (nil) จึงสรุปไดวาไมมี 23 ในตนไมน้ี จากตัวอยางในภาพประกอบ 11.2 ตัวอยางตนไมคนแบบทวิภาค
!"( จะเห็นวาหากคนหา 11 จะพบวาเปนขอมูลซึ่งเมื่อคนแลวพบที่ใชเวลาการคนหามากที่สุด เพราะ 11 อยูล กึ สุดในตน และสําหรับกรณีคน หาไมพบ ก็คงตองเปนการคคนหาซึง่ สิน้ สุดลงทีต่ น ไมยอ ยทางซาย หรือสิ้นสุดลงที่ตนไมทางขวาของ 11 เชนกัน ถาดูที่ตัวตนไมจะพบวาเปนขอมูลที่อยูมากกวา 7 และ นอยกวา 11 (กรณีจบการคนหาที่ตนไมยอยทางซายของ 11) หรือเปนขอมูลที่มากกวา 11 และนอยกวา 13 (กรณีจบ การคนหาที่ตนไมยอยทางขวาของ 11) ดังนัน้ จึงสรุปไดตอนนีเ้ กีย่ วกับเรือ่ งของประสิทธิภาพเชิงเวลาในการ คนหาก็คือ เวลาในการคนหากรณีเลวสุดจะแปรผันโดยตรงกับความสูงของตนไม หรือเขียนไดวาเปน O(h) โดยที่ h คือความสูงของตนไมคนหาแบบทวิภาค การคนหาทีบ่ รรยายมาขางตนนีเ้ ขียนเปนฟงกชนั ในภาษาปาสกาลไดดงั นี้
!"" ,-./012.3Find( t : BinarySearchTree; x : integer ) : TreePtr; BEGIN IF t = nil THEN Find := nil ELSE IF x = t^.element THEN Find := t ELSE IF x < t^.element THEN Find := Find( t^.left, x ) ELSE Find := Find( t^.right, x ); END;3
โปรแกรมขางบนนี้เขียนแบบเวียนเกิดตรงกับที่ไดบรรยายมา นัน่ คือเริม่ ดวยกรณีของการสิน้ สุดและ ไมเรียกแบบเวียนเกิดตอ ก็คอื กรณีซง่ึ พบตนไมวา ง ก็ใหคืนคา nil กลับไป เพื่อบอกวาหาไมพบ หรือกรณี ซึ่งพบขอมูลที่ตองการคนหาแลวที่ราก ก็คนื โหนดรากนัน้ กลับไป ถาไมเปนทัง้ สองกรณีขา งตนก็ตอ งเรียก แบบเวียนกิดตอ โดยจะไปคนตอในตนไมยอยทางซายก็เมื่อ x < t^.element มิฉะนั้นก็ไปคนตอที่ตนไม ยอยทางขวา แตถาตองการเพิ่มความเร็วในการคนในเชิงของเทคนิคการเขียนโปรแกรมแลวละก็ ก็ควรเขียนแบบ ขางลางนี้ FUNCTION Find( t : BinarySearchTree; x : integer ) : TreePtr; BEGIN IF t = nil THEN Find := nil ELSE IF x = t^.element THEN Find := t ELSE IF x < t^.element THEN Find := find( t^.left, x ) ELSE Find := Find( t^.right, x ); END;
โดยเรายายกรณีการเปรียบเทียบวาเทากันนัน้ ลงไปหลังสุด เพราะวาเปนกรณีทม่ี โี อกาสเปนจริง นอยทีส่ ดุ สมมติวาขอมูลที่เราจะคนหานั้นอยูทางดานลางของตนไม การคนหาก็ตองผานขอมูลตามโหนด ตางดานบนมากอน ระหวางการเลือ้ ยลงมานัน้ ผลของการเปรียบเทียบวาเทากันหรือไมนั้น ยอมเปนเท็จเสมอ จึงนาทีจ่ ะนําการเปรียบเทียบนอยกวาหรือมากกวาไวเปนกรณีทพ่ี จิ ารณาเสียกอน หรือถาตองการใหเร็วกวานี้ ก็เห็นจะตองเขียนแบบไมใชการเรียกแบบเวียนเกิด แลวเขียนใหหมุน เปนวงวนดังนี้
)** FUNCTION Find( t : BinarySearchTree; x : integer ) : TreePtr; BEGIN WHILE (t <> nil) and (x <> t^.element) DO BEGIN IF x < t^.element THEN t := t^.left ELSE t := t^.right; END; Find := t; END;3
ในโปรแกรมขางบนนี้เราอาศัยตัวแปร t ซึง่ ถูกเปลีย่ นคาเพือ่ เลือ้ ยลงมาเรือ่ ยๆ ในตนไม (ไมวาจะเปนการ เปลีย่ นไปเปนตําแหนงของโหนดลูกทางซาย หรือเปลี่ยนไปเปนตําแหนงของโหนดลูกทางขวา) ตามผลลัพธ ของการเปรียบเทียบภายในวงวน while ตราบเทาที่ยังไมเปนตนไมวาง (คือ nil) และยังไมใชขอมูลที่ ตองการคนหา เมือ่ หลุดออกจากวงวนซึง่ เกิดขึน้ เมือ่ t = nil (นั่นคือไมพบขอมูล) หรือเมื่อ x = ื คาของ t เปนผลลัพธของการคนหานีไ้ ดทนั ที t^.element (ซึ่งก็คือพบขอมูลแลวที่โหนดที่ t ชี้อยู) ก็คน เรื่องที่ 11.1.3 การคนหาขอมูลที่มีคานอยที่สุด จากลักษณะการจัดเก็บขอมูลในตนไมคนหาแบบทวิภาคนั้น เราจะสรุปไดวา ขอมูลทีม่ คี า นอยทีส่ ดุ ตองอยูใ นโหนดซึง่ เปนลูกซายของโหนดซึง่ เปนลูกซายของโหนดซึง่ เปนลูกซาย...ของโหนดซึ่งเปนลูกซายของ ราก (ซึง่ แสดงวานอยกวาโหนดอืน่ ๆ แนๆ) และตองอยูในโหนดที่ไมมีลูกซายดวย (ซึง่ แสดงวานอยทีส่ ดุ เพราะไมมีใครนอยกวา) อยากใหนักศึกษาลองกลับไปดูตนไมคนหาแบบทวิภาคใน ภาพประกอบ 11.2 ดูเอง ก็จะเห็นวา 2 อยูใ นตําแหนงทีล่ กั ษณะดังกลาว ดังนั้นการคนหาโหนดที่มีขอมูลที่นอยที่สุด จึงอาศัยการวิ่งลงซายไปเรื่อยๆ (เริ่มตนจากราก) จนกวา จะพบโหนดซึ่งไมมีลูกซาย ก็เปนอันเสร็จสิน้ การคนหา เขียนเปนโปรแกรมไดดังนี้ FUNCTION FindMin( t : BinarySearchTree ) : TreePtr; BEGIN IF t <> nil THEN WHILE t^.left <> nil DO t := t^.left; FindMin := t; END;3
ประสิทธิภาพการทํางานของการคนหาขอมูลที่มีคานอยที่สุด ก็เปนในทํานองเดียวกันกับการคนหา ขอมูล เราเริม่ ทีร่ าก เลือ้ ยลงมาทางซายเรือ่ ยๆ ดังนั้นเวลาการทํางานจึงแปรตามความสูงของตนไมเปน O(h) เชนกัน เรื่องที่ 11.1.4 การคนหาขอมูลที่มีคามากที่สุด และในทางกลับกัน เราสามารถคนหาโหนดที่มีขอมูลที่มากที่สุดได ดวยการเวิ่งทางขวาไปเรื่อยจน พบโหนดที่ไมมีลูกขวา เขียนเปนโปรแกรมไดดังนี้
)*! FUNCTION FindMax( t : BinarySearchTree ) : TreePtr; BEGIN IF t <> nil THEN WHILE t^.right <> nil DO t := t^.right; FindMax := t; END;3
หรือถาอยากจะเขียนแบบเวียนเกิดก็ทําไดโดยอาศัยความจริงที่วา หากรากของตนไมตนหนึ่งไมมี ลูกขวา รากนัน่ เองแหละมีคา มากทีส่ ดุ แตถารากมีลูกทางขวา โหนดที่มีคามากที่สุดของตนไมนี้ ยอยตองเปน โหนดที่มีคามากที่สุดของตนไมยอยดานขวาของราก เขียนโปรแกรมแบบเวียนเกิดไดดังนี้ FUNCTION FindMax( t : BinarySearchTree ) : TreePtr; BEGIN IF t = nil THEN FindMax := nil ELSE IF t^.right = nil THEN FindMax := t ELSE FindMax := FindMax( t^.right ); END;3
ประสิทธิภาพการทํางานของการคนหาขอมูลที่มีคามากที่สุด ก็เปนในทํานองเดียวกันกับการคนหา ขอมูลที่มีคานอยที่สุด เราเริม่ ทีร่ ากเลือ้ ยลงมาทางขวาเรือ่ ยๆ ดังนั้นเวลาการทํางานจึงแปรตามความสูงของ ตนไมเปน O(h) เชนกัน เรื่องที่ 11.1.5 การเพิ่มขอมูล จุดประสงคที่เห็นไดชัดของการเพิ่มขอมูลก็คือ หลังจากเพิ่มขอมูลแลว มีโหนดใหมซึ่งเก็บขอมูลใหม ถูกเพิ่มเขาไปเปนสวนหนึ่งของตนไม ตนไมก็มีจํานวนโหนดเพิ่มขึ้นอีกหนึ่ง โดยที่การจัดเก็บขอมูลยัง คงเปนไปตามกฏเกณฑที่ไดกําหนดไวของตนไมคนหาแบบทวิภาค สิ่งที่เราตองพิจารณาคือ โหนดใหม ที่เก็บขอมูลใหมนั้นอยูที่ใดในตนไม ตนไมจะเปลี่ยนโครงสรางไปมากนอยแคไหนหลังการเพิ่ม และใช เวลาในการเพิ่มเทาใด พิจารณาตนไมดานบนในภาพประกอบ 11.3 ตัวอยางการเปลี่ยนแปลงตนไม หลังการเพิ่ม ถาเราเพิม่ 10 เขาไปในตนไมนี้ จะทําใหตนไมหลังการเพิ่มเปลี่ยนแปลงไปอยางไร ตนไมดานลางทั้ง สามตนใน ภาพประกอบ 11.3 ตัวอยางการเปลี่ยนแปลงตนไมหลังการเพิ่ม ตางก็เปนผลลัพธทเ่ี ปนไปไดทง สิน้ เนื่องจากตนไมใหมมี 10 เปนขอมูลใหมที่เพิ่มขึ้น และตนไมทั้ง สามนี้ก็ยังรักษาความสัมพันธของขอมูลตามกฏของตนไมคนหาแบบทวิภาค
)*) # "
&
4
$
*
#
!4
" 4
#
& $
$
*
"
" !4
4
& $
4
* #
&
!4
*
ภาพประกอบ 11.3 ตัวอยางการเปลี่ยนแปลงตนไมหลังการเพิ่ม หากเราพิจารณาโดยคํานึงวาตนไมหลังการเพิ่มนาจะมีรูปรางที่เปนประโยชนตอการคนหาในอนาคต เราก็คงอยากไดตน ไมหลังการเพิม่ เปนแบบตนไมดา นลางรูปขวาสุด เนือ่ งจากเปนตนไมทเ่ี ตีย้ สุดในบรรดาตน ไมทั้งสาม ยอมทําใหประสิทธิภาพการคนหาดีตามไปดวย (เพราะวาเวลาการคนหาเปน O(h)) ปญหาที่ตาม มาก็คือวาตนไมกอนและหลังเพิ่มนั้นมีตําแหนงของขอมูลตางๆ ตางกันโดยสิ้นเชิง ตองมีการปรับตัวชี้มาก มาย อีกทั้งยังไมรูเลยดวยวาจะออกแบบอัลกอริทึมอยางไรเพื่อเปลี่ยนตนไมในเปนไปตามที่ตองการไดอยาง ไร ในเวลาอันรวดเร็ว แตถา เราพิจารณาตนไมดา นลางสองรูปทางซาย จะพบวาขอมูลเดิมในตนไมหลังการเพิ่มมีความ สัมพันธเหมือนเดิมทุกประการ โหนดใหมซึ่งมี 10 เปนขอมูลถูกเพิ่มเขาไปใหมในสองลักษณะที่แตกตางกัน ตนซาย 10 ถูกเพิ่มเปนใบใหม สวนตนกลาง 10 ถูกเพิ่มเปนรากใหม อยากใหนักศึกษาลองคิดตามวาการ เพิ่มขอมูลใหมใหเปนรากใหมนั้นคงทําไมไดในทุกกรณีหรอก สําหรับตัวอยางนี้เราโชคดีที 10 ซึ่งเปนขอมูล ใหมนั้นมีคามากกวาขอมูลทุกตัวในตนไมเดิม จึงตอเปนรากใหมไดตามที่แสดงในตนกลางของรูป (และยังมี กรณีโชคดีอีกกรณีเมื่อขอมูลใหมมีคานอยกวาขอมูลเดิม) แตถาขอมูลใหมที่เขามาเพิ่มมีคาอยูระหวางคาบาง คาในตนไมเดิม จะตอเปนรากไมได (ลองพิจารณาดูเองเชนถาขอมูลใหมเปน 7) สําหรับกรณีนําขอมูลมาสรางเปนใบใหมในตนไมนั้น เรามีตําแหนงใหใบอยูไดมากมาย ทั้งนี้ขึ้นกับคา ของขอมูลใหม ดังนั้นจะขอนําเสนอขั้นตอนการเพิ่มที่ไมซับซอน โดยนําขอมูลใหมมาสรางเปนใบ ใหมแลวตอเปนโหนดลูกของโหนดที่มีอยูเดิมในตนไม ปญหาที่ตองพิจารณาก็คือการหาตําแหนงที่ใบ ใหมนี้จะตอเปนลูก แนวคิดงายๆ ก็คือ ใหลองแกลงทําการคนหาขอมูลใหมที่กําลังจะเพิ่มนั้นในตนไม กอนการเพิ่ม แนนอนวายอมตองคนหาไมพบขอมูลใหมนี้ เนื่องจากเรากําหนดไวตั้งแตตนวาชุดขอมูล ที่เราสนใจจะจัดเก็บนั้นมีคาตางกันหมด การคนหาซึ่งไมพบนั้นจะตองจบที่ตนไมวาง (ซึ่งคือตัวชี้ที่เปน nil) ที่ตําแหนงใดตําแหนงหนึ่ง เราก็นําใบใหม (ซึ่งเก็บขอมูลใหมที่จะเพิ่ม) ตอเปนลูก ณ ตําแหนง นั้น ดูตัวอยางในการเพิ่ม 10 ในตนไมดานบนของภาพประกอบ 11.3 ตัวอยางการเปลี่ยนแปลงตนไม หลังการเพิ่ม
)*# ถาลองคนหา 10 ดูจะตองจบที่ตัวชี้ลูกขวาของโหนดที่เก็บ 9 ซึง่ เปน nil ก็เพียงแตสรางใบใหมซึ่ง เก็บ 10 แลวตอใหเปนลูกขวาของโหนดที่เก็บ 9 เพียงเทานีก้ ส็ าํ เร็จ (อยากใหนกั ศึกษาลองเปลีย่ นแนวคิดที่ กลาวมานีเ้ ปนโปรแกรมดู) คราวนี้มาดูอีกแนวคิดหนึ่งซึ่งมีขั้นตอนการทํางานเหมือนกับที่นําเสนอมาในยอหนาที่แลว จะตางกัน ก็เพียงแตคราวนี้เราจะคิดแบบเวียนเกิด นัน่ คือ procedure insert( t, x ) ซึ่งทําหนาที่เพิ่ม x เขาไปใน ตนไมคนหาแบบทวิภาค t จะมีขั้นตอนการทํางานดังนี้ • กรณีสน้ิ สุดเกิดขึน้ เมือ่ t เปนตนไมวา ง ก็เพียงแตสรางโหนดใหม ใสคา x เขาไป กําหนดใหลูก ทั้งสองของโหนดใหมนี้เปน nil • ถา t ไมเปนตนไมวาง และ x มีคานอยกวาที่รากของ t ก็ควรนํา x ไปเพิม่ เขาในตนไมยอ ย ทางซายของราก นั่นคือเรียกใช insert( t^.left, x) • ถา t ไมเปนตนไมวาง และ x มีคามากกวาที่รากของ t ก็ควรนํา x ไปเพิ่มเขาในตนไมยอยทาง ขวาของราก นั่นคือเรียกใช insert( t^.right, x) เขียนไดเปนโปรแกรมปาสกาลขางลางนี้ (ขอเนนวาตัวแปร t ทีร่ บั เขาไปนัน้ เปนแบบ var อยากให นักศึกษาลองดูคดิ วา ถาไมใส var อะไรจะเกิดขึน้ และถายืนกรานจะไมใส var จะตองเปลี่ยนแปลงตัว โปรแกรมอยางไร จึงทํางานถูกตอง) PROCEDURE Insert( VAR t : BinarySearchTree; x : integer ); BEGIN IF t = nil THEN BEGIN new( t ); t^.element := x; t^.left := nil; t^.right := nil; END ELSE IF x < t^.element THEN Insert( t^.left, x ) ELSE IF x > t^.element THEN Insert( t^.right, x ); END;3
ตองขอชี้แจงตรงนี้เล็กนอยวา เพื่อใหโปรแกรมที่เขียนนี้ดูกระทัดรัด จึงขอละการตรวจสอบความผิด พลาดในการจองโหนดของคําสัง่ new วาเกิดกรณีไมมีหนวยความจําใหจองหรือไม เนื่องจากขั้นตอนวิธีการเพิ่มขอมูลที่ไดกลาวมานี้ เรานําขอมูลใหมมาสรางเปนใบใหมตอเขาเปนลูกของโหน ดมที่มีอยูในตนไม ดังนัน้ ประสิทธิภาพการทํางานก็จะเปน O(h) เนือ่ งจากกรณีเลวสุดก็คอื การตอเปนใบใหม ของโหนดที่อยูลึกสุดในตนไม
)*$ เรื่องที่ 11.1.6 การลบขอมูลที่มีคามากที่สุด กอนที่จะไปอธิบายเรื่องการลบขอมูล ขอนําเสนอขั้นตอนการลบขอมูลที่มีคามากที่สุดกอน และที่ พิเศษก็คือนอกจากลบทิ้งออกจากตนไมแลว ที่เราจะเขียนนี้เปนฟงกชันซึ่งคืนโหนดที่ถูกลบออกมา ดวย (ซึ่งเปนโหนดซึ่งเก็บขอมูลที่มีคามากที่สุดของตนไม) พิจารณาภาพประกอบ 11.4 การลบขอมูล ที่มีคานอยที่สุด ตามไปดวย ถา x เปนโหนดที่มีคามากที่สุด การลบ x ออก ก็เพียงแตนําลูกซายของ x ไปตอเปนลูก ขวาของโหนดพอของ x ที่กระทําเชนนี้ไดก็เพราะวาโหนดซึ่งเก็บขอมูลที่มีคามากที่สุดนั้นยอมจะตองไมมีลูก ทางขวาแนนอน แตอาจจะมีหรือไมมีลูกทางซายก็ได ...
... 5
...
5 6
... 7
7
ภาพประกอบ 11.4 การลบขอมูลที่มีคานอยที่สุด เราสามารถนําโปรแกรมการคนหาขอมูลทีม่ คี า มากทีส่ ดุ มาปรับเปลีย่ นเล็กนอยไดเปนการลบขอมูลที่ มีคามากที่สุดตามตองการไดดังนี้ FUNCTION DeleteMax( VAR t : BinarySearchTree ) : TreePtr; BEGIN IF t = nil THEN deleteMax := nil ELSE IF t^.right = nil THEN BEGIN DeleteMax := t; t := t^.left; END ELSE DeleteMax := DeleteMax( t^.right ); END;3
อยากใหนกั ศึกษาลองเปรียบเทียบ deleteMax ขางบนนี้กับ findMax ทีไ่ ดแสดงกอนหนานี้ ซึ่งมี หลักการทํางานคลายคลึงกันมาก การทํางานของ deleteMax แบงออกเปน 3 กรณีคอื กรณีที่ตนไม t เปนตนไมวา ง ในกรณีนี้หมายความวาไมมีโหนดที่ตองการเพราะตนไมไมมัสักโหนด ก็จะคืนคา nil กลับไป กรณีที่สองคือกรณีที่เปนตนไมที่ไมมีลูกทางขวา ก็ยอยแสดงวารากนั่นเองคือโหนดที่มีขอมูลที่มีคามากที่สุด โหนดรากจะถูกคืน พรอมทัง้ เปลีย่ นรากใหเปนลูกทางซายของรากเดิม และกรณีสดุ ทายก็คอื เมือ่ รากมีลกู ทาง ขวา ก็โยนภาระใหไปลบโหนดที่ขอมูลมีคามากที่สุดออกจากตนไมยอยทางขวา
)*% จากขัน้ ตอนการลบขางตนนี้ ซึ่งคลายกับการคนหาโหนดที่มีขอมูลมากที่สุด จึงมีเวลาการทํางาน เปน O(h) เหมือนกัน เพราะอยางมากสุดก็เลือ้ ยลงมาถึงใบทีล่ กึ สุดในตนไม เรื่องที่ 11.1.7 การลบขอมูล การลบขอมูลจะเปนการดําเนินการที่ออกจะซับซอนสักหนอย เมื่อเทียบกับการดําเนินการที่ผานๆ มา ลองพิจาณาเมื่อเราตองการลบขอมูล 3 ออกจากตนไมในภาพประกอบ 11.5 ตัวอยางการเปลี่ยน แปลงตนไมหลังการลบ ก กอนอื่นก็คงตองหาโหนดที่เก็บ 3 เสียกอน จากนั้นก็ลบโหนดนั้นออกดังแสดงในภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ข จะเห็นไดวาหลังลบโหนด 3 ออกจะแบงตนไมเดิมออกเปนสามสวน สิ่งที่เราตองทําก็คือประกบตน ไมทั้งสามตนใหเปนตนเดียวที่เปนตนไมคนหาแบบทวิภาค วิธีหนึ่งก็คือการยกใหตนไมยอยทางขวา ของโหนดที่ถูกลบใหมาแทนที่มันเอง จากนั้น (ตรงนี้อานชาๆหนอย) นํารากของตนไมยอยทางซาย ของโหนดที่ถูกลบมาตอเปนลูกทางซายโหนดที่ขอมูลมีคานอยที่สุดในตนไมยอยทางขวาของโหนดที่ถู กลบ ในตัวอยางนี้ (ดูภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ค) ก็คือยกโหนดที่เก็บ 6 มาเแทนตําแหนงเดิมของโหนดที่เก็บ 3 (นั่นคือเปนลูกของโหนดที่เก็บ 0) นําโหนดที่เก็บ 2 มาตอเปนลูกซายของโหนดที่เก็บ 4 ซึ่งโหนดนี้ของเดิมตองไมมีลูกซาย เพราะเปน โหนดที่มีขอมูลนอยที่สุดในตนไมยอยทางขวาของโหนดที่เก็บ 3 (ซึ่งเราไดลบออก) เพียงเทานี้ก็ ประกบตนไมตางๆ ในภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ข ใหเปนตนเดียวและมีคุณสมบัติเปนตนไมคนหาแบบทวิภาค ก
4
ข
4
# "
&
!
%
" *
4
"
%
* !4
$
ค
&
$
! !4
$
%
&
4
ง "
*
!
&
!4
% $
* !4
!
ภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ
)*& หรือวาจากภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ข เราจะเลือกทําในทางกลับกันก็คือยกใหตนไมยอยทางซายของโหนดที่ถูกลบใหมาแทนที่มันเอง นํา รากของตนไมยอยทางขวาของโหนดที่ถูกลบมาตอเปนลูกทางขวาโหนดที่มีคามากที่สุดในตนไมยอย ทางซายของโหนดที่ถูกลบ สําหรับตัวอยางขางบนนี้ จะไดตนไมในภาพประกอบ 11.5 ตัวอยางการ เปลี่ยนแปลงตนไมหลังการลบ ง นั่นคือยกโหนดที่เก็บ 2 มาเแทนตําแหนงเดิมของโหนดที่เก็บ 3 (นั่นคือเปนลูกของโหนดที่เก็บ 0) นําโหนดที่เก็บ 6 มาตอเปนลูกซายของโหนดที่เก็บ 2 ซึ่งโหนดนี้ของเดิมตองไมมีลูกขวา เพราะเปน โหนดที่มีขอมูลมากที่สุดในตนไมยอยทางซายของโหนดที่เก็บ 3 (ซึ่งเราไดลบออก) เพียงเทานี้ก็ ประกบตนไมตางๆ ในภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ข ใหเปนตนเดียวและมีคุณสมบัติเปนตนไมคนหาแบบทวิภาคเชนกัน ถาเปรียบเทียบตนไมหลังการลบในภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ค และ ภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการลบ ง ก็คงเห็นไดชัดวาเราคงอยากไดตนไมในภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไมหลังการ ลบ ง มากกวาเพราะเตี้ยกวา การลบแลวไดตนไมในภาพประกอบ 11.5 ตัวอยางการเปลี่ยนแปลงตนไม หลังการลบ ค นัน้ ดูผลแลวรูส กึ แปลกในแงทว่ี า เราลบขอมูลออกจากตนไมแลวทําใหตนไมสูงขึ้น สูงกวากอนลบ ดวย จึงเห็นไดวาแนวคิดการลบดวยวิธีที่นําเสนอมานี้ใชไดไมคอยดีนัก คราวนีเ้ ราจะนําเสนอีกวิธหี นึง่ กอนอื่นขอใหทําความเขาใจกอนวาสิ่งที่เราตองการก็คือการลบขอมูล ออกจากตนไม เราไมไดบอกวาจะตองลบโหนดที่เก็บขอมูลที่ตองการลบออกจากตนไม จึงจะขอใชแนวคิดนี้ ในการลบขอมูล กอนอืน่ ขอจําแนกการลบออกเปนสามกรณีดว ยกันคือ • กรณีทข่ี อ มูลทีต่ อ งการลบถูกเก็บอยูท ใ่ี บ กรณีนง้ี า ยทีส่ ดุ เพราะเพียงแตเปลี่ยนตัวชี้ที่เคยชี้ใบ นั้นใหไปชี้ nil ก็เปนอันเสร็จ • กรณีที่ขอมูลที่ตองการลบถูกเก็บอยูในโหนดที่มีเพียงลูกเดียว กรณีนก้ี ง็ า ยอีก เพียงแตยกลูกที่ มีอยูหนึ่งเดียวนั้นมาแทนที่ตนเอง (การลบโหนดทีข่ อ มูลมีคา มากทีส่ ดุ ก็เปนกรณีพเิ ศษของกรณี นี้)
)*' • กรณีทข่ี อ มูลทีต่ อ งการลบถูกเก็บอยูใ นโหนดทีม่ สี องลูก สมมติวาโหนดดังกลาคือ โหนด x กรณี นี้เองที่เราจะไมลบโหนด x ออก แตจะอาศัยการ deleteMax โหนดที่ขอมูลมีคามากที่สุดในตน ไมยอยทางซายของโหนด x ออกมา เพื่อนําขอมูลที่โหนดนั้นมาเก็บไวที่โหนด x ซึ่งก็เทากับวา ขอมูลเดิมที่โหนด x ถูกลบทิ้งไป ขออธิบายกรณีสุดทายใหละเอียดขึ้นอีกหนอย โดยดูภาพประกอบ 11.6 ขั้นตอนการลบขอมูล A ประกอบดวย เราตองการลบ A ออก เนื่องจากโหนดที่เก็บ A มีสองลูก (ภาพประกอบ 11.6 ขั้นตอน การลบขอมูล A ก) จึงทําการ deleteMax เพื่อลบโหนดที่ขอมูลมีคามากที่สุดในตนไมยอยทางซายของโหนดที่เก็บ A ออกมา ไดโหนดซึ่งเก็บขอมูล B (ภาพประกอบ 11.6 ขั้นตอนการลบขอมูล A ข) จากนั้นคา B แทนที่คา A (ภาพประกอบ 11.6 ขั้นตอนการลบขอมูล A ค) ก็เปนอันเสร็จสิน้ การลบ A ออกมาตนไม การนํา B มาแทน A ในลักษณะดังกลางสรางความมัน่ ใจไดวาตนไมหลังการลบนั้นยังคงเปตนไมคนหาแบบทวิภาค อีกทั้งประกันไดวาตนไมไมมีทางสูงขึ้นได 37
7
8
38 38
ก
ข
ค
ภาพประกอบ 11.6 ขั้นตอนการลบขอมูล A 4
4
4
%
%
" !
"
9 $
+
#
&
ก
$
!
* !4
"
9 #
+ &
$
!
*
9 #
+
!4
ข
*
&
ค
ภาพประกอบ 11.7 ตัวอยางการลบขอมูล มาดูกันสักหนึ่งตัวอยางในภาพประกอบ 11.7 ตัวอยางการลบขอมูล
!4
)*( เราตองการลบ 5 ออกจากตนไมในภาพประกอบ 11.7 ตัวอยางการลบขอมูล ก เริม่ จากราก 0 พบวาตองไปลบที่ตนไมยอยทางขวา (เพราะ 5 > 0) จากนั้นก็พบ 5 เนื่องจาก 5 อยูใ น โหนดที่มีสองลูก ก็ไปลบโหนดที่ขอมูลมีคามากสุดในตนไมยอยทางซาย ซึ่งก็คือโหนดที่มีขอมูล 4 (กํากับดวยลูกศรในภาพประกอบ 11.7 ตัวอยางการลบขอมูล ก) ก็ลบโหนดนี้ออก ไดดังภาพประกอบ 11.7 ตัวอยางการลบขอมูล ข (โดยการเลื่อน 3 ขึ้นมาแทน 4) จากนั้นทําสําเนาขอมูล 4 ไปเก็บไวในโหนดซึ่ง 5 เก็บอยู (ดูภาพ ประกอบ 11.7 ตัวอยางการลบขอมูล ข) ไดผลลัพธของการลบ 5 ออกเปนดังแสดงในภาพประกอบ 11.7 ตัวอยางการลบขอมูล ค สําหรับตัวโปรแกรมนั้นก็แบงเปนสองสวน สวนแรกคือการคนหาขอมูลเพือลบ เมื่อพบแลวก็แบง ออกเปนสามกรณียอ ยขางตน ดังนี้
)*" PROCEDURE Delete( VAR t : BinarySearchTree; x : integer ); VAR tmp : TreePtr; BEGIN IF t <> nil THEN IF x < t^.element THEN Delete( t^.left, x ) ELSE IF x > t^.element THEN Delete( t^.right, x ) ELSE IF (t^.left = nil) and (t^.right = nil) THEN BEGIN dispose( t ); t := nil; END ELSE IF t^.left = nil THEN BEGIN tmp := t; t := t^.right; dispose( tmp ); END ELSE IF t^.right = nil THEN BEGIN tmp := t; t := t^.left; dispose( tmp ); END ELSE BEGIN tmp := deleteMax( t^.left ); t^.element := tmp^.element; dispose( tmp ); END; 3 END;
ขอใหนกั ศึกษาไปคิดตอวาสําหรับรณีสดุ ทายของการลบนัน้ แทนที่เราจะ deleteMax จากตนไมย ยอทางซาย เราจะใชการ deleteMin จากตนไมยอยทางขวาไดหรือไม เพราะอะไร สําหรับในประเด็นของเวลาการทํางานแลว การลบประกอบดวยการคนหา เมือ่ พบและเปนกรณีทม่ี ี สองลูก ก็ตอ งเลือ้ ยลงตอไป ในกรณีเลวรายสุด ก็เลือ้ ยตอไปจนพบใบทีอ่ ยูล กึ สุด ดังนั้นการลบขอมูลจึงใชเวลา เปน O(h)
)!* ตอนที่ 11.2 ความลึกของโหนดและการเรียงลําดับขอมูลแบบ Tree Sort หัวเรื่อง เรือ่ งที่ 11.2.1 ความลึกของโหนด เรือ่ งที่ 11.2.2 การเรียงลําดับขอมูลแบบตนไม แนวคิด 1. ความลึกของโหนดโดยเฉลี่ยในตนไมคนหาแบบทวิภาคที่ไดจากการเพิ่มขอมูลเชิงสุมเปน O(log n) 2. การแวะผานแบบตามลําดับในตนไมคนหาแบบทวิภาค จะไดลําดับขอมูลที่ถูกแวะเรียงจากนอย ไปหามาก 3. การเรียงลําดับขอมูลแบบ Tree sort อาศัยการสรางตนไมคนหาแบบทวิภาค กับการแวะผาน แบบตามลําดับ เพื่อใหไดลําดับของชุดขอมูลเรียงจากนอยไปมาก วัตถุประสงค หลังจากที่ศึกษาตอนที่ 11.2 แลว นักศึกษาสามารถ 1. วิเคราะหประสิทธิภาพเชิงเวลาของการดําเนินการตางๆ ในตนไมคนหาแบบทวิภาคได 2. บอกตัวอยางการประยุกตใชสแตกในสาขาวิชาวิทยาการคอมพิวเตอรได เรื่องที่ 11.2.1 ความลึกของโหนด จากการดําเนินการตางๆ ทีไ่ ดนาํ เสนอมาบนตนไมคน หาแบบทวิภาคนัน้ สรุปไดวา ใชเวลาเปน O(h) ซึ่งคือแปรตามความสูงแบบเชิงเสนทั้งสิ้น โดยถากลาวใหละเอียดขึ้นจะไดวาประสิทธิภาพการทํางานของการ ดําเนินการตางๆ บนตนไม จะขึ้นกับความลึกของโหนดที่ตองยุงเกี่ยวดวยในการดําเนินการนั้นๆ แลว โหนด ตางๆ ในตนไมคนหาแบบทวิภาคมีความลึกไดตั้งแต 0 ถึง n – 1 ขึ้นกับลักษณะของตนไมซึ่งเปนชวงที่กวาง มากเหลือเกิน เราจะแสดงใหเห็นวาความลึกโดยเฉลี่ยของโหนดในตนไมซึ่งสรางดวยการเพิ่มขอมูลเชิงสุม นัน้ เปน O( log n) แตกอนจะวิเคราหใหเห็นจริงทางคณิตศาสตร จะขออธิบายใหเห็นภาพกอนวา ถาเราสรางตนไมคน หาแบบทวิภาคดวยการเพิ่มขอมูลที่สุมขึ้นมาจํานวน n ตัว ในที่สุดแลวมีโอกาสสูงมากที่จะไดตนไม เตี้ยๆ เปน "พุม" (แทนที่จะเปนตนสน) สมมติวาหลังจากเพิ่มขอมูลไปไดหาแสนตัว ก็มีโอกาสไดตนไม หลายแบบ (ขึ้นกับลักษณะของขอมูลที่เพิ่ม) ภาพประกอบ 11.8 ตัวอยางตนไมคนหาแบบทวิภาค ขนาดใหญ
)!! แสดงตัวอยางตนไมสามลักษณะ แบบที่สูงมากๆ แบบที่เปนพุมแตก็ยังมีสวนแหวง และแบบที่เตี้ย สุดๆ ตนไมที่มีหาแสนโหนดก็ตองมีตัวชี้หนึ่งลานตัว (โหนดละสองตัวชี้สําหรับลูกซายและลูกขวา) แตมีเพียง 499,999 ตัวชี้เทานั้นที่ไดชี้โหนดอื่นในตนไม (เพราะทุกๆ โหนดในตนไมตองมีโหนดอื่นชี้ ยกเวนรากที่ไมมี โหนดแมจึงไมมีใครชี้) ตัวชี้ที่เหลือ 500,001 ตัวจึงมีคาเปน nil นั่นหมายความวาถาเราเพิ่มขอมูลตัวใหมซึ่ง จะถูกสรางเปนใบ ตอเพิ่มเขาไปในตนไมนี้ ก็มที ล่ี งได 500,001 ตําแหนง ถาตนไมสูง ยอมแสดงวาตนไมเบี้ยว เอียง สงผลใหมีที่ตอใบใหมมากมายที่จะไมเพิ่มความสูงของตนไม สวนตนไมเติย้ สุดๆ นั้นการเพิ่มใบก็มี โอกาสทําใหตนไมสูงขึ้นไดมากกวา แตคงเห็นชัดไดวายิ่งตนไมใหญมาก การเพิ่มขอมูลที่จะทําใหตนไมสูง ขึ้นเรื่อยๆ ยอมมีโอกาสนอย เพราะมีที่ตอใบมากมายซึ่งไมทําใหตนไมสูงขึ้น
ภาพประกอบ 11.8 ตัวอยางตนไมคนหาแบบทวิภาคขนาดใหญ คราวนีเ้ ราจะวิเคราะหเชิงคณิตศาสตรกนั กอนอื่นขอปรับตนไมคนหาแบบทวิภาดใหถือวาตัวชี้ที่เปน nil นัน้ คือใบของตนไม ถาเปนเชนนีข้ อ มูลทุกๆ ที่เราจัดเก็บจะอยูโหนดภายในของตนไม แลวเราจะวิเคราะห หาความลึกเฉลี่ยของโหนดภายใน และความลึกเฉลี่ยของโหนดภายนอก (ซึ่งคือใบ) จะขอเริ่มดวยการหาคา D1(n) ซึ่งคือความลึกเฉลี่ยของโหนดภายในของตนไมคนแบบทวิภาคที่มีขอมูล n ตัว กําหนดให I(n) คือผล รวมของความยาวของวิถีจากรากถึงทุกๆ โหนดภายใน เรียกคานี้วาความยาวของวิถีภายใน (internal path length) จะไดวา !1":#;3<3
=
$:#; 3" #
>
ภาพประกอบ 11.9 ตนไมคนแบบทวิภาค พิจารณาภาพประกอบ 11.9 ตนไมคนแบบทวิภาค
)!) ถาตนไมทั้งตนมี n โหนด และตน L มี k โหนด ตน R ก็จะมี n–k–1 โหนด ถาพิจารณาเฉพาะตน L (ไมคิดวาเปนลูกของใคร) L ยอมมีความยาวของวิถีภายในเปน I(k) และในทํานองเดียวกัน R ยอมมีความยาว ของวิถีภายในเปน I(n–k–1) แตเมื่อพิจารณา L ตอนที่เปนตนไมยอยทางซาย ทุกๆ โหนดใน L ยอมมีความลึก เพิม่ อีกหนึง่ ระดับ และทํานองเดียวกับตน R ที่เมื่อเปนตนไมยอยทางขวา ทุกๆ โหนดก็จะลึกลงอีกหนึง่ ระดับ เชนกัน เขียนเปนความสัมพันธเวียนเกิดของ I(n)ไดดงั นี้ I(n) = (I(k) + k )+ (I(n–k–1) + n–k–1) = I(k)+ I(n–k–1) + n–1 เนื่องจากตน L มีสิทธิ์ที่จะมีจํานวนโหนดภายในไดตั้งแต 0 ถึง n–1 โดยที่แตละแบบมีโอกาสเทาๆ กัน ทั้งนี้เพราะวาตนทางซายจะมีกี่ตัว ก็ขึ้นกับคาของรากวาเปนอันดับที่เทาไรในขอมูล n ตัว เนื่องจากขอมูล ทีเ่ พิม่ เขามานัน้ เปนคาสุม โอกาสทีร่ ากจะเปนอันดับใดนัน้ จึงถือไดวา เทาๆ กัน ดังนั้นคาเฉลี่ยของความยาว ของวิถีภายในยอมเทากับ $ : #; =
! # −! ($ :% ; + $ :# − % − !; + # − !) # % =4
=
! # −! ($ :% ; + $ :# − % − !; )+ :# − !; # % =4
=
" # −! $ :% ; + :# − !; # % =4
สามารถหาผลเฉลยของ I(n) ไดดงั นี้ #$ :#; = "
# −!
$ :% ; + #:# − !;
คูณ n ตลอด
% =4
:# − !; $ :#; = "
#−"
$ :% ; + :# − !;:# − ";
เปลีย่ น n เปน n–1
% =4
#$:#;3 <3:#?!;$:#@!;3?3":#@!;3 3
A3:#?!;$:#@!;3?3"#"
" $:#; $:#B!; 33 A3 333?3 33 :#?!; #?! # ! $:!; ! ! 3?" 3? 3?C? 33 3 <3 " #?! # $
$:#;3
A3":#?!;&#?!3
นําสองความสัมพันธขางตนมาลบกัน ตัด –1 ตัวขวาสุดทิ้ง (จะทําใหงายตอการ หาผลเฉลย) หาร n(n+1) ตลอด จากนัน้ คลีค่ วาม สัมพันธเวียนเกิด โดยที่ I(1) = 0 ผลบวก ที่ปรากฎในวงเล็บคลายจํานวนฮารมอนิก Hn+1 จากความรู Hn = O(log n)
3
<32:3#3DEF3#3; 2:#DEF3#; ดังนัน้ จาก D1(n) 3<3$:#; 33= 3= O( log n ) # #
เราสามารถหาความลึกเฉลี่ยของใบ (DG(n)) ของตนไมคนแบบทวิภาคไดจากความยาวของวิถีภาย นอก (E(n) : external path length) ซึ่งคือผลรวมของความยาวของวิถีจากรากถึงใบทุกใบ โดย DG(n) 3<3':#; 3 #?!
)!# (ที่ตองหารดวย n+1 ก็เพราะวาตนไมที่มีโหนดภายใน n โหนดจะมี n+1 ใบ) เราสามารถหา E(n) ไดจากสม การ E(n) = I(n) + 2n (สามารถพิสจู นไดงายๆ ดวยอุปนัยเชิงคณิตศาสตร ซึ่งจะไมขอกลาวรายละเอียดในที่ 2:#DEF3#; นี้) เมื่อ I(n) = O(n log n) จะไดวา E(n) = O(n log n) ดวย ดังนัน้ DG(n) 3<3':#; 33= 33= O( log n ) #?! #?!
สรุปไดวาความลึกเฉลี่ยของทั้งโหนดภายในและโหนดภายนอกเปน O( log n ) การคนหาขอมูลที่ใช เวลาแปรตาม D1(n) กรณีคน พบ และแปรตาม DG(n) กรณีคน ไมพบ รวมถึงการเพิ่มและการลบที่ใชเวลาแปร ตาม DG(n) จึงใชเวลาในกรณีเฉลีย่ เปน O( log n ) ทัง้ สิน้
)!$ เรื่องที่ 11.2.2 การเรียงลําดับขอมูลแบบตนไม ขอปดทายเรื่องของตนไมคนหาแบบทวิภาคดวยการเรียงลําดับขอมูลที่มีชื่อเรียกกันวา Tree Sort กอนอื่นขอทวนอีกครั้งวาการแวะผานตนไมแบบตามลําดับนั้น เราจะแวะผานตนไมยอ ยทางซายของรากกอน ตามดวยแวะโหนดราก แลวจึงแวะผานตนไมยอยทางขวาของราก เมื่อนํามาใชกับตนไมคนหาแบบทวิภาคจะ ไดวาเปนการแวะโหนดตางๆ ทั้งหลายที่ขอมูลมีคานอยกวาราก ตามดวยแวะโหนดราก และปดทายดวยการ แวะโหนดตางๆ ที่ขอมูลมีคามากกวาราก สรุปไดวา ขอมูลตางๆ ในตนไมที่ถูกแวะผานแบบตามลําดับ จะเปน ไปตามลําดับของขอมูลเหลานัน้ เรียงจากนอยสุดไปมากสุด หมายความวาขอมูลที่นอยที่สุดถูกแวะกอนใคร เพือ่ น ดังนัน้ เราสามารถออกแบบวิธกี ารเรียงลําดับขอมูลไดงา ยๆ วิธีหนึ่งคือการนําชุดขอมูลที่ตองการ เรียงลําดับมาจัดเก็บในตนไมคนหาแบบทวิภาค แลวตามการแวะผานตนไมแบบตามลําดับ ก็จะทําใหขอมูล ตางๆ ถูกแวะเรียงตามลําดับจากนอยไปมาก การสรางตนไมคนหาแบบทวิภาคดวยการคอยเพิ่มขอมูลทั้ง n ตัว ใชเวลา O(nh) บวกกับเวลาในการแวะผานแบบตามลําดับซึ่งเปน O(n) จึงไดเวลารวมของการเรียงลําดับ ขอมูลแบบนี้เปน O(nh + n) = O(nh) ในกรณีที่ขอมูลที่นํามาเพิ่มในตนไมระหวางการสรางนั้นมีคาที่ไมคอยมี ระเบียบเทาใด จะไดวา h = O(log n) สรุปไดวาใชเวลารวมเปน O(nlog n) สําหรับลักษณะของขอมูลที่นํามา เพิ่มดังที่กลาวถึง แตถาขอมูลที่เขามาเพิ่มมีระเบียบมาคือไลเพิ่มจากตัวนอยสุดไปยังตัวมากสุด (หรือในกรณี อืน่ ๆ ที่จะไดตนไมที่เปน "สายเดีย่ ว" นั่นคือมีโหนดเปนจํานวน O(n) ที่มีเพียงหนึ่งลูก) ก็จะทําให h = O(n) สง ผลใหการเรียงลําดับแบบนี้ใชเวลาเปน O(n2) กิจกรรม 11.1 ปญหาการหารสามคูณสอง : วากันวาจํานวนเต็มใดๆ สามารถหาไดดว ยการเริม่ จากเลข 1 แลวหาร สาม (ปดเศษทิ้ง) และ/หรือคูณสอง ไปเรือ่ ยๆ เชน 10 = 1×2×2×2×2/3×2 เปนตน โปรแกรมขางลางนี้วิธีหา คําตอบของปญหานี้ อยากใหนักศีกษาทําความเขาใจกับการทํางานหลักของโปรแกรมนี้กอน จากนัน้ เติม insert และ find ที่ใชจัดเก็บและคนหาขอมูลดวยตนไมคนหาแบบทวิภาค เพื่อใหโปรแกรมใชงานไดจริง
)!% PROGRAM M2d3; USES Wincrt,M2D3U; CONST MaxDepth = 100; TYPE Solution = ARRAY[1..MaxDepth] of String; VAR n : integer; x : Solution; i : Integer; t : BinarySearchTree; FUNCTION M2D3_DFS( VAR t : BinarySearchTree; VAR x : Solution; k, n, s : integer ) : boolean; VAR s1 : integer; found : boolean; BEGIN IF k = MaxDepth THEN found := false ELSE BEGIN insert( t, s ); IF ( s = n ) THEN found := true ELSE BEGIN found := false; s1 := s div 3; IF find( t, s1 ) = nil THEN BEGIN x[k+1] := '/3'; found := M2D3_DFS( t, x, k+1, n, s1 ); END; s1 := 2*s; IF (not found) and (find( t, s1 ) = nil) 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.