العودية
حل المسألة الكبيرة بحل نسخة أصغر منها، حتى تصل إلى مسألة جوابها معروف. ⏱ نحو 30 دقيقة.
- تعرّف العودية وجزأيها: حالة التوقف والخطوة العودية.
- تتتبع الاستدعاءات العودية ومكدس الاستدعاءات.
- تكتب دوال عودية بسيطة: المضروب والمجموع والأس.
- تقارن بين العودية والحلقة وتختار المناسب.
١دالة تستدعي نفسها
العودية Recursion: أن تحل الدالة المسألة باستدعاء نفسها على نسخة أصغر من المسألة نفسها. مثل الدمى الروسية: تفتح الدمية فتجد دمية أصغر، حتى تصل إلى أصغر دمية لا تُفتح.
| الجزء | معناه | في المضروب n! |
|---|---|---|
| حالة التوقف Base Case | أصغر مسألة نعرف جوابها مباشرة. | 0! = 1 |
| الخطوة العودية | نحل المسألة بالاعتماد على حل مسألة أصغر. | n! = n × (n−1)! |
def factorial(n): if n == 0: # حالة التوقف return 1 return n * factorial(n - 1) # الخطوة العودية print(factorial(5))
٢تتبّع الاستدعاءات
كل استدعاء ينتظر نتيجة الاستدعاء الأصغر. اضغط «التالي» وراقب n وهي تنقص حتى 0، ثم النتائج وهي تعود صعودًا:
factorial(3)def factorial(n): if n == 0: return 1 return n * factorial(n - 1) print(factorial(3))
يحفظ بايثون الاستدعاءات المنتظرة في مكدس الاستدعاءات Call Stack: آخر استدعاء يُحل أولًا، كما في المكدس الذي عرفته في الدرس 2.
٣أمثلة أخرى
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)
٤بلا حالة توقف: انهيار
def forever(n): return forever(n + 1) # لا توجد حالة توقف forever(1)
كل استدعاء يحجز مكانًا في مكدس الاستدعاءات. بلا حالة توقف يمتلئ المكدس، فيوقف بايثون البرنامج بعد نحو 1000 مستوى بالخطأ RecursionError.
٥العودية أم الحلقة؟
كل حل عودي يمكن كتابته بحلقة، والعكس. فمتى نختار العودية؟
| العودية أنسب حين… | الحلقة أنسب حين… |
|---|---|
| تتكون المسألة من نسخ أصغر منها: الأشجار، والمجلدات داخل المجلدات، وفرّق تسُد. | المسألة تكرار بسيط: مجموع، أو عدّ، أو بحث خطي. |
| يصبح الكود أقصر وأوضح كثيرًا. | العمق قد يكون كبيرًا جدًّا (آلاف المستويات). |
# المضروب بحلقة: الناتج نفسه بلا مكدس استدعاءاتdef factorial_loop(n): result = 1 for k in range(2, n + 1): result *= k return result print(factorial_loop(5))
توقّع وتحقّق
الجزء الذي يوقف العودية:
Base Case.ما ناتج power(3, 2) في المثال؟
3 × 3.الخطأ عند غياب حالة التوقف:
أين يحفظ بايثون الاستدعاءات المنتظرة؟
Call Stack.الخلاصة
- العودية: الدالة تستدعي نفسها على مسألة أصغر.
- لا بد من حالة توقف، وكل استدعاء يقترب منها.
- الاستدعاءات المنتظرة في مكدس الاستدعاءات؛ وبلا توقف:
RecursionError. - العودية للمسائل المتداخلة كالأشجار وفرّق تسُد، والحلقة للتكرار البسيط.
تحدٍّ برمجي: عدّ الأرقام بالعودية
متوسطاكتب دالة عودية digits(n) تُرجع عدد أرقام العدد الصحيح الموجب n، دون تحويله إلى نص.
- حالة التوقف: العدد أصغر من 10 ← رقم واحد.
- الخطوة العودية:
1 + digits(n // 10). - اطبع
digits(7)وdigits(2025)وdigits(1000000)كل واحدة في سطر.
ابدأ من هذا الكود، واضغط ▶ شغّل هنا لتكتب حلّك وتجرّبه مباشرة:
def digits(n): pass
💡 تلميح 1
n // 10 تحذف آخر رقم: 2025 // 10 = 202💡 تلميح 2
1 + digits(...)حاول بنفسك أولًا؛ للمسألة أكثر من حلّ صحيح.
def digits(n): if n < 10: return 1 return 1 + digits(n // 10) print(digits(7))print(digits(2025))print(digits(1000000))