AlgoVerse

شمارش درخت‌های دودویی (بازگشتی)

تعداد درخت‌های دودویی با n گره را مستقیم از تعریف بازگشتی می‌شمارد؛ درخت فراخوانی نشان می‌دهد چرا نسخه‌ی پویا (عددهای کاتالان) بهتر است.

درخت دودویی با n گره را با ریشه‌اش و زیردرخت‌هایش توصیف می‌کنیم. اگر i گره در زیردرخت چپ باشد، n − 1 − i گره در راست است و هر زوجی از دو زیردرخت یک درخت تازه می‌سازد:

ادامه‌ی توضیح ↓
ورودی:
n=3خروجی PDF

۳ گره: ۵ درخت (۲ + ۱ + ۲، بسته به اینکه چند گره زیر ریشه در سمت چپ است).

  1. def count_trees(n):
  2. # The number of binary trees with n nodes: the root, then i nodes on the left and n − 1 − i on the right.
  3. if n <= 1:
  4. return 1
  5. total = 0
  6. for left in range(n):
  7. right = n - 1 - left
  8. total = total + count_trees(left) * count_trees(right)
  9. return total

ناوردا: هر درخت با n گره دقیقاً یک بار شمرده می‌شود: با تعداد گره‌های زیردرخت چپش.

شروع اجرا

n3
left—
right—
total—

درخت فراخوانی‌های بازگشتی

3

نارنجی پر = فراخوانی در حال اجرا · حاشیه‌ی نارنجی = هنوز روی پشته · خاکستری = تمام شده

متغیرهای محلی

n3

نارنجی = در همین گام عوض شد

Call stack

  1. count_trees()خط 1
گام ۱ از ۷۲
خط سیاه = count · خط نارنجی = گام کلیدیbase: 0/12 · split: 0/7 · count: 0/3
    توضیح

    ایده

    درخت دودویی با n گره را با ریشه‌اش و زیردرخت‌هایش توصیف می‌کنیم. اگر i گره در زیردرخت چپ باشد، n − 1 − i گره در راست است و هر زوجی از دو زیردرخت یک درخت تازه می‌سازد:

    T(n) = Σ T(i) · T(n − 1 − i)   برای i = 0 … n − 1،   T(0) = T(1) = 1
    

    جواب‌ها همان عددهای کاتالان هستند: ۱، ۱، ۲، ۵، ۱۴، ۴۲، …

    چرا کند است؟

    هر فراخوانی چند زیرمسئله‌ی کوچک‌تر را صدا می‌زند و همان زیرمسئله‌ها بارها از نو حساب می‌شوند؛ تعداد فراخوانی‌ها نمایی است. نسخه‌ی برنامه‌ریزی پویا (صفحه‌ی «عددهای کاتالان») هر T(k) را یک بار حساب می‌کند: O(n²).

    در trace به چه چیزی نگاه کنی

    • درخت فراخوانی‌ها را ببین: گره‌هایی با همان n بارها تکرار می‌شوند.