ในทฤษฎีกราฟ **ปัญหาระดับบรรพบุรุษ** (อังกฤษ: 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(*n**2*)

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

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

ในการหาคำตอบ ให้เริ่มต้นจากจุดยอด *v* และกระโดดโดยใช้ตัวชี้จาก *dp* ไปให้สูงที่สุดแต่ไม่ไปไกลเกินจุดยอดเป้าหมาย และกระโดดต่อจากจุดยอดนั้นไปเรื่อย ๆ จนกว่าจะเจอจุดยอดเป้าหมาย เนื่องจากการหาตัวชี้ที่จะใช้นั้นสามารถทำได้ใน O(*1*) จากสูตร {\displaystyle \lfloor \log \_{2}((\operatorname {depth} (v)-d)\rfloor } และการกระโดดแต่ละครั้งจะเหลือระยะห่างน้อยลงไป 2 เท่า จึงทำให้ใช้เวลา O(log *n*) ในการกระโดด

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

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

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

รูป 2. กรณีเลวร้ายสุดของการแยกส่วนของวิถียาวสุด ซึ่งจะทำให้การแก้ปัญหาระดับบรรพบุรุษใช้เวลา O(√*n*)
การแยกส่วนของวิถียาวสุด (Longest path decomposition) เป็นการหาวิถียาวสุดจากรากของต้นไม้ไปถึงใบทั้งหลาย หลังจากหาได้แล้วก็จะนำจุดยอดในวิถียาวสุดไปเรียงเก็บไว้ในแถวลำดับ 'LP' จากรูป 1. ก็จะได้ว่าวิถียาวสุดคือวิถีจาก *r* → *v* ดังนั้น 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) ใช้แถวลำดับ 'Ladder' มีลักษณะขั้นตอนวิธีแทบจะเหมือนกับการแยกส่วนของวิถียาวสุดทุกประการ แต่แตกต่างตรงที่แถวลำดับ Ladder นอกจากจะเก็บข้อมูลของลูกหลานของรากต้นไม้ย่อยแล้ว ยังเก็บข้อมูลบรรพบุรุษเป็นจำนวนข้อมูลเท่ากับจำนวนลูกหลานที่เก็บไว้ด้วย กล่าวคือหาก LP มีขนาด size(LP) แล้ว จะได้ว่า size(Ladder) เท่ากับ 2 × size(LP) ยกเว้นในกรณีที่บรรพบุรุษขึ้นไปถึงรากของต้นไม้ ซึ่งในกรณีนั้นจะมีสมาชิกน้อยกว่า 2 × size(LP)

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

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

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

อ้างอิง
-------

1. 1 2 3  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 (1994). "Finding level-ancestors in trees". *J. Comput. Syst. Sci*. 2. **48**: 214–230. doi:10.1016/S0022-0000(05)80002-9. `{{cite journal}}`: ไม่รู้จักพารามิเตอร์ `|coauthors=` ถูกละเว้น แนะนำ (`|author=`) (help); ไม่รู้จักพารามิเตอร์ `|month=` ถูกละเว้น (help)
3. 1 2  Prof. Erik Demaine, Advanced Data Structures (Lecture 8 — March 2, 2010)