ข้ามไปเนื้อหา

ปัญหาระดับบรรพบุรุษ

จากวิกิพีเดีย สารานุกรมเสรี

ในทฤษฎีกราฟ ปัญหาระดับบรรพบุรุษ (อังกฤษ: level ancestor problem) เป็นปัญหาในการนำต้นไม้ T มาคำนวณล่วงหน้าเพื่อตอบคำถามว่า จุดยอดที่มีความลึกระดับ d จากราก และทิศทางมุ่งสู่จุดยอด v คือจุดยอดอะไร เขียนฟังก์ชันแทนปัญหานี้ได้ว่า LA(v,d) โดยความลึกของจุดยอด v คือจำนวนเส้นเชื่อมจากรากถึงจุดยอดดังกล่าวบนวิถีสั้นสุดจากรากถึงจุดยอดนั้น

ปัญหาระดับบรรพบุรุษ

[แก้]
รูป 1. รูปแสดงวิถีสั้นสุดจากราก (จุดยอด r) ถึงจุดยอด v และแสดงให้เห็นว่า LA(v,2) = h

ปัญหาระดับบรรพบุรุษเป็นปัญหาพื้นฐานบนกราฟต้นไม้อย่างหนึ่ง มีจุดประสงค์คือต้องการหาจุดยอดที่มีความลึก d จากจุดยอดราก r และมีทิศทางมุ่งเข้าสู่จุดยอด v ปัญหานี้มีความสัมพันธ์กับปัญหาบรรพบุรุษร่วมต่ำสุดซึ่งถามเกี่ยวกับจุดยอดที่เป็นบรรพบุรุษร่วมของจุดยอด 2 จุดและมีความลึกมากที่สุด และจะเห็นได้ว่าแนวคิดของปัญหาระดับบรรพบุรุษหลาย ๆ อย่างก็ได้นำไปใช้ในการแก้ไขปัญหาบรรพบุรุษร่วมต่ำสุดด้วย

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

เพื่อที่จะลดเวลาการตอบคำถามลง จึงอาจสร้างแถวลำดับ 2 มิติ และเก็บคำตอบทั้งหมดไว้ในตาราง ด้วยวิธีนี้การตอบคำถามก็จะเป็นเพียงการเรียกข้อมูลจากแถวลำดับมาตอบเท่านั้นซึ่งใช้เวลา O(1) อย่างไรก็ตามในการที่จะสร้างตารางคำตอบขึ้นมาก็ต้องใช้เวลาถึง O(n2)

มีขั้นตอนวิธีมากมายที่คิดขึ้นมาเพื่อให้แก้ไขปัญหาระดับบรรพบุรุษได้โดยมีประสิทธิภาพดียิ่งขึ้น[1][2] ตารางด้านล่างนี้แสดงถึงขั้นตอนวิธีที่มีประสิทธิภาพแตกต่างกันออกไปในการแก้ปัญหาระดับบรรพบุรุษ

ขั้นตอนวิธี เวลาคำนวณล่วงหน้า เวลาตอบคำถาม
ดำเนินการตรง ๆ O(1) O(n)
เก็บคำตอบในตาราง O(n2) O(1)
ขั้นตอนวิธีตัวชี้กระโดด O(n log n) O(log n)
การแยกส่วนของวิถียาวสุด O(n) O(√n)
การแยกส่วนของบันได O(n) O(log n)
ขั้นตอนวิธีบันได O(n log n) O(1)
ขั้นตอนวิธีของ Dietz O(n) O(1)

ขั้นตอนวิธีตัวชี้กระโดด

[แก้]

ขั้นตอนวิธีตัวชี้กระโดด (Jump pointer algorithm)[1] เป็นขั้นตอนวิธีแบบกำหนดการพลวัต อาศัยการคำนวณล่วงหน้าโดยการสร้างแถวลำดับ 2 มิติ ชื่อ dp ขนาด n × log n โดย dp[v][p] จะชี้ไปยังจุดยอดที่อยู่สูงขึ้นไปจากจุดยอด v เป็นจำนวน 2p ขั้น อาศัยความสัมพันธ์ว่า dp[v][p] = dp[dp[v][p - 1]][p - 1] และกรณีฐานว่า dp[v][0] คือพ่อของจุดยอด v ก็จะสามารถเติมตาราง dp ได้ในเวลา O(n log n)

