ما هو الاستدعاء الذاتي؟
دالة تستدعي نفسها — كأن تنظر في مرآة أمام مرآة أخرى.
factorial(5)
│
┌────┴────┐
│ 5 * factorial(4) │
│ │ │
│ ┌────┴────┐ │
│ │ 4 * factorial(3) │
│ │ │ │
│ │ ┌────┴────┐ │
│ │ │ 3 * factorial(2) │
│ │ │ │ │
│ │ │ ┌────┴────┐ │
│ │ │ │ 2 * factorial(1) │
│ │ │ │ │ │
│ │ │ │ ┌────┴────┐ │
│ │ │ │ │ 1 │ │
│ │ │ │ └─────────┘ │
│ │ │ └─────────────────┘ │
│ │ └─────────────────────────┘ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
Base Case vs Recursive Case
كل دالة استدعاء ذاتي تحتاج ركنين:
- Base Case: متى تتوقف؟ (الخروج من المرآة)
- Recursive Case: كيف تستدعي نفسها بمدخل أصغر؟
def countdown(n):
# Base case
if n <= 0:
print("🚀 إقلاع!")
return
# Recursive case
print(n)
countdown(n - 1)
تصوّر مكدس الاستدعاءات (Call Stack)
الاستدعاء countdown(3):
الخطوة 1: الخطوة 2: الخطوة 3:
┌─────────┐ ┌─────────┐ ┌─────────┐
│count(3) │ │count(3) │ │count(3) │
├─────────┤ ├─────────┤ ├─────────┤
│ │ │count(2) │ │count(2) │
└─────────┘ ├─────────┤ ├─────────┤
│ │ │count(1) │
└─────────┘ ├─────────┤
│ │
└─────────┘
الخطوة 4 (تبدأ العودة):
┌─────────┐ ┌─────────┐ ┌─────────┐
│count(3) │ │count(3) │ │ │
├─────────┤ ├─────────┤ └─────────┘
│count(2) │ │ │
├─────────┤ └─────────┘
│count(1) │
├─────────┤
│🚀 إقلاع│
└─────────┘
⚠️ كل استدعاء يستهلك ذاكرة على المكدس. نسيان base case = Stack Overflow.
Factorial مع إطارات المكدس
def fact(n):
if n <= 1:
return 1
return n * fact(n - 1)
fact(4) يستدعي fact(3) ويحتاج نتيجته
└─ fact(3) يستدعي fact(2) ويحتاج نتيجته
└─ fact(2) يستدعي fact(1) ويحتاج نتيجته
└─ fact(1) → يرجع 1 (BASE CASE!)
ثم تبدأ العودة عكسياً:
fact(1) = 1
fact(2) = 2 × 1 = 2
fact(3) = 3 × 2 = 6
fact(4) = 4 × 6 = 24 ✅
Recursion vs Iteration
| المقارنة | الاستدعاء الذاتي | الحلقات (Iteration) |
|---|---|---|
| حجم الكود | أقل وأوضح | أطول أحيانًا |
| الذاكرة | يستهلك مكدسًا | لا يستهلك إضافي |
| الأداء | أبطأ قليلاً | أسرع |
| الاستخدام | أشجار، رسوم بيانية، Divide & Conquer | مسائل بسيطة متكررة |
| سهولة الفهم | طبيعي للمسائل الهرمية | طبيعي للتكرار الخطي |
مشاكل شائعة
1. نسيان base case — يستمر الاستدعاء إلى مالا نهاية:
def forever():
return forever() # Stack Overflow بعد microseconds
2. عدم تقليل حجم المسألة — نفس القيمة تدخل مرارًا:
def infinite(n):
if n == 0:
return 0
return n + infinite(n) # n لم يتغير!
3. استهلاك ذاكرة كبير — recursion depth يتجاوز الحد:
import sys
print(sys.getrecursionlimit()) # عادة 1000
تطبيقات واقعية
- File System: تجدير المجلدات — كل مجلد يحتوي مجلدات فرعية
- DOM Tree: تصفح شجرة HTML
- JSON Parsing: بيانات متداخلة
- Fractals: أشكال رياضية متكررة مثل مثلث Sierpinski
الطبيعة تحب الاستدعاء الذاتي:
🌳 شجرة → فروع ← فروع أصغر ← فروع أصغر ...
🥦 Broccoli → زهيرات ← زهيرات أصغر ...
🫧 فقاعات في الرغوة ← فقاعات أصغر ...
🎯 التالي: البرمجة الديناميكية — أمثلة من الحياة الواقعية.