การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ
ติดตามการเรียกซ้ำเป็นต้นไม้ ใช้ทฤษฎีบทมาสเตอร์ และหาความซับซ้อนด้านเวลาของการเรียงแบบผสาน แฟกทอเรียล และฟีโบนัชชีรูปแบบต่าง ๆ
การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
การเรียกซ้ำและสแตกการเรียกฟังก์ชัน
เมื่อฟังก์ชันเรียกตัวเอง การเรียกแต่ละครั้งจะเพิ่ม เฟรมของสแตก ซึ่งสะสมต่อกันจนกว่าจะถึงกรณีฐาน แล้วจึงคลายกลับออกมา การจินตนาการภาพนี้คือขั้นตอนแรกในการวิเคราะห์การเรียกซ้ำ
def factorial(n):
if n == 0: # base case
return 1
return n * factorial(n - 1) # recursive call
# Call chain: factorial(4)
# 4 * factorial(3)
# 3 * factorial(2)
# 2 * factorial(1)
# 1 * factorial(0) -> 1
# Unwinds: 1, 2, 6, 24
print(factorial(5)) # 120ต้นไม้การเรียกซ้ำของฟีโบนัชชี
ต้นไม้การเรียกซ้ำจะแตกการเรียกแต่ละครั้งออกเป็นการเรียกย่อยของมัน การคำนวณฟีโบนัชชีแบบตรงไปตรงมาจะแตกออกเป็นสองกิ่งทุกครั้ง ทำให้ได้ต้นไม้ที่มีประมาณ 2^n โหนด — จึงมีความซับซ้อนเป็น O(2^n) ดูโค้ดได้เลย
call_count = [0]
def fib_naive(n):
call_count[0] += 1
if n <= 1:
return n
return fib_naive(n-1) + fib_naive(n-2)
for n in [5, 10, 15, 20]:
call_count[0] = 0
result = fib_naive(n)
print(f'fib({n})={result}, calls={call_count[0]}')
# Calls roughly double each time n increases by 1การระบุปัญหาย่อยที่เกิดซ้ำ
ในต้นไม้นั้น การเรียกเดิม ๆ เช่น fib(3) จะเกิดซ้ำในหลายกิ่ง ปัญหาย่อยที่ซ้อนทับกันเหล่านี้เป็นสัญญาณให้ใช้การจดจำผลลัพธ์ ซึ่งลดความซับซ้อนจาก O(2^n) เหลือ O(n)
# Memoised: each unique sub-problem computed once
def fib_memo(n, memo={}):
if n in memo: return memo[n]
if n <= 1: return n
memo[n] = fib_memo(n-1, memo) + fib_memo(n-2, memo)
return memo[n]
call_count2 = [0]
def fib_counted(n, memo={}):
call_count2[0] += 1
if n in memo: return memo[n]
if n <= 1: return n
memo[n] = fib_counted(n-1, memo) + fib_counted(n-2, memo)
return memo[n]
fib_counted(20)
print(f'calls with memo: {call_count2[0]}') # only 21ต้นไม้การเรียกซ้ำของการเรียงลำดับแบบผสาน
ต้นไม้ของการเรียงลำดับแบบผสานมี log n ระดับ และแต่ละระดับทำงานรวมกันเป็น O(n) — สมาชิกทุกตัวถูกเข้าถึงครั้งเดียว จึงนำจำนวนระดับมาคูณกันได้เป็น O(n log n) ดูโค้ดได้เลย
# Merge sort: at each level, n total elements are merged
# Level 0: 1 merge of n elements -> n work
# Level 1: 2 merges of n/2 each -> n work
# Level 2: 4 merges of n/4 each -> n work
# ...log(n) levels...
# Total: n * log(n)
# Verify with operation counter:
def merge_sort_counted(arr):
ops = [0]
def _sort(a):
if len(a) <= 1: return a
m = len(a) // 2
l, r = _sort(a[:m]), _sort(a[m:])
result, i, j = [], 0, 0
while i < len(l) and j < len(r):
ops[0] += 1
if l[i] <= r[j]: result.append(l[i]); i+=1
else: result.append(r[j]); j+=1
return result + l[i:] + r[j:]
return _sort(arr), ops[0]
_, c = merge_sort_counted(list(range(64, 0, -1)))
print(f'Merge ops: {c}') # ~384 ~ 64*log2(64)=384ทฤษฎีบทมาสเตอร์
ทฤษฎีบทมาสเตอร์ใช้แก้สมการเวียนเกิด T(n) = a*T(n/b) + O(n^d) ได้ด้วยสามกรณี สำหรับการเรียงลำดับแบบผสาน (a=2, b=2, d=1) จะได้ O(n log n) จงจำทั้งสามกรณีไว้สำหรับการสอบ
# Merge sort: T(n) = 2*T(n/2) + O(n)
# a=2, b=2, d=1, log_b(a)=log2(2)=1=d => O(n log n)
# Binary search: T(n) = 1*T(n/2) + O(1)
# a=1, b=2, d=0, log2(1)=0=d => O(log n)
# Strassen matrix mult: T(n) = 7*T(n/2) + O(n^2)
# a=7, b=2, d=2, log2(7)~2.81 > 2 => O(n^log2(7)) ~ O(n^2.81)
import math
print('log2(7) =', math.log2(7)) # 2.807...การวาดต้นไม้การเรียกซ้ำทีละขั้น
การวาด ต้นไม้การเรียกซ้ำทำได้ดังนี้: วาง T(n) ไว้ด้านบน แตกการเรียกแต่ละครั้งออกมา รวมงานในแต่ละระดับ แล้วคูณด้วยจำนวนระดับ ฝึกจนทำได้โดยอัตโนมัติ
# Factorial: T(n) = T(n-1) + O(1)
# Tree is a chain: n levels, O(1) each -> O(n)
# Fibonacci: T(n) = T(n-1) + T(n-2) + O(1)
# Binary tree of depth n, ~2^n nodes -> O(2^n)
# Merge sort: T(n) = 2*T(n/2) + O(n)
# Log levels, n work each -> O(n log n)
def count_recursive_calls(n, results=[]):
if n <= 1:
results.append(n)
return n
return count_recursive_calls(n-1, results) + count_recursive_calls(n-2, results)
results = []
count_recursive_calls(8, results)
print(f'fib(8) leaf calls: {len(results)}')การเรียกซ้ำแบบเลขชี้กำลัง: เซตย่อย
การสร้าง เซตย่อยทั้งหมดมีความซับซ้อนเป็น O(2^n) — มีเซตย่อยอยู่ exactement 2^n ชุด จึงไม่มีทางทำให้เร็วกว่านี้ได้ แต่ละสมาชิกจะถูกเลือกให้เข้าหรือไม่เข้าก็ได้ ทำให้เกิดต้นไม้การเลือกแบบทวิภาค ดูโค้ดได้เลย
def subsets(nums):
result = []
def backtrack(start, current):
result.append(list(current)) # O(n) copy
for i in range(start, len(nums)):
current.append(nums[i])
backtrack(i + 1, current)
current.pop()
backtrack(0, [])
return result
nums = [1, 2, 3]
ss = subsets(nums)
print(len(ss)) # 8 = 2^3
print(ss)การเรียกซ้ำแบบหางและการเพิ่มประสิทธิภาพ
การเรียกซ้ำแบบหางคือการที่การเรียกซ้ำเป็นขั้นตอนสุดท้ายอย่างแท้จริง ภาษาบางภาษาจะนำเฟรมเดิมกลับมาใช้ใหม่ แต่ Python ไม่ทำเช่นนั้น — ดังนั้นการเรียกซ้ำที่ลึกมากยังคงทำให้เกิดการล้นของสแตกได้ ให้ใช้ลูปแทน
# Tail-recursive factorial (accumulator pattern)
def fact_tail(n, acc=1):
if n == 0:
return acc
return fact_tail(n - 1, n * acc) # tail call
# Python does NOT TCO, so this overflows for large n
# Instead, convert to iterative:
def fact_iter(n):
acc = 1
while n > 0:
acc *= n
n -= 1
return acc
print(fact_tail(10)) # 3628800
print(fact_iter(10)) # 3628800ความซับซ้อนด้านพื้นที่ของการเรียกซ้ำ
การเรียกซ้ำแต่ละครั้งเก็บเฟรมหนึ่งเฟรมไว้ ดังนั้นการเรียกซ้ำจึงใช้พื้นที่เป็น O(ความลึก) การเรียกซ้ำแบบเชิงเส้นมีความซับซ้อนเป็น O(n) ส่วนการค้นหาแบบ DFS บนต้นไม้สมดุลมีความซับซ้อนเป็น O(log n) หากเรียกลึกเกินไปจะพบ RecursionError
import sys
print(sys.getrecursionlimit()) # default 1000
# Increase limit for deep problems
sys.setrecursionlimit(10000)
# Track max depth manually
def max_depth_tracker(n, depth=0, max_seen=[0]):
max_seen[0] = max(max_seen[0], depth)
if n <= 0:
return
max_depth_tracker(n - 1, depth + 1, max_seen)
return max_seen[0]
print(max_depth_tracker(50)) # 50 => O(n) stack framesต้นไม้การเรียกซ้ำของการเรียงลำดับแบบเร็ว
การเรียงลำดับแบบเร็วมีความซับซ้อนเป็น O(n log n) เมื่อเลือกจุดหมุนได้ดี แต่ถ้าเลือกจุดหมุนได้แย่กับข้อมูลเข้าที่เรียงอยู่แล้ว ความซับซ้อนจะเพิ่มเป็น O(n^2) นี่คือเหตุผลที่การสุ่มจุดหมุนมีความสำคัญ ดูโค้ดได้เลย
import random
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = random.choice(arr) # randomised -> O(n log n) expected
less = [x for x in arr if x < pivot]
equal = [x for x in arr if x == pivot]
greater = [x for x in arr if x > pivot]
return quick_sort(less) + equal + quick_sort(greater)
print(quick_sort([3, 6, 8, 10, 1, 2, 1])) # sortedฟังก์ชันยกกำลัง: การเรียกซ้ำแบบ log n
การคำนวณ x^n แบบตรงไปตรงมาต้องคูณ O(n) ครั้ง แต่การ ยกกำลังสองจะลดงานลงครึ่งหนึ่งในแต่ละขั้น: x^n = (x^(n/2))^2 จึงได้ O(log n) อย่างชัดเจน — เป็นการลดลงครึ่งหนึ่งที่เห็นได้จริง ดูโค้ดได้เลย
def fast_pow(x, n):
if n == 0: return 1
if n < 0: return 1 / fast_pow(x, -n)
if n % 2 == 0:
half = fast_pow(x, n // 2)
return half * half # O(log n) calls
return x * fast_pow(x, n - 1)
print(fast_pow(2, 10)) # 1024
print(fast_pow(3, 5)) # 243
# Only log2(10)=3-4 recursive calls for n=10ตรวจสอบความเข้าใจอย่างรวดเร็ว
ตรวจสอบความเข้าใจอย่างรวดเร็ว — แสดงสิ่งที่วิธีต้นไม้การเรียกซ้ำสอนคุณ หนึ่งคำถาม ค่อย ๆ คิดได้เต็มที่ 🌳
ทบทวนบทเรียน
ทบทวน: ต้นไม้การเรียกซ้ำเผยให้เห็นงานทั้งหมด ทฤษฎีบทมาสเตอร์ใช้แก้สมการเวียนเกิดของการแบ่งแล้วพิชิต และการเรียกซ้ำใช้พื้นที่สแตกเป็น O(ความลึก)
คำถามที่พบบ่อย
บทเรียน “การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ”
ติดตามการเรียกซ้ำเป็นต้นไม้ ใช้ทฤษฎีบทมาสเตอร์ และหาความซับซ้อนด้านเวลาของการเรียงแบบผสาน แฟกทอเรียล และฟีโบนัชชีรูปแบบต่าง ๆ คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน
บทเรียน “การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม
ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- สัญกรณ์ Big-O ตั้งแต่พื้นฐาน
- วิเคราะห์ลูปและลูปซ้อน
- การเรียกซ้ำและวิธีต้นไม้การเรียกซ้ำ
- ความซับซ้อนด้านพื้นที่และการแลกเปลี่ยน