DFS แบบลำดับกลาง ก่อน และหลัง
สร้างการท่องต้นไม้แบบ DFS ทั้งสามลำดับ ทั้งแบบเรียกซ้ำและแบบวนซ้ำด้วยสแตกที่ระบุชัด พร้อมอธิบายประโยชน์ของแต่ละลำดับ
DFS แบบลำดับกลาง ก่อน และหลัง เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
การท่องแบบ DFS สามลำดับ
DFS บนต้นไม้ทวิภาคจะเยี่ยมชมโหนดตามหนึ่งในสามลำดับ โดยพิจารณาจาก จังหวะที่ประมวลผลราก เมื่อเทียบกับโหนดลูก การท่องแบบพรีออร์เดอร์: ราก → ซ้าย → ขวา การท่องแบบอินออร์เดอร์: ซ้าย → ราก → ขวา การท่องแบบโพสต์ออร์เดอร์: ซ้าย → ขวา → ราก ชื่อของแต่ละแบบบอกว่า รากอยู่ที่ใด ในลำดับ การทำความเข้าใจทั้งสามแบบเป็นสิ่งสำคัญ เพราะโจทย์แต่ละประเภทต้องใช้ลำดับที่แตกต่างกัน
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
# Build: 1 -> left=2(left=4,right=5), right=3
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
# pre: 1 2 4 5 3
# in: 4 2 5 1 3
# post: 4 5 2 3 1
print('Tree built successfully')การท่องแบบพรีออร์เดอร์ด้วยการเรียกซ้ำ
ในการ ท่องแบบพรีออร์เดอร์ จะประมวลผลโหนดปัจจุบัน ก่อน ต้นไม้ย่อยของโหนดนั้น ลำดับนี้สอดคล้องกับการอ่านต้นไม้จากบนลงล่างตามธรรมชาติ และใช้สำหรับการคัดลอกต้นไม้ การทำให้เป็นอนุกรม และการประเมินนิพจน์คำนำหน้า การเขียนแบบเรียกซ้ำมีขนาดสั้นมาก แต่จะสร้างสแตกการเรียกที่มีความลึก O(h) โดย h คือความสูงของต้นไม้
def preorder(root):
if not root:
return []
return [root.val] + preorder(root.left) + preorder(root.right)
# More memory-efficient with an accumulator:
def preorder_v2(root, result=None):
if result is None:
result = []
if not root:
return result
result.append(root.val) # PROCESS ROOT FIRST
preorder_v2(root.left, result)
preorder_v2(root.right, result)
return result
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(preorder_v2(root)) # [1, 2, 4, 5, 3]การท่องแบบอินออร์เดอร์ด้วยการเรียกซ้ำ
การท่องแบบอินออร์เดอร์จะเยี่ยมชมต้นไม้ย่อยด้านซ้าย จากนั้นราก แล้วจึงต้นไม้ย่อยด้านขวา สำหรับ ต้นไม้ค้นหาแบบทวิภาค การท่องแบบอินออร์เดอร์จะสร้าง ลำดับที่เรียงแล้ว เสมอ คุณสมบัตินี้ถูกใช้ในโจทย์ต่าง ๆ เช่น การตรวจสอบ BST องค์ประกอบที่มีค่าน้อยที่สุดลำดับที่ k และการแปลง BST เป็นอาร์เรย์ที่เรียงแล้ว นี่คือการท่องแบบเดียวที่สำคัญที่สุดที่ควรรู้สำหรับโจทย์ BST
def inorder(root, result=None):
if result is None:
result = []
if not root:
return result
inorder(root.left, result) # left subtree first
result.append(root.val) # PROCESS ROOT MIDDLE
inorder(root.right, result) # right subtree last
return result
# For a BST, inorder gives sorted output:
from collections import deque
def make_bst():
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
return root
bst = make_bst()
print(inorder(bst)) # [1, 2, 3, 4, 6] - sorted!การท่องแบบโพสต์ออร์เดอร์ด้วยการเรียกซ้ำ
การท่องแบบโพสต์ออร์เดอร์จะประมวลผลโหนดลูกทั้งสอง ก่อนโหนดปัจจุบัน ลำดับจากล่างขึ้นบนนี้เหมาะตามธรรมชาติเมื่อการคำนวณของโหนดแม่ขึ้นอยู่กับผลลัพธ์ของโหนดลูก เช่น การคำนวณขนาดของต้นไม้ย่อย การลบต้นไม้ หรือการประเมินต้นไม้นิพจน์ โจทย์เกี่ยวกับต้นไม้ส่วนใหญ่ที่ส่งข้อมูล ขึ้นไป ใช้ตรรกะแบบโพสต์ออร์เดอร์โดยนัย
def postorder(root, result=None):
if result is None:
result = []
if not root:
return result
postorder(root.left, result) # left subtree
postorder(root.right, result) # right subtree
result.append(root.val) # PROCESS ROOT LAST
return result
# Use case: delete a tree (children before parent)
def delete_tree(root):
if not root:
return
delete_tree(root.left)
delete_tree(root.right)
print(f'Deleting node {root.val}') # safe: children gone
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
print(postorder(root)) # [4, 2, 3, 1]การท่องแบบพรีออร์เดอร์วนซ้ำด้วยสแตก
เพื่อหลีกเลี่ยงข้อจำกัดด้านความลึกของการเรียกซ้ำ ให้เขียน DFS แบบวนซ้ำโดยใช้ สแตกที่สร้างขึ้นอย่างชัดเจน สำหรับพรีออร์เดอร์ ให้ใส่รากลงในสแตก จากนั้นในแต่ละรอบให้ใช้ pop เพื่อนำโหนดออก บันทึกโหนดนั้น แล้วใส่โหนดลูกด้านขวาก่อนโหนดลูกด้านซ้าย (ใส่ด้านขวาก่อนเพื่อให้ประมวลผลด้านซ้ายก่อน) วิธีนี้เลียนแบบพฤติกรรมแบบ LIFO ของสแตกการเรียก และเป็นแนวทางหลักสำหรับต้นไม้ลึกที่ขีดจำกัดการเรียกซ้ำเริ่มต้น 1000 ของไพธอนอาจทำงานไม่สำเร็จ
def preorder_iterative(root):
if not root:
return []
result = []
stack = [root]
while stack:
node = stack.pop()
result.append(node.val) # process now
if node.right: # push right FIRST
stack.append(node.right)
if node.left: # push left second (popped first)
stack.append(node.left)
return result
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(preorder_iterative(root)) # [1, 2, 4, 5, 3]การท่องแบบอินออร์เดอร์วนซ้ำด้วยสแตก
การท่องแบบอินออร์เดอร์วนซ้ำทำได้ซับซ้อนขึ้นเล็กน้อย ให้ใช้สแตกและตัวชี้ curr: เดินไปทางซ้ายให้ไกลที่สุด โดยใส่ทุกโหนดลงในสแตก เมื่อไม่สามารถไปทางซ้ายต่อได้ ให้ใช้ pop นำโหนดออก บันทึกโหนดนั้น แล้วเลื่อนไปทางขวา รูปแบบนี้ — ใส่โหนดทางซ้ายจนเป็นค่าว่าง ใช้ pop แล้วประมวลผล จากนั้นไปทางขวา — เป็นเทคนิควนซ้ำพื้นฐานที่มักปรากฏในโจทย์ตัววนซ้ำของ BST
def inorder_iterative(root):
result = []
stack = []
curr = root
while curr or stack:
# Go as far left as possible
while curr:
stack.append(curr)
curr = curr.left
# Pop and process
curr = stack.pop()
result.append(curr.val)
# Move to right subtree
curr = curr.right
return result
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(inorder_iterative(root)) # [4, 2, 5, 1, 3]การท่องแบบโพสต์ออร์เดอร์วนซ้ำด้วยสองสแตก
การท่องแบบโพสต์ออร์เดอร์วนซ้ำมีเคล็ดลับที่เรียบง่าย: ใช้การท่องแบบพรีออร์เดอร์ที่ดัดแปลง (ราก → ขวา → ซ้าย) แล้ว เก็บผลลัพธ์ในลำดับย้อนกลับ ใส่รากลงในสแตก ใช้ pop แล้วเพิ่มโหนดไว้ด้านหน้าของผลลัพธ์ จากนั้นใส่โหนดซ้ายแล้วขวา การกลับลำดับจะแปลงราก-ขวา-ซ้ายเป็นซ้าย-ขวา-ราก ซึ่งตรงกับโพสต์ออร์เดอร์พอดี อีกทางเลือกหนึ่งคือใช้ตัวชี้ prev เพื่อติดตามโหนดที่เยี่ยมชมล่าสุดด้วยสแตกเดียว
from collections import deque
def postorder_iterative(root):
if not root:
return []
result = deque()
stack = [root]
while stack:
node = stack.pop()
result.appendleft(node.val) # prepend = reverse pre-order
if node.left:
stack.append(node.left) # push left first
if node.right:
stack.append(node.right) # push right second
return list(result)
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
root.left.left = TreeNode(4)
root.left.right = TreeNode(5)
print(postorder_iterative(root)) # [4, 5, 2, 3, 1]ควรเลือกลำดับการท่องแบบใด
การเลือกลำดับการท่องที่เหมาะสมเป็นสัญญาณสำคัญในการสัมภาษณ์ ใช้ พรีออร์เดอร์ เมื่อต้องประมวลผลโหนดแม่ก่อนโหนดลูก (ทำให้ต้นไม้เป็นอนุกรม คัดลอกโครงสร้าง) ใช้ อินออร์เดอร์ กับ BST เพื่อใช้ประโยชน์จากลำดับที่เรียงแล้ว ใช้ โพสต์ออร์เดอร์ เมื่อคำนวณค่าที่ขึ้นอยู่กับโหนดลูกทั้งสอง (เช่น height เส้นผ่านศูนย์กลาง และผลรวมของต้นไม้ย่อย) ควรเลือก BFS สำหรับโจทย์เส้นทางสั้นที่สุดและการจัดกลุ่มตามระดับ
# Pattern summary:
# Pre-order -> top-down: parent info flows DOWN to children
# In-order -> BST sorted property, kth element, validate BST
# Post-order -> bottom-up: children info flows UP to parent
# BFS -> shortest path, level grouping, level averages
# Example: compute subtree sum (post-order because
# we need left + right sum before computing total)
def subtree_sum(root):
if not root:
return 0
left = subtree_sum(root.left)
right = subtree_sum(root.right)
return root.val + left + right # uses children FIRST
root = TreeNode(1)
root.left = TreeNode(2)
root.right = TreeNode(3)
print(subtree_sum(root)) # 6การท่องแบบมอร์ริส: อินออร์เดอร์ด้วยพื้นที่ O(1)
การท่องแบบมอร์ริสทำการท่องแบบอินออร์เดอร์โดยใช้พื้นที่ O(1) ด้วยการปรับเปลี่ยนต้นไม้ชั่วคราว สำหรับแต่ละโหนดที่มีต้นไม้ย่อยด้านซ้าย ให้ค้นหา โหนดก่อนหน้าในลำดับอินออร์เดอร์ (โหนดขวาสุดของต้นไม้ย่อยด้านซ้าย) แล้วเชื่อมตัวชี้ด้านขวาของโหนดนั้นกลับมายังโหนดปัจจุบัน หลังจากเยี่ยมชมแล้ว ให้คืนค่าการเชื่อมโยงเดิม เทคนิคขั้นสูงนี้มักถูกถามในการสัมภาษณ์ระดับสูงสุด เมื่อผู้สัมภาษณ์ถามว่า «สามารถทำโดยใช้พื้นที่เพิ่มเติม O(1) ได้หรือไม่»
def morris_inorder(root):
result = []
curr = root
while curr:
if not curr.left:
result.append(curr.val)
curr = curr.right
else:
# Find in-order predecessor
pred = curr.left
while pred.right and pred.right != curr:
pred = pred.right
if not pred.right:
# Make thread and move left
pred.right = curr
curr = curr.left
else:
# Remove thread, visit, move right
pred.right = None
result.append(curr.val)
curr = curr.right
return result
root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
print(morris_inorder(root)) # [1, 2, 3, 4, 6]การสร้างต้นไม้ใหม่จากลำดับการท่อง
เมื่อมีอาร์เรย์ พรีออร์เดอร์และอินออร์เดอร์ คุณสามารถสร้างต้นไม้เดิมกลับคืนมาได้ องค์ประกอบแรกของพรีออร์เดอร์จะเป็นรากเสมอ ให้ค้นหารากนั้นในอาร์เรย์อินออร์เดอร์ ทุกสิ่งที่อยู่ทางซ้ายจะเป็นของต้นไม้ย่อยด้านซ้าย และทุกสิ่งที่อยู่ทางขวาจะเป็นของต้นไม้ย่อยด้านขวา จากนั้นใช้วิธีเดียวกันกับอาร์เรย์ย่อยโดยการเรียกซ้ำ ความซับซ้อนด้านเวลาคือ O(n) เมื่อใช้การค้นหาดัชนีในตารางแฮช
def build_from_preorder_inorder(preorder, inorder):
if not preorder:
return None
root_val = preorder[0]
root = TreeNode(root_val)
mid = inorder.index(root_val)
# left subtree: inorder[0:mid], preorder[1:mid+1]
root.left = build_from_preorder_inorder(
preorder[1:mid+1], inorder[:mid])
# right subtree: inorder[mid+1:], preorder[mid+1:]
root.right = build_from_preorder_inorder(
preorder[mid+1:], inorder[mid+1:])
return root
pre = [3, 9, 20, 15, 7]
ino = [9, 3, 15, 20, 7]
root = build_from_preorder_inorder(pre, ino)
print(root.val, root.left.val, root.right.val) # 3 9 20สรุปเวลาและพื้นที่ของการท่องต้นไม้
การท่องแบบ DFS ทั้งสามแบบมี ความซับซ้อนด้านเวลา O(n) เนื่องจากเยี่ยมชมแต่ละโหนดเพียงครั้งเดียว ความซับซ้อนด้านพื้นที่คือ O(h) โดย h คือความสูงของต้นไม้ — O(log n) สำหรับต้นไม้สมดุล และ O(n) สำหรับต้นไม้ที่เอน (เนื่องจากสแตกการเรียกหรือสแตกที่สร้างขึ้นเอง) การเขียนแบบวนซ้ำช่วยหลีกเลี่ยงขีดจำกัดการเรียกซ้ำของไพธอน แต่ใช้พื้นที่เชิงลำดับการเติบโตเท่าเดิม การท่องแบบมอร์ริสมีคุณสมบัติเฉพาะที่ใช้พื้นที่ O(1) โดยนำตัวชี้ด้านขวาของต้นไม้กลับมาใช้ใหม่
# Complexity table:
# Traversal | Time | Space (recursion) | Space (iterative)
# -----------|------|-------------------|------------------
# Pre-order | O(n) | O(h) | O(h)
# In-order | O(n) | O(h) | O(h)
# Post-order | O(n) | O(h) | O(h)
# Morris | O(n) | O(1) | O(1)
# BFS | O(n) | O(w) | O(w)
# h = height, w = max width
# Balanced: h = log n, w = n/2
# Skewed: h = n, w = 1
print('O(n) time for all traversals')ตรวจสอบอย่างรวดเร็ว
ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์เขียนโปรแกรมจากบทเรียนนี้
บทสรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้: ลำดับการท่องแบบ DFS สามแบบ (ก่อน ราก และหลัง) รวมถึงเวลาที่ควรเลือกใช้แต่ละแบบ การเขียนแบบเรียกซ้ำและวนซ้ำโดยใช้สแตกที่สร้างขึ้นอย่างชัดเจน และเทคนิค มอร์ริสที่ใช้พื้นที่ O(1) บทถัดไปเราจะสำรวจการคำนวณเส้นผ่านศูนย์กลาง ความสูง และความสมดุลของต้นไม้ทวิภาค
เรียนรู้ Python ด้วย AI tutor — ฟรี
เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป
- คอร์ส
- 30
- บทเรียน
- 120
คำถามที่พบบ่อย
บทเรียน “DFS แบบลำดับกลาง ก่อน และหลัง” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “DFS แบบลำดับกลาง ก่อน และหลัง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “DFS แบบลำดับกลาง ก่อน และหลัง”
สร้างการท่องต้นไม้แบบ DFS ทั้งสามลำดับ ทั้งแบบเรียกซ้ำและแบบวนซ้ำด้วยสแตกที่ระบุชัด พร้อมอธิบายประโยชน์ของแต่ละลำดับ คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “DFS แบบลำดับกลาง ก่อน และหลัง” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม
ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- คลาส TreeNode และ BFS แบบเรียงตามระดับ
- DFS แบบลำดับกลาง ก่อน และหลัง
- เส้นผ่านศูนย์กลาง ความสูง และต้นไม้สมดุล
- ผลรวมเส้นทางและบรรพบุรุษร่วมที่ใกล้ที่สุด