📝 أسئلة اختبارية
عدد الأسئلة: 3
سؤال 4: وضح مزايا استخدام الاستدعاء الذاتي وعيوبه.
- أ) المزايا: كفاءة عالية، العيوب: تعقيد الكود
- ب) المزايا: توفير الذاكرة، العيوب: بطء التنفيذ
- ج) المزايا: بساطة الكود، العيوب: استهلاك ذاكرة أكثر
- د) المزايا: سرعة التنفيذ، العيوب: صعوبة الفهم
الإجابة الصحيحة: مزايا الاستدعاء الذاتي: 1. بساطة ووضوح الكود 2. مناسب للمشاكل التي يمكن تقسيمها إلى مشاكل فرعية متشابهة 3. يقلل من الحاجة إلى حلقات التكرار. عيوبه: 1. قد يكون أبطأ في التنفيذ 2. يستهلك ذاكرة أكثر بسبب تكدس استدعاءات الدوال 3. قد يسبب تجاوز سعة المكدس (stack overflow) إذا كان التكرار عميقاً جداً.
الشرح: الاستدعاء الذاتي مفيد للخوارزميات المتكررة مثل الفرز والبحث، لكنه قد يكون غير فعال للعمليات البسيطة التي يمكن حلها بالتكرار العادي.
تلميح: فكر في كيفية عمل الاستدعاء الذاتي في الذاكرة وكيفية تقسيم المشاكل.
سؤال 5: اكتب دالة استدعاء تكرارية بلغة البايثون تقوم بحساب الرقم الأكبر بترتيب محدد (مثلاً ثاني أكبر رقم) في قائمة من الأرقام.
- أ) def largest_nth(lst, n): return sorted(lst)[-n]
- ب) def find_nth_largest(lst, n):
if len(lst) == 0: return None
max_val = max(lst)
if n == 1: return max_val
new_lst = [x for x in lst if x != max_val]
return find_nth_largest(new_lst, n-1)
- ج) def nth_largest(lst, n):
for i in range(n):
m = max(lst)
lst.remove(m)
return m
- د) def recursive_nth(lst, n):
lst.sort()
return lst[-n]
الإجابة الصحيحة: def find_nth_largest(lst, n):
if len(lst) == 0:
return None
max_val = max(lst)
if n == 1:
return max_val
new_lst = [x for x in lst if x != max_val]
return find_nth_largest(new_lst, n-1)
الشرح: تستخدم الدالة الاستدعاء الذاتي لإيجاد أكبر رقم ثم إزالته من القائمة وتكرار العملية حتى الوصول للترتيب المطلوب.
تلميح: يمكنك استخدام الدالة max() لإيجاد أكبر قيمة ثم إنشاء قائمة جديدة بدونها.
سؤال 6: اكتب دالة استدعاء تكرارية بلغة البايثون لحساب مجموع كل الأرقام الزوجية في قائمة معينة.
- أ) def sum_even(lst):
total = 0
for num in lst:
if num % 2 == 0:
total += num
return total
- ب) def sum_even_numbers(lst):
if len(lst) == 0: return 0
first = lst[0]
rest = lst[1:]
if first % 2 == 0:
return first + sum_even_numbers(rest)
else:
return sum_even_numbers(rest)
- ج) def recursive_even_sum(lst):
return sum([x for x in lst if x % 2 == 0])
- د) def even_sum(lst):
if not lst: return 0
return (lst[0] if lst[0] % 2 == 0 else 0) + even_sum(lst[1:])
الإجابة الصحيحة: def sum_even_numbers(lst):
if len(lst) == 0:
return 0
first = lst[0]
rest = lst[1:]
if first % 2 == 0:
return first + sum_even_numbers(rest)
else:
return sum_even_numbers(rest)
الشرح: تتحقق الدالة من أول عنصر في القائمة، إذا كان زوجياً تضيفه إلى المجموع، ثم تستدعي نفسها على بقية القائمة.
تلميح: تذكر أن الشرط الأساسي هو عندما تكون القائمة فارغة، وعندها ترجع 0.
🎴 بطاقات تعليمية للمراجعة
عدد البطاقات: 4 بطاقة لهذه الصفحة
ما هي المزايا الرئيسية لاستخدام الاستدعاء الذاتي (Recursion) في البرمجة؟
الإجابة: تتضمن المزايا الرئيسية للاستدعاء الذاتي تبسيط الحلول للمشاكل التي يمكن تقسيمها إلى مشاكل فرعية متشابهة، وجعل الكود أكثر وضوحاً وقابلية للقراءة في بعض الحالات، وتمكين الحلول الأنيقة للمشاكل المعقدة مثل معالجة هياكل البيانات المتداخلة (مثل الأشجار).
الشرح: الاستدعاء الذاتي هو تقنية برمجية تسمح للدالة باستدعاء نفسها. هذا مفيد بشكل خاص للمشاكل التي لها طبيعة تكرارية، حيث يمكن تقسيم المشكلة الكبيرة إلى نسخ أصغر من نفس المشكلة. هذا يؤدي إلى شفرة غالباً ما تكون أقصر وأكثر تعبيراً مقارنة بالحلول التكرارية التقليدية (باستخدام الحلقات).
تلميح: فكر في كيف يمكن أن يجعل حل المشكلة المعقدة أبسط بتكرار نفس الخطوات على أجزاء أصغر منها.
ما هي أبرز عيوب استخدام الاستدعاء الذاتي (Recursion) في البرمجة؟
الإجابة: تشمل العيوب الرئيسية للاستدعاء الذاتي استهلاك كمية كبيرة من ذاكرة المكدس (Stack Memory) مما قد يؤدي إلى تجاوز سعة المكدس (Stack Overflow)، وصعوبة تتبع التنفيذ وفهمه مقارنة بالحلقات، واحتمالية بطء الأداء بسبب الحمل الزائد لاستدعاء الدوال المتكرر، وعدم كفاءته في بعض المسائل مقارنة بالحلول التكرارية.
الشرح: كل استدعاء دالة يضيف إطاراً جديداً إلى مكدس الاستدعاءات (Call Stack) لتخزين معلومات حول الاستدعاء. عند استخدام الاستدعاء الذاتي بشكل مفرط دون وجود شرط توقف مناسب، يمكن أن يمتلئ المكدس، مما يؤدي إلى خطأ تجاوز سعة المكدس. كما أن استدعاءات الدوال المتكررة تحمل تكلفة أداء أعلى من مجرد تكرار كتلة من التعليمات باستخدام حلقة.
تلميح: ما هي الآلية التي تعمل بها الدوال؟ وكيف يمكن للعديد من استدعاءات الدوال أن تؤثر على هذه الآلية؟
اكتب دالة بايثون استدعاء تكراري (Recursive Function) لحساب ثاني أكبر رقم في قائمة غير مرتبة من الأرقام.
الإجابة: python
def find_second_largest_recursive(arr):
if len(arr) < 2:
return None # أو يمكن رفع خطأ
# حالة الأساس: إذا كانت القائمة تحتوي على عنصرين فقط
if len(arr) == 2:
return min(arr)
# دعنا نتبع نهجاً يبحث عن أكبر عنصر أولاً ثم ثاني أكبر عنصر
# لتجنب تعقيد دالة تكرارية واحدة لحساب ثاني أكبر عنصر مباشرة
# سنفترض هنا وجود دالة مساعدة أو نهج مبسط.
# الحل التكراري المباشر لـ "ثاني أكبر" معقد جداً.
# لتبسيط الأمر، سنستخدم هنا طريقة غير مباشرة تتطلب معالجة إضافية.
# الأسلوب الأكثر شيوعاً هو الفرز أو التكرار، لكن لغرض التوضيح التكراري:
# مثال توضيحي لفكرة استدعاء تكراري (ليس الحل الأمثل والأكثر كفاءة لـ "ثاني أكبر")
# الفكرة هي إيجاد الأكبر، ثم البحث عن الأكبر في الباقي.
def find_max(lst):
if len(lst) == 1:
return lst[0]
return max(lst[0], find_max(lst[1:]))
max_num = find_max(arr)
# الآن، نجد الأكبر في القائمة بعد إزالة جميع تكرارات أكبر رقم
# هذا النهج قد لا يكون فعالاً جداً بالاستدعاء الذاتي فقط
# ولكنه يوضح استخدام الاستدعاء الذاتي في حل جزء من المشكلة.
# لتبسيط الإجابة والتركيز على مفهوم الاستدعاء التكراري:
# مثال أبسط: حساب أكبر رقم (أسهل للاستدعاء التكراري)
# def find_max_recursive(lst):
# if len(lst) == 1:
# return lst[0]
# return max(lst[0], find_max_recursive(lst[1:]))
# max_num = find_max_recursive(arr)
# لإيجاد ثاني أكبر رقم بتعقيد مقبول باستخدام الاستدعاء
# غالباً ما يتم ذلك عبر تتبع أكبر رقمين أثناء المرور
# الذي يمكن تمثيله تكرارياً.
# إليك حل يتبع مبدأ الاستدعاء ولكن يتطلب نهجاً مختلفاً قليلاً:
# سنتتبع أكبر عنصرين:
def find_two_largest(lst, largest, second_largest):
if not lst:
return second_largest
current = lst[0]
if current > largest:
second_largest = largest
largest = current
elif current > second_largest and current != largest:
second_largest = current
return find_two_largest(lst[1:], largest, second_largest)
initial_largest = float('-inf')
initial_second_largest = float('-inf')
# نحتاج لتهيئة أولية مختلفة قليلاً إذا كانت القائمة تحتوي على أقل من عنصرين
if len(arr) < 2:
return None # لا يوجد ثاني أكبر
# تهيئة أولية تعتمد على أول عنصرين
if arr[0] > arr[1]:
initial_largest = arr[0]
initial_second_largest = arr[1]
else:
initial_largest = arr[1]
initial_second_largest = arr[0]
# ابدأ الاستدعاء من العنصر الثالث
return find_two_largest(arr[2:], initial_largest, initial_second_largest)
الشرح: لإيجاد ثاني أكبر رقم باستخدام الاستدعاء الذاتي، نحتاج إلى تتبع أكبر رقمين أثناء معالجة القائمة. دالة `find_two_largest` تأخذ القائمة، أكبر قيمة تم العثور عليها حتى الآن، وثاني أكبر قيمة. في كل خطوة، تقارن العنصر الحالي مع أكبر وثاني أكبر، وتقوم بتحديثهما حسب الحاجة، ثم تستدعي نفسها لبقية القائمة. شرط التوقف هو عندما تصبح القائمة فارغة.
تلميح: فكر في كيفية معالجة القائمة عن طريق تقسيمها إلى أول عنصر وبقية القائمة، وتتبع أكبر رقمين أثناء هذا التقسيم.
اكتب دالة استدعاء تكراري (Recursive Function) بلغة البايثون لحساب مجموع كل الأرقام الزوجية في قائمة معينة.
الإجابة: python
def sum_even_recursive(arr):
if not arr:
return 0 # حالة الأساس: قائمة فارغة، المجموع صفر
first_element = arr[0]
rest_of_list = arr[1:]
# تحقق إذا كان العنصر الأول زوجياً
if first_element % 2 == 0:
# إذا كان زوجياً، أضفه إلى مجموع الأرقام الزوجية في بقية القائمة
return first_element + sum_even_recursive(rest_of_list)
else:
# إذا كان فردياً، تجاهله وأوجد مجموع الأرقام الزوجية في بقية القائمة
return sum_even_recursive(rest_of_list)
الشرح: هذه الدالة تحقق ما إذا كان العنصر الأول في القائمة زوجياً. إذا كان كذلك، فإنه يُضاف إلى نتيجة استدعاء الدالة نفسه على بقية القائمة. إذا كان العنصر فردياً، فإنه يتم تجاهله ويتم حساب مجموع الأرقام الزوجية في بقية القائمة فقط. شرط التوقف هو عندما تصبح القائمة فارغة، حيث يكون المجموع صفر.
تلميح: ما هو الشرط الذي يجب أن يتحقق ليتم تضمين الرقم في المجموع؟ وكيف يمكنك معالجة بقية القائمة بنفس المنطق؟