Coding Interview Prep · บทเรียน

DFS แบบลำดับกลาง ก่อน และหลัง

สร้างการท่องต้นไม้แบบ DFS ทั้งสามลำดับ ทั้งแบบเรียกซ้ำและแบบวนซ้ำด้วยสแตกที่ระบุชัด พร้อมอธิบายประโยชน์ของแต่ละลำดับ

บทเรียน 2 จาก 413 ขั้นตอน

DFS แบบลำดับกลาง ก่อน และหลัง เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding 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) บทถัดไปเราจะสำรวจการคำนวณเส้นผ่านศูนย์กลาง ความสูง และความสมดุลของต้นไม้ทวิภาค

เริ่มต้นได้ฟรี

เรียนรู้ Coding Interview Prep ด้วย AI tutor — ฟรี

เขียนและเรียกใช้โค้ดจริงในเบราว์เซอร์ของคุณ รับความช่วยเหลือทันทีจาก AI tutor 24/7 และเรียนรู้ต่อจากที่คุณหยุดบนเว็บหรือในแอป

คอร์ส
90
บทเรียน
360

คำถามที่พบบ่อย

บทเรียน “DFS แบบลำดับกลาง ก่อน และหลัง” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “DFS แบบลำดับกลาง ก่อน และหลัง” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Coding Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “DFS แบบลำดับกลาง ก่อน และหลัง”

สร้างการท่องต้นไม้แบบ DFS ทั้งสามลำดับ ทั้งแบบเรียกซ้ำและแบบวนซ้ำด้วยสแตกที่ระบุชัด พร้อมอธิบายประโยชน์ของแต่ละลำดับ คุณปฏิบัติ Coding Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Coding Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Coding Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “DFS แบบลำดับกลาง ก่อน และหลัง” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Coding Interview Prep นี้ได้ไหม

ได้ บทเรียน Coding Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. คลาส TreeNode และ BFS แบบเรียงตามระดับ
  2. DFS แบบลำดับกลาง ก่อน และหลัง
  3. เส้นผ่านศูนย์กลาง ความสูง และต้นไม้สมดุล
  4. ผลรวมเส้นทางและบรรพบุรุษร่วมที่ใกล้ที่สุด
← กลับไปที่ Coding Interview Prep