ในการหาคำตอบ ให้เริ่มต้นจากจุดยอด v และกระโดดโดยใช้ตัวชี้จาก dp ไปให้สูงที่สุดแต่ไม่ไปไกลเกินจุดยอดเป้าหมาย และกระโดดต่อจากจุดยอดนั้นไปเรื่อย ๆ จนกว่าจะเจอจุดยอดเป้าหมาย เนื่องจากการหาตัวชี้ที่จะใช้นั้นสามารถทำได้ใน O(1) จากสูตร และการกระโดดแต่ละครั้งจะเหลือระยะห่างน้อยลงไป 2 เท่า จึงทำให้ใช้เวลา O(log n) ในการกระโดด

ขั้นตอนวิธีบันได

[แก้]

ขั้นตอนวิธีบันได (Ladder algorithm)[1] ใช้แนวคิดที่ว่าปัญหาระดับบรรพบุรุษสามารถแก้ได้อย่างง่ายดายหากกราฟต้นไม้ที่จะประมวลผลเป็นกราฟวิถี เนื่องจากสามารถเปลี่ยนวิถีดังกล่าวไปเป็นแถวลำดับได้ และในการตอบคำถามก็เป็นเพียงการเรียกข้อมูลจากแถวลำดับโดยตรง อย่างไรก็ตามแนวคิดนี้ใช้ไม่ได้กับกราฟต้นไม้ทั่วไปที่มีกิ่งแตกออกมามากมาย จึงใช้วิธีแบ่งต้นไม้ออกเป็นหลาย ๆ วิถี และเก็บข้อมูลของวิถีต่าง ๆ ไว้ในแถวลำดับ และดำเนินการด้วยแนวคิดดังที่กล่าวมา สังเกตว่าด้วยวิธีนี้แล้วการกระโดดเพื่อหาคำตอบปัญหาระดับบรรพบุรุษแต่ละครั้งสามารถกระโดดข้ามไปเป็นวิถีได้เลย

การแยกส่วนของวิถียาวสุด

[แก้]
รูป 2. กรณีเลวร้ายสุดของการแยกส่วนของวิถียาวสุด ซึ่งจะทำให้การแก้ปัญหาระดับบรรพบุรุษใช้เวลา O(√n)

การแยกส่วนของวิถียาวสุด (Longest path decomposition)[3] เป็นการหาวิถียาวสุดจากรากของต้นไม้ไปถึงใบทั้งหลาย หลังจากหาได้แล้วก็จะนำจุดยอดในวิถียาวสุดไปเรียงเก็บไว้ในแถวลำดับ 'LP' จากรูป 1. ก็จะได้ว่าวิถียาวสุดคือวิถีจาก rv ดังนั้น LP ของ r ก็จะมีข้อมูล LP[r][0] = r, LP[r][1] = a, ..., LP[r][5] = v หลังจากเก็บข้อมูลวิถีเสร็จแล้วก็จะทำการลบวิถีนี้ออกจากต้นไม้ ทำให้ต้นไม้ถูกแยกออกเป็นหลายส่วน ก็ให้เริ่มต้นขั้นตอนดังกล่าวอีกรอบโดยพิจารณาต้นไม้ย่อยทั้งหลายที่เหลือเป็นต้นไม้ใหม่ เมื่อดำเนินการไปเรื่อย ๆ ก็จะไม่เหลือจุดยอดในต้นไม้อีก และแต่ละจุดยอดก็จะเก็บอยู่ในแถวลำดับเพียงแค่ครั้งเดียวเท่านั้น

ในการดำเนินการดังกล่าว สามารถกระทำได้โดยทำการค้นหาในแนวลึก ซึ่งใช้เวลาในการดำเนินการ O(n) ส่วนในการหาคำตอบปัญหาระดับบรรพบุรุษ LA(v, d) ก็ให้เริ่มจากจุดยอด v และหาว่า v อยู่ใน LP ชุดไหน จากนั้นก็กระโดดไปยังต้นของ LP และกระโดดขึ้นหนึ่งขั้น และดำเนินการแบบนี้ซ้ำไปเรื่อย ๆ จนกว่าจะกระโดดไปเจอต้นของ LP ที่มีความลึกน้อยกว่า d ซึ่งแสดงให้เห็นว่า LP ชุดนั้นมีจุดยอดคำตอบอยู่ ก็สามารถคำนวณหาตำแหน่งของจุดยอดนั้นและตอบได้ภายในเวลา O(1) เลย

อย่างไรก็ตาม ในกรณีเลวร้ายมากสุด (รูป 2.) จะต้องใช้เวลาในการกระโดดระหว่างวิถีต่าง ๆ มากถึง Θ(√n) ซึ่งค่านี้สามารถลดได้จากการใช้การแยกส่วนของบันไดซึ่งเป็นหัวข้อถัดไป

การแยกส่วนของบันได

[แก้]

การแยกส่วนของบันได (Ladder decomposition)[3] ใช้แถวลำดับ 'Ladder' มีลักษณะขั้นตอนวิธีแทบจะเหมือนกับการแยกส่วนของวิถียาวสุดทุกประการ แต่แตกต่างตรงที่แถวลำดับ Ladder นอกจากจะเก็บข้อมูลของลูกหลานของรากต้นไม้ย่อยแล้ว ยังเก็บข้อมูลบรรพบุรุษเป็นจำนวนข้อมูลเท่ากับจำนวนลูกหลานที่เก็บไว้ด้วย กล่าวคือหาก LP มีขนาด size(LP) แล้ว จะได้ว่า size(Ladder) เท่ากับ 2 × size(LP) ยกเว้นในกรณีที่บรรพบุรุษขึ้นไปถึงรากของต้นไม้ ซึ่งในกรณีนั้นจะมีสมาชิกน้อยกว่า 2 × size(LP)

จากการเก็บข้อมูลแบบนี้ สังเกตว่าหากเริ่มต้นที่จุดยอด v ซึ่ง Ladder ของ v มีขนาดดั้งเดิม h เมื่อผ่านการเพิ่มจุดยอดบรรพบุรุษแล้วก็จะมีขนาด 2h แทน กรณีเลวร้ายมากสุดในการกระโดดก็คือขณะนี้กำลังอยู่ที่รากของต้นไม้ย่อย ซึ่งจะทำให้กระโดดได้เพียง h ขั้น ในรอบที่สอง แน่นอนว่า Ladder ของจุดยอดที่กำลังอยู่ต้องมีขนาดดั้งเดิมมากกว่าหรือเท่ากับ 2h แน่นอน เมื่อเพิ่มจุดยอดบรรพบุรุษเข้าไปแล้วก็จะทำให้มีขนาดเป็น 4h เช่นเดียวกันกับรอบที่แล้ว กรณีเลวร้ายมากที่สุดก็คือขึ้นไปได้เพียง 2h และในรอบต่อ ๆ ไปอย่างเลวร้ายที่สุดก็จะขึ้นได้ 4h 8h 16h ไปเรื่อย ๆ และจากที่ h มีค่าน้อยสุดคือ 1 จึงทำให้สรุปได้ว่าการกระโดดโดยรวมจะใช้เวลาเพียง O(log n)

ขั้นตอนวิธีบันได

[แก้]

ขั้นตอนวิธีนี้รวม 'ขั้นตอนวิธีตัวชี้กระโดด' และ 'การแยกส่วนของบันได' เข้าด้วยกัน โดยสังเกตว่าจากการกระโดดหนึ่งครั้งของ 'ขั้นตอนวิธีตัวชี้กระโดด' ไปที่จุดยอด z ไป 2p ขั้น จะเหลือการกระโดดอีกไม่ถึง 2p ขั้นเช่นกันเพื่อที่จะไปถึงจุดยอดเป้าหมาย และจากการที่เพิ่งกระโดดขึ้นมา 2p ขั้น Ladder ของ z ก็จะมีขนาดมากกว่าหรือเท่ากับ 2p ทำให้ Ladder ของ z นี้เก็บข้อมูลของจุดยอดปลายทางกับจุดยอด z ไว้ในแถวลำดับเดียวกันอย่างแน่นอน ดังนั้นจึงสามารถตอบคำถามนี้ได้ในเวลา O(1)

อ้างอิง

[แก้]
  1. 1.0 1.1 1.2 Bender, Michael A. (2004). "The Level Ancestor Problem Simplified". Theor. Comput. Sci. 321: 5–12. doi:10.1016/j.tcs.2003.05.002. {{cite journal}}: ไม่รู้จักพารามิเตอร์ |coauthors= ถูกละเว้น แนะนำ (|author=) (help)
  2. Berkman (apr 1994). "Finding level-ancestors in trees". J. Comput. Syst. Sci. 2. 48: 214–230. doi:10.1016/S0022-0000(05)80002-9. {{cite journal}}: ตรวจสอบค่าวันที่ใน: |date= (help); ไม่รู้จักพารามิเตอร์ |coauthors= ถูกละเว้น แนะนำ (|author=) (help)
  3. 3.0 3.1 Prof. Erik Demaine, Advanced Data Structures (Lecture 8 — March 2, 2010)