0Pricing
Coding Interview Prep · บทเรียน

การแทรกและค้นหาใน BST

สร้างการแทรกและค้นหาทั้งแบบเรียกซ้ำและแบบวนซ้ำ ติดตามเส้นทางผ่านต้นไม้สำหรับคีย์ต่าง ๆ และวิเคราะห์ความซับซ้อนกรณีเลวร้ายสุดของต้นไม้ที่ไม่สมดุล

การแทรกและค้นหาใน BST เป็นบทเรียน Coding Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 1 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Coding Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Coding Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

นิยามคุณสมบัติของ BST

ต้นไม้ค้นหาแบบทวิภาคมีเงื่อนไขคงที่หนึ่งข้อ: สำหรับทุกโหนด ค่าทั้งหมดในต้นไม้ย่อยด้านซ้ายต้องน้อยกว่าค่าของโหนดนั้นอย่างเคร่งครัด และค่าทั้งหมดในต้นไม้ย่อยด้านขวาต้องมากกว่าอย่างเคร่งครัด คุณสมบัติด้านลำดับนี้ต้องคงอยู่ตลอดทั้งต้นไม้ย่อย ไม่ใช่เฉพาะโหนดลูกโดยตรง จึงทำให้การค้นหา การแทรก และการลบในต้นไม้ที่สมดุลใช้เวลา O(log n) และทำให้ BST แตกต่างจากต้นไม้ทวิภาคทั่วไป

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

# Valid BST:
#       4
#      / \
#     2   6
#    / \ / \
#   1  3 5  7
# For node 4: left subtree {1,2,3} < 4 < right subtree {5,6,7}
# This holds recursively for EVERY node in the tree.
print('BST property: left < node < right at every level')

การค้นหาใน BST แบบเรียกซ้ำ

การค้นหาใน BST ทำงานคล้ายการค้นหาแบบทวิภาค โดยเปรียบเทียบเป้าหมายกับค่าของโหนดปัจจุบัน แล้วเรียกซ้ำไปยังต้นไม้ย่อยที่เหมาะสม หากเป้าหมายเท่ากับค่าปัจจุบัน ให้ส่งคืนโหนดนั้น หากเป้าหมายมีค่าน้อยกว่า ให้ไปทางซ้าย หากมีค่ามากกว่า ให้ไปทางขวา หากไปถึงโหนดว่าง ให้ส่งคืนค่าว่าง ความซับซ้อนด้านเวลาคือ O(h) — O(log n) สำหรับต้นไม้ที่สมดุล และ O(n) สำหรับต้นไม้ที่เอนเอียง

def search_bst(root, val):
    if not root:
        return None  # not found
    if root.val == val:
        return root  # found
    if val < root.val:
        return search_bst(root.left, val)
    else:
        return search_bst(root.right, val)

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)

result = search_bst(root, 2)
print(result.val if result else 'Not found')  # 2
result = search_bst(root, 5)
print(result.val if result else 'Not found')  # Not found

การค้นหาใน BST แบบวนซ้ำ

การค้นหาแบบวนซ้ำหลีกเลี่ยงค่าใช้จ่ายของสแตกการเรียกฟังก์ชัน และเป็นรูปแบบที่เหมาะกับโค้ดที่ใช้งานจริงมากกว่า ใช้ตัวชี้ curr เดินลงไปตามต้นไม้ โดยเลือกทางซ้ายหรือขวาตามผลการเปรียบเทียบ วิธีนี้เป็นวงวนอย่างง่ายที่มีสามกรณี: ค่าว่าง (ไม่พบ) ค่าตรงกัน (พบ) หรือปรับทิศทาง การค้นหาแบบวนซ้ำยังใช้เวลา O(h) แต่ใช้พื้นที่ O(1) เมื่อเทียบกับ O(h) ของแบบเรียกซ้ำ

def search_bst_iterative(root, val):
    curr = root
    while curr:
        if val == curr.val:
            return curr
        elif val < curr.val:
            curr = curr.left
        else:
            curr = curr.right
    return None  # not found

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)

node = search_bst_iterative(root, 3)
print(node.val if node else 'Not found')  # 3
print(search_bst_iterative(root, 9))     # None

การแทรกใน BST แบบเรียกซ้ำ

การแทรกใน BST จะหาตำแหน่งที่ถูกต้องโดยตัดสินใจไปทางซ้ายหรือขวาแบบเดียวกับการค้นหา จากนั้นเชื่อมโหนดใหม่เข้ากับตำแหน่ง null แรกที่พบ วิธีแบบเรียกซ้ำจะส่งคืนรากของต้นไม้ย่อยแต่ละต้น (ซึ่งอาจเป็นรากใหม่): หากโหนดปัจจุบันเป็นค่าว่าง ให้ส่งคืน TreeNode ใหม่ มิฉะนั้นให้ปรับปรุง root.left หรือ root.right ด้วยผลลัพธ์จากการเรียกซ้ำ รูปแบบนี้เข้าใจง่ายและพบได้บ่อยในคำตอบการสัมภาษณ์

def insert_bst(root, val):
    if not root:
        return TreeNode(val)  # create new node here
    if val < root.val:
        root.left = insert_bst(root.left, val)
    elif val > root.val:
        root.right = insert_bst(root.right, val)
    # val == root.val: duplicate, do nothing (or handle as needed)
    return root

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root = insert_bst(root, 1)
root = insert_bst(root, 5)
# Tree is now: 4, left=2(left=1), right=7(left=5)
print(root.right.left.val)  # 5

การแทรกใน BST แบบวนซ้ำ

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

def insert_bst_iterative(root, val):
    new_node = TreeNode(val)
    if not root:
        return new_node
    curr = root
    while True:
        if val < curr.val:
            if curr.left is None:
                curr.left = new_node
                break
            curr = curr.left
        else:  # val > curr.val
            if curr.right is None:
                curr.right = new_node
                break
            curr = curr.right
    return root

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root = insert_bst_iterative(root, 3)
print(root.left.right.val)  # 3

กรณีเลวร้ายที่สุดของ BST: ต้นไม้เอนเอียง

หากแทรกลำดับที่เรียงแล้วลงใน BST จะได้ต้นไม้เอนเอียงที่เสื่อมสภาพเป็นเหมือนรายการเชื่อมโยง การค้นหา การแทรก และการลบทั้งหมดจะใช้เวลา O(n) นี่คือเหตุผลที่มี BST ที่สมดุล เช่น ต้นไม้ AVL และต้นไม้แดง-ดำ ในการสัมภาษณ์ ควรกล่าวถึงกรณีเลวร้ายที่สุดนี้เสมอเมื่อถูกถามเกี่ยวกับความซับซ้อนของ BST — การตอบว่า 'โดยเฉลี่ย O(log n) และกรณีเลวร้ายที่สุด O(n) สำหรับต้นไม้ที่ไม่สมดุล' แสดงให้เห็นถึงความเข้าใจอย่างลึกซึ้ง

# Inserting 1, 2, 3, 4, 5 into a BST:
# 1
#  \
#   2
#    \
#     3
#      \
#       4
#        \
#         5
# This is a right-skewed tree: search is O(n) not O(log n)

root = None
for val in [1, 2, 3, 4, 5]:
    root = insert_bst(root, val)

# Verify the skew
node = root
depth = 0
while node:
    depth += 1
    node = node.right
print(f'Height: {depth}')  # 5 = O(n), not O(log n)

การค้นหาค่าต่ำสุดและค่าสูงสุด

ใน BST ค่าต่ำสุดจะอยู่ที่โหนดซ้ายสุดเสมอ (ไปทางซ้ายต่อไปจนพบค่าว่าง) และค่าสูงสุดจะอยู่ที่โหนดขวาสุด การดำเนินการเหล่านี้ใช้เวลา O(h) และมักใช้เป็นฟังก์ชันย่อยในการลบจาก BST (เพื่อค้นหาโหนดถัดไปตามลำดับอินออร์เดอร์) และการค้นหาช่วง การจำฟังก์ชันผู้ช่วยเหล่านี้ให้ขึ้นใจจะช่วยประหยัดเวลาในการสัมภาษณ์

def find_min(root):
    while root.left:
        root = root.left
    return root

def find_max(root):
    while root.right:
        root = root.right
    return root

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(7)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
root.right.right = TreeNode(9)

print(find_min(root).val)  # 1
print(find_max(root).val)  # 9

โหนดถัดไปและโหนดก่อนหน้าตามลำดับอินออร์เดอร์

โหนดถัดไปตามลำดับอินออร์เดอร์ของโหนดหนึ่งคือโหนดที่มีค่าน้อยที่สุดซึ่งมากกว่าค่าของโหนดนั้น หากโหนดนั้นมีต้นไม้ย่อยด้านขวา โหนดถัดไปคือ find_min(node.right) หากไม่มีต้นไม้ย่อยด้านขวา โหนดถัดไปคือบรรพบุรุษที่อยู่ต่ำที่สุดซึ่งโหนดที่กำหนดอยู่ในต้นไม้ย่อยด้านซ้าย การทำความเข้าใจเรื่องนี้มีความสำคัญอย่างยิ่งต่อปัญหาการลบใน BST และตัววนซ้ำของ BST

def inorder_successor(root, p):
    successor = None
    while root:
        if p.val < root.val:
            successor = root  # possible successor
            root = root.left
        else:
            root = root.right
    return successor

def inorder_predecessor(root, p):
    predecessor = None
    while root:
        if p.val > root.val:
            predecessor = root  # possible predecessor
            root = root.right
        else:
            root = root.left
    return predecessor

root = TreeNode(4)
root.left = TreeNode(2)
root.right = TreeNode(6)
root.left.left = TreeNode(1)
root.left.right = TreeNode(3)
p = root.left  # node with val=2
print(inorder_successor(root, p).val)   # 3
print(inorder_predecessor(root, p).val) # 1

การวิเคราะห์ความซับซ้อนของการค้นหาใน BST

ประสิทธิภาพของ BST ขึ้นอยู่กับความสูงของต้นไม้โดยสิ้นเชิง สำหรับ BST ที่สมดุลซึ่งมี n โหนด ความสูงคือ O(log n) ทำให้การค้นหา การแทรก และการลบใช้เวลา O(log n) สำหรับ BST ที่เอนเอียง ความสูงคือ O(n) ทำให้การดำเนินการทั้งหมดใช้เวลา O(n) Python ไม่มี BST ที่สมดุลมาให้ในตัว (ต่างจาก TreeMap ของ Java) ดังนั้นคุณอาจเขียน AVL หรือต้นไม้แดง-ดำด้วยตัวเอง ใช้ sortedcontainers.SortedList หรือใช้ฮีปสำหรับกรณีใช้งานคิวลำดับความสำคัญ

# Python's BST alternatives:
# 1. heapq - min/max heap, O(log n) push/pop
# 2. sortedcontainers.SortedList (third-party, often allowed)
# 3. Manual AVL or Red-Black (rarely required in interviews)

# When interviews say 'use a BST':
# - LeetCode: implement TreeNode-based solution
# - Real interview: mention sortedcontainers or Java TreeMap equivalent
# - O(log n) operations matter when you need ordered access

# For pure insert/lookup without ordering: use dict (O(1) average)
print('Use heap for priority, dict for lookup, BST for ordered range')

การแทรกลงใน BST: กรณีพิเศษ

ควรตรวจสอบเสมอว่าการแทรกของคุณจัดการกับกรณีต่อไปนี้ได้: ต้นไม้ที่ว่างเปล่า (ส่งคืนโหนดใหม่เป็นราก) ค่าซ้ำ (กำหนดว่าจะละเว้น แทรกทางซ้าย หรือแทรกทางขวา และใช้แนวทางนั้นอย่างสม่ำเสมอ) และค่าที่ใหญ่มากหรือเล็กมาก ในการสัมภาษณ์ ให้ระบุสมมติฐานเกี่ยวกับค่าซ้ำก่อนเขียนโค้ด ข้อตกลงที่พบบ่อยที่สุดในปัญหาของ LeetCode คือค่าทั้งหมดแตกต่างกัน เว้นแต่จะระบุไว้เป็นอย่างอื่น

def insert_bst_no_duplicates(root, val):
    if not root:
        return TreeNode(val)
    if val < root.val:
        root.left = insert_bst_no_duplicates(root.left, val)
    elif val > root.val:
        root.right = insert_bst_no_duplicates(root.right, val)
    # else: val == root.val -> duplicate, skip
    return root

# Test all edge cases:
root = None
root = insert_bst_no_duplicates(root, 5)  # empty tree
root = insert_bst_no_duplicates(root, 5)  # duplicate
root = insert_bst_no_duplicates(root, 3)
root = insert_bst_no_duplicates(root, 7)
print(root.val, root.left.val, root.right.val)  # 5 3 7

BST จากอาร์เรย์ที่เรียงแล้ว

การสร้าง BST ที่สมดุลตามความสูงจากอาร์เรย์ที่เรียงแล้ว (LeetCode #108) ใช้แนวคิดแบ่งแล้วพิชิต: สมาชิกตรงกลางจะกลายเป็นราก ครึ่งซ้ายจะกลายเป็นต้นไม้ย่อยด้านซ้าย และครึ่งขวาจะกลายเป็นต้นไม้ย่อยด้านขวา วิธีนี้รับประกันต้นไม้ที่สมดุลและมีความสูง O(log n) ความซับซ้อนด้านเวลาคือ O(n) เนื่องจากประมวลผลสมาชิกแต่ละตัวเพียงครั้งเดียว

def sorted_array_to_bst(nums):
    if not nums:
        return None
    mid = len(nums) // 2
    root = TreeNode(nums[mid])
    root.left = sorted_array_to_bst(nums[:mid])
    root.right = sorted_array_to_bst(nums[mid+1:])
    return root

nums = [-10, -3, 0, 5, 9]
root = sorted_array_to_bst(nums)
print(root.val)        # 0 (middle element)
print(root.left.val)   # -3
print(root.right.val)  # 9

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

ทบทวนบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้: คุณสมบัติของ BST (ต้นไม้ย่อยด้านซ้ายมีค่าน้อยกว่าอย่างเคร่งครัด ต้นไม้ย่อยด้านขวามีค่ามากกว่าอย่างเคร่งครัด) การค้นหาและการแทรกทั้งแบบเรียกซ้ำและแบบวนซ้ำในเวลา O(h) และต้นไม้เอนเอียงในกรณีเลวร้ายที่สุดที่ความสูงเท่ากับ n บทถัดไปเราจะจัดการกับการลบใน BST และสามกรณีของการลบ

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

บทเรียน “การแทรกและค้นหาใน BST” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “การแทรกและค้นหาใน BST”

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

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

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

บทเรียน “การแทรกและค้นหาใน BST” ใช้เวลานานแค่ไหน

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

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

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

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

  1. การแทรกและค้นหาใน BST
  2. การลบใน BST: สามกรณี
  3. ตรวจสอบ BST และคุณสมบัติลำดับกลาง
  4. ค่าที่น้อยที่สุดลำดับ k ผลรวมช่วง และ BST เป็นอาร์เรย์เรียงลำดับ
← กลับไปที่ Coding Interview Prep