طَبِّقْهاتعلّم المبدأ، وطبّقه بيدكمقرر 101 · تركيب البيانات والخوارزميات
الدرس 5 · الوحدة 3: تصميم وتنفيذ خوارزميات فعّالة

العودية

حل المسألة الكبيرة بحل نسخة أصغر منها، حتى تصل إلى مسألة جوابها معروف. ⏱ نحو 30 دقيقة.

بعد هذا الدرس ستستطيع أن:
  • تعرّف العودية وجزأيها: حالة التوقف والخطوة العودية.
  • تتتبع الاستدعاءات العودية ومكدس الاستدعاءات.
  • تكتب دوال عودية بسيطة: المضروب والمجموع والأس.
  • تقارن بين العودية والحلقة وتختار المناسب.

١دالة تستدعي نفسها

العودية Recursion: أن تحل الدالة المسألة باستدعاء نفسها على نسخة أصغر من المسألة نفسها. مثل الدمى الروسية: تفتح الدمية فتجد دمية أصغر، حتى تصل إلى أصغر دمية لا تُفتح.

الجزءمعناهفي المضروب n!
حالة التوقف Base Caseأصغر مسألة نعرف جوابها مباشرة.0! = 1
الخطوة العوديةنحل المسألة بالاعتماد على حل مسألة أصغر.n! = n × (n−1)!
main.py
def factorial(n):    if n == 0:              # حالة التوقف        return 1    return n * factorial(n - 1)   # الخطوة العودية print(factorial(5))
Shell×
>>> %Run main.py
120
>>>

٢تتبّع الاستدعاءات

كل استدعاء ينتظر نتيجة الاستدعاء الأصغر. اضغط «التالي» وراقب n وهي تنقص حتى 0، ثم النتائج وهي تعود صعودًا:

🔎 تتبّع: factorial(3)
main.py
def factorial(n):    if n == 0:        return 1    return n * factorial(n - 1) print(factorial(3))
Variables · المتغيّرات في الذاكرة
Shell×

يحفظ بايثون الاستدعاءات المنتظرة في مكدس الاستدعاءات Call Stack: آخر استدعاء يُحل أولًا، كما في المكدس الذي عرفته في الدرس 2.

٣أمثلة أخرى

main.py
def total(a):    """مجموع عناصر القائمة بالعودية"""    if len(a) == 0:        return 0    return a[0] + total(a[1:]) def power(x, n):    if n == 0:        return 1    return x * power(x, n - 1) def count_down(n):    if n == 0:        print("انطلق!")        return    print(n)    count_down(n - 1) print(total([4, 7, 1, 8]))print(power(2, 10))count_down(3)
Shell×
>>> %Run main.py
20
1024
3
2
1
انطلق!
>>>

٤بلا حالة توقف: انهيار

main.py
def forever(n):    return forever(n + 1)    # لا توجد حالة توقف forever(1)
Shell×
>>> %Run main.py
Traceback (most recent call last):
File "main.py", line 4, in <module>
forever(1)
File "main.py", line 2, in forever
return forever(n + 1) # لا توجد حالة توقف
File "main.py", line 2, in forever
return forever(n + 1) # لا توجد حالة توقف
File "main.py", line 2, in forever
return forever(n + 1) # لا توجد حالة توقف
[Previous line repeated 995 more times]
RecursionError: maximum recursion depth exceeded
>>>

كل استدعاء يحجز مكانًا في مكدس الاستدعاءات. بلا حالة توقف يمتلئ المكدس، فيوقف بايثون البرنامج بعد نحو 1000 مستوى بالخطأ RecursionError.

٥العودية أم الحلقة؟

كل حل عودي يمكن كتابته بحلقة، والعكس. فمتى نختار العودية؟

العودية أنسب حين…الحلقة أنسب حين…
تتكون المسألة من نسخ أصغر منها: الأشجار، والمجلدات داخل المجلدات، وفرّق تسُد.المسألة تكرار بسيط: مجموع، أو عدّ، أو بحث خطي.
يصبح الكود أقصر وأوضح كثيرًا.العمق قد يكون كبيرًا جدًّا (آلاف المستويات).
main.py
# المضروب بحلقة: الناتج نفسه بلا مكدس استدعاءاتdef factorial_loop(n):    result = 1    for k in range(2, n + 1):        result *= k    return result print(factorial_loop(5))
Shell×
>>> %Run main.py
120
>>>

توقّع وتحقّق

الجزء الذي يوقف العودية:

Base Case.

ما ناتج power(3, 2) في المثال؟

3 × 3.

الخطأ عند غياب حالة التوقف:

يمتلئ المكدس.

أين يحفظ بايثون الاستدعاءات المنتظرة؟

Call Stack.

الخلاصة

تحدٍّ برمجي: عدّ الأرقام بالعودية

متوسط

اكتب دالة عودية digits(n) تُرجع عدد أرقام العدد الصحيح الموجب n، دون تحويله إلى نص.

المطلوب:
  • حالة التوقف: العدد أصغر من 10 ← رقم واحد.
  • الخطوة العودية: 1 + digits(n // 10).
  • اطبع digits(7) و digits(2025) و digits(1000000) كل واحدة في سطر.

ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:

main.py
def digits(n):    pass
المطلوب×
1
4
7
💡 تلميح 1
n // 10 تحذف آخر رقم: 2025 // 10 = 202
💡 تلميح 2
لا تنسَ return قبل 1 + digits(...)

حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.

main.py
def digits(n):    if n < 10:        return 1    return 1 + digits(n // 10) print(digits(7))print(digits(2025))print(digits(1000000))
انتقل إلى تقييم الدرساختيار من متعدد، وصح وخطأ، وأكمل الفراغ، وتتبّع الناتج.ابدأ التقييم ←

📝 معلم؟ أرسل ملاحظة للمؤلف عن هذا الدرس