Issuu on Google+

!"#

แผนการสอนประจําบทเรียน รายชือ่ อาจารยผจู ดั ทํา สมชาย ประสิทธิจ์ ตู ระกูล หัวขอของเนื้อหา ตอนที่ 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.


data structure lesson11