الاستدعاء الذاتي (علوم الحاسوب)
في علم الحاسوب ، يُعدّ الاستدعاء الذاتي طريقةً لحلّ المسائل الحسابية التي يعتمد حلّها على حلولٍ لحالاتٍ أصغر من المسألة نفسها. [ 1 ] [ 2 ] يحلّ الاستدعاء الذاتي هذه المسائل باستخدام دوالّ تستدعي نفسها من داخل شيفرتها البرمجية. يمكن تطبيق هذا النهج على أنواعٍ عديدة من المسائل، ويُعتبر الاستدعاء الذاتي أحد الأفكار المحورية في علم الحاسوب. [ 3 ]
تكمن قوة الاستدعاء الذاتي بوضوح في إمكانية تعريف مجموعة لا نهائية من الكائنات بعبارة محدودة . وبالمثل، يمكن وصف عدد لا نهائي من العمليات الحسابية بواسطة برنامج استدعاء ذاتي محدود، حتى لو لم يتضمن هذا البرنامج أي تكرارات صريحة.
— نيكلاوس ويرث ، الخوارزميات + هياكل البيانات = البرامج ، 1976 [ 4 ]
تدعم معظم لغات البرمجة التكرارية، إذ تسمح للدالة باستدعاء نفسها من داخل شيفرتها. بعض لغات البرمجة الوظيفية (مثل كلوجر ) [ 5 ] لا تُعرّف أي بنى تكرارية مدمجة، وتعتمد بدلاً من ذلك على التكرار فقط. وقد ثبت في نظرية الحوسبة أن هذه اللغات التي تعتمد على التكرار فقط هي لغات كاملة تورينج ؛ وهذا يعني أنها تتمتع بنفس قوة اللغات الإجرائية القائمة على هياكل التحكم (يمكن استخدامها لحل نفس المشكلات while) for.
قد يؤدي استدعاء دالة بشكل متكرر من داخلها إلى أن يصبح حجم مكدس الاستدعاءات مساويًا لمجموع أحجام المدخلات لجميع الاستدعاءات المعنية. وبناءً على ذلك، بالنسبة للمسائل التي يمكن حلها بسهولة بالتكرار، يكون الاستدعاء الذاتي أقل كفاءة بشكل عام ، وفي بعض المسائل، قد تُحسّن تقنيات التحسين الخوارزمي أو المُصرّف، مثل تحسين استدعاء الذيل، الأداء الحسابي مقارنةً بالتنفيذ الاستدعائي البسيط.
تاريخ
نشأ مفهوم الاستدعاء الذاتي في علوم الحاسوب من المنطق الرياضي ، وأصبح لاحقًا جزءًا أساسيًا من تصميم لغات البرمجة . [ 6 ] وقد أرست الأعمال المبكرة التي قام بها كل من تشرش ، وغودل ، وكلين ، وتورينغ حول الدوال الاستدعائية والحوسبة الأساس الذي جعل الاستدعاء الذاتي ممكنًا في لغات البرمجة. [ 6 ] استخدم علماء الرياضيات الاستدعاء الذاتي لفترة طويلة، ولكنه لم يصبح أداة عملية للبرمجة إلا في أواخر الخمسينيات وأوائل الستينيات. [ 7 ] وقد ساهمت شخصيات بارزة مثل جون مكارثي ولجنة تصميم ALGOL 60 في إدخال الاستدعاء الذاتي إلى البرمجة. [ 7 ]
خطا جون مكارثي الخطوات الأولى بإنشاء لغة البرمجة LISP عام 1960. [ 8 ] في بحثه " الدوال التكرارية للتعبيرات الرمزية وحسابها بواسطة الآلة، الجزء الأول" ، بيّن مكارثي أن التكرار يمكن أن يكون جوهريًا في لغة برمجة تتعامل مع الرموز من خلال معالجتها خطوة بخطوة. [ 8 ] في LISP، يمكن استخدام التكرار في الدوال باستخدام قواعد بسيطة، كما توجد طريقة لتقييمها داخل اللغة. [ 8 ] أثبت هذا أن التكرار طريقة عملية لكتابة البرامج، وأنه يصف أيضًا عملية الحساب. [ 6 ] لذلك، أصبحت LISP من أوائل لغات البرمجة التي استخدمت التكرار كميزة رئيسية، وأثرت لاحقًا على لغات أخرى تلتها. [ 7 ]
خلال تلك الفترة، أُضيفت خاصية الاستدعاء الذاتي إلى لغة ALGOL 60. [ 9 ] وكان تقرير لغة الخوارزميات ALGOL 60 ، الذي نُشر عام 1960، ثمرة جهد دولي لتصميم لغة قياسية. [ 9 ] وكان السماح للإجراءات باستدعاء نفسها إحدى الميزات الجديدة المهمة للغة. [ 7 ] قبل ذلك، كان يُسمح للمبرمجين باستخدام الحلقات فقط، لذا كان هذا تغييرًا جوهريًا. [ 7 ] وقد مكّن الاستدعاء الذاتي المبرمجين من وصف الخوارزميات بطريقة أكثر طبيعية ومرونة. [ 7 ]
بنية الدالة التكرارية
يُقسّم تعريف الدالة التكرارية عادةً إلى جزأين: حالة أساسية واحدة أو أكثر، وحالة تكرارية واحدة أو أكثر. [ 10 ] يعكس هذا الهيكل منطق الاستقراء الرياضي ، وهو أسلوب إثبات يضمن فيه إثبات الحالة الأساسية وخطوة الاستقراء صحة نظرية معينة لجميع المدخلات الصحيحة.
الحالة الأساسية
تحدد حالة الأساس قيم الإدخال التي يمكن للدالة عندها تقديم نتيجة مباشرة، دون الحاجة إلى أي تكرار إضافي. [ 11 ] عادةً ما تكون هذه أبسط أو أصغر المدخلات الممكنة (والتي يمكن حلها بسهولة)، مما يسمح بإنهاء الحساب. حالات الأساس ضرورية لأنها تمنع التكرار اللانهائي. بعبارة أخرى، تحدد حالة توقف تُنهي التكرار.
مثال على ذلك حساب مضروب العدد الصحيح n، وهو حاصل ضرب جميع الأعداد الصحيحة من 0 إلى n. في هذه المسألة، يُعدّ التعريف 0! = 1 حالة أساسية. بدونه، قد يستمر الاستدعاء الذاتي إلى ما لا نهاية، مما يؤدي إلى عدم توقف البرنامج أو حتى أخطاء تجاوز سعة المكدس في التطبيقات العملية.
يُعدّ تصميم حالة أساسية صحيحة أمرًا بالغ الأهمية لأسباب نظرية وعملية على حد سواء. فبعض المسائل لها حالة أساسية طبيعية (مثلًا، القائمة الفارغة هي حالة أساسية في بعض دوال معالجة القوائم المتكررة)، بينما تتطلب مسائل أخرى مُعاملًا إضافيًا لتوفير معيار للتوقف (مثلًا، استخدام عداد العمق في اجتياز الشجرة المتكرر ).
في البرمجة الحاسوبية التكرارية ، قد يؤدي إغفال حالة التوقف أو تعريفها بشكل خاطئ إلى تكرار لا نهائي غير مقصود. وقد أظهرت دراسة أن العديد من الطلاب يجدون صعوبة في تحديد حالات التوقف المناسبة. [ 11 ]
الحالة المتكررة
تصف الحالة التكرارية كيفية تقسيم المشكلة إلى مشاكل فرعية أصغر من نفس الشكل. [ 11 ] تُحوّل كل خطوة تكرارية المدخلات بحيث تقترب من حالة أساسية، مما يضمن التقدم نحو الإنهاء. إذا فشلت خطوة الاختزال في التقدم نحو حالة أساسية، فقد يعلق البرنامج في حلقة لا نهائية .
في مثال المضروب ، تُعرَّف الحالة التكرارية على النحو التالي: هنا، كل استدعاء للدالة يقلل من المدخلاتبـ 1. وبالتالي، يضمن ذلك أن يصل التكرار في النهاية إلى الحالة الأساسية لـ.
تُشابه الحالة التكرارية خطوة الاستقراء في البرهان بالاستقراء : فهي تفترض أن الدالة تعمل مع حالة أصغر، ثم تُعمم هذا الافتراض على المُدخل الحالي. وبالتالي، فإن التعريفات والخوارزميات التكرارية تُوازي الحجج الاستقرائية في الرياضيات ، وغالبًا ما تعتمد صحتها على تقنيات استدلال مماثلة.
أمثلة
توجد العديد من التطبيقات العملية للتكرار التي تتبع بنية الحالة الأساسية والحالة التكرارية. وتُستخدم هذه التطبيقات على نطاق واسع لحل المشكلات المعقدة في علوم الحاسوب.
- تستخدم عمليات اجتياز الأشجار ، مثل البحث العميق أولاً ، حالة تكرارية لمعالجة كل شجرة فرعية (الحالة التكرارية)، وتتوقف في النهاية عند العقد الورقية (الحالة الأساسية). [ 12 ]
- فرز الدمج هو خوارزمية فرز تقوم بفرز ودمج المصفوفات الفرعية المقسمة بشكل متكرر (الحالة المتكررة) حتى تتكون كل مصفوفة فرعية من عنصر واحد (الحالة الأساسية). [ 13 ]
- يتم تعريف متتالية فيبوناتشي عن طريق جمع الرقمين السابقين في المتتالية (الحالة التكرارية)، والتوقف عندما n = 0 أو n = 1 (الحالات الأساسية).
- يقوم البحث الثنائي بتقسيم فترة البحث إلى نصفين بشكل متكرر (الحالة المتكررة) حتى يتم العثور على الهدف أو تصبح الفترة فارغة (الحالات الأساسية).
أنواع البيانات المتكررة
تتطلب العديد من برامج الحاسوب معالجة أو توليد كميات هائلة من البيانات . يُعدّ الاستدعاء الذاتي أسلوبًا لتمثيل البيانات التي يكون حجمها الدقيق غير معروف للمبرمج ، حيث يُمكنه تحديد هذه البيانات بتعريف مرجعي ذاتي . يوجد نوعان من التعريفات المرجعية الذاتية: التعريفات الاستقرائية والتعريفات الاستقراءية المشتركة .
البيانات المحددة استقرائياً
تعريف البيانات الاستقرائي المتكرر هو تعريف يحدد كيفية إنشاء نسخ من البيانات. على سبيل المثال، يمكن تعريف القوائم المرتبطة استقرائيًا (هنا، باستخدام صيغة هاسكل ):
data ListOfStrings = EmptyList | Cons String ListOfStringsيُحدد الكود أعلاه قائمة من السلاسل النصية إما فارغة، أو بنية تحتوي على سلسلة نصية وقائمة من السلاسل النصية. يسمح المرجع الذاتي في التعريف بإنشاء قوائم تحتوي على أي عدد (محدود) من السلاسل النصية.
مثال آخر على التعريف الاستقرائي هو الأعداد الطبيعية (أو الأعداد الصحيحة الموجبة ):
العدد الطبيعي إما 1 أو n+1، حيث n هو عدد طبيعي.
وبالمثل ، تُستخدم التعريفات التكرارية غالبًا لنمذجة بنية التعبيرات والعبارات في لغات البرمجة. غالبًا ما يُعبّر مصممو اللغات عن القواعد النحوية بصيغة مثل صيغة باكوس-ناور ؛ إليك مثال على هذه القواعد النحوية، للغة بسيطة من التعبيرات الحسابية التي تتضمن الضرب والجمع:
<expr> :: = <number> | ( <expr> * <expr> ) | ( < تعبير > + < تعبير > ) ينص هذا على أن التعبير إما أن يكون عددًا، أو حاصل ضرب تعبيرين، أو مجموع تعبيرين. وباستخدام الإشارة المتكررة إلى التعبيرات في السطرين الثاني والثالث، تسمح القواعد النحوية بتعبيرات حسابية معقدة كيفما كانت (5 * ((3 * 6) + 8))، مثل التعبير الذي يحتوي على أكثر من عملية ضرب أو جمع في التعبير الواحد.
البيانات المُعرَّفة استقرائيًا والتكرار المشترك
تعريف البيانات الاستقرائي هو تعريف يحدد العمليات التي يمكن إجراؤها على جزء من البيانات؛ وعادةً ما تُستخدم التعريفات الاستقرائية المرجعية الذاتية لهياكل البيانات ذات الحجم اللانهائي.
قد يبدو التعريف الاستقرائي للتيارات اللانهائية من السلاسل، المقدم بشكل غير رسمي، على النحو التالي:
سلسلة السلاسل النصية هي كائن s بحيث: الرأس (الرؤوس) عبارة عن سلسلة، و tail(s) عبارة عن سلسلة من السلاسل النصية.
هذا يشبه إلى حد كبير التعريف الاستقرائي لقوائم السلاسل النصية؛ والفرق هو أن هذا التعريف يحدد كيفية الوصول إلى محتويات بنية البيانات - أي عبر وظائف الوصولhead - وما tailقد تكون عليه تلك المحتويات، بينما يحدد التعريف الاستقرائي كيفية إنشاء البنية وما يمكن إنشاؤها منه.
التكرار المشترك مرتبط بالاستقراء المشترك، ويمكن استخدامه لحساب حالات محددة من كائنات (قد تكون) لانهائية. كتقنية برمجة، يُستخدم غالبًا في سياق لغات البرمجة الكسولة ، وقد يكون مفضلًا على التكرار عندما يكون حجم أو دقة مخرجات البرنامج غير معروفة. في مثل هذه الحالات، يتطلب البرنامج تعريفًا لنتيجة لانهائية الحجم (أو لانهائية الدقة)، وآلية لأخذ جزء محدود من تلك النتيجة. تُعد مشكلة حساب أول n عدد أولي مثالًا على مشكلة يمكن حلها باستخدام برنامج تكرار مشترك (على سبيل المثال هنا ).
أنواع الاستدعاء الذاتي
التكرار الأحادي والتكرار المتعدد
يُعرف الاستدعاء الذاتي الذي يحتوي على مرجع ذاتي واحد فقط باسمالاستدعاء الذاتي الأحادي ، بينما يُعرف الاستدعاء الذاتي الذي يحتوي على مراجع ذاتية متعددة باسمالاستدعاء الذاتي المتعدد . تشمل الأمثلة القياسية للاستدعاء الذاتي الفردي اجتياز القائمة، كما هو الحال في البحث الخطي، أو حساب دالة المضروب، بينما تشمل الأمثلة القياسية للاستدعاء الذاتي المتعدداجتياز الشجرة، كما هو الحال في البحث العميق أولاً.
غالبًا ما يكون الاستدعاء الذاتي الأحادي أكثر كفاءة من الاستدعاء الذاتي المتعدد، ويمكن استبداله عمومًا بعملية حسابية تكرارية، تعمل في وقت خطي وتتطلب مساحة ثابتة. في المقابل، قد يتطلب الاستدعاء الذاتي المتعدد وقتًا ومساحة أسيين، وهو في جوهره استدعاء ذاتي، ولا يمكن استبداله بالتكرار دون استخدام مكدس صريح.
يمكن أحيانًا تحويل الاستدعاء المتعدد إلى استدعاء أحادي (وإذا رغبنا، إلى تكرار). على سبيل المثال، بينما يتطلب حساب متتالية فيبوناتشي في ظاهره تكرارًا متعددًا، حيث تتطلب كل قيمة قيمتين سابقتين، يمكن حسابها باستخدام استدعاء أحادي بتمرير قيمتين متتاليتين كمعاملات.
التكرار غير المباشر
تُظهر معظم الأمثلة الأساسية للاستدعاء الذاتي، ومعظم الأمثلة المعروضة هنا،الاستدعاء المباشر هو استدعاء دالة لنفسها. أما الاستدعاء غير المباشر فيحدث عندما تُستدعى دالةٌ ما ليس من قِبلها، بل من قِبل دالة أخرى استدعتها (سواءً بشكل مباشر أو غير مباشر). على سبيل المثال، إذااستدعت الدالة f الدالة f، فهذا استدعاء مباشر، أما إذااستدعت f الدالة g التي تستدعي f، فهذا استدعاء غير مباشر للدالة f. ويمكن أن تتكون سلاسل الاستدعاء من ثلاث دوال أو أكثر؛ على سبيل المثال، تستدعي الدالة 1 الدالة 2، ثم تستدعي الدالة 2 الدالة 3، وهكذا تستدعي الدالة 3 الدالة 1 مرة أخرى.
يُطلق على الاستدعاء غير المباشر أيضًا اسم الاستدعاء المتبادل ، وهو مصطلح أكثر تناسقًا، مع أن هذا مجرد اختلاف في التركيز، وليس اختلافًا في المفهوم. بمعنى آخر، إذا استدعت الدالة f الدالة g ، ثم استدعت g الدالة f، التي بدورها استدعت g مرة أخرى، فمن وجهة نظر f وحدها، تكون f مستدعاة بشكل غير مباشر، بينما من وجهة نظر g وحدها، تكون g مستدعاة بشكل غير مباشر، أما من وجهة نظر كلتيهما، فإن f و g تستدعيان بعضهما البعض بشكل متبادل. وبالمثل، يمكن تسمية مجموعة من ثلاث دوال أو أكثر تستدعي بعضها البعض بمجموعة من الدوال المستدعاة بشكل متبادل.
التكرار المجهول
عادةً ما يتم تنفيذ الاستدعاء الذاتي عن طريق استدعاء دالة بالاسم صراحةً. ومع ذلك، يمكن أيضًا تنفيذ الاستدعاء الذاتي عن طريق استدعاء دالة ضمنيًا بناءً على السياق الحالي، وهو أمر مفيد بشكل خاص للدوال المجهولة ، ويُعرف باسم الاستدعاء الذاتي المجهول .
التكرار البنيوي مقابل التكرار التوليدي
يصنف بعض المؤلفين الاستدعاء الذاتي إلى نوعين: "بنيوي" و"توليدي". ويرتبط هذا التمييز بمصدر البيانات التي يحصل عليها الإجراء الاستدعائي، وكيفية معالجته لتلك البيانات.
تقوم الدوال التي تستخدم البيانات المهيكلة عادةً بتحليل وسائطها إلى مكوناتها الهيكلية المباشرة، ثم تعالج هذه المكونات. إذا كان أحد المكونات المباشرة ينتمي إلى نفس فئة البيانات التي ينتمي إليها المُدخل، فإن الدالة تكون تكرارية. ولهذا السبب، نشير إلى هذه الدوال باسم الدوال التكرارية (هيكليًا).
— فيليسين، فيندلر، فلات، وكريشناورثي، كيفية تصميم البرامج ، 2001 [ 14 ]
وبالتالي، فإن السمة المميزة للدالة التكرارية البنيوية هي أن وسيط كل استدعاء تكراري هو محتوى حقل من المدخلات الأصلية. يشمل التكرار البنيوي جميع عمليات اجتياز الأشجار تقريبًا، بما في ذلك معالجة XML، وإنشاء الأشجار الثنائية والبحث فيها، وما إلى ذلك. وبالنظر إلى البنية الجبرية للأعداد الطبيعية (أي أن العدد الطبيعي إما أن يكون صفرًا أو العدد التالي لعدد طبيعي آخر)، يمكن اعتبار دوال مثل دالة المضروب تكرارًا بنيويًا أيضًا.
الاستدعاء التوليدي هو البديل:
تُنشئ العديد من الخوارزميات التكرارية المعروفة بيانات جديدة تمامًا من البيانات المُعطاة، ثم تُكرر العملية عليها. ويُشير كتاب " كيفية تصميم البرامج " ( HtDP ) إلى هذا النوع من الخوارزميات باسم التكرار التوليدي. ومن أمثلة التكرار التوليدي: إيجاد القاسم المشترك الأكبر ، والفرز السريع ، والبحث الثنائي ، وفرز الدمج ، وطريقة نيوتن ، والكسور الهندسية ، والتكامل التكيفي .
— ماتياس فيليسين، البرمجة الوظيفية المتقدمة ، 2002 [ 15 ]
هذا التمييز مهم في إثبات انتهاء الدالة.
- يمكن بسهولة إثبات أن جميع الدوال المتكررة هيكليًا على هياكل البيانات المحدودة ( المحددة استقرائيًا ) تنتهي، من خلال الاستقراء الهيكلي : بشكل بديهي، تتلقى كل استدعاء متكرر جزءًا أصغر من بيانات الإدخال، حتى يتم الوصول إلى حالة أساسية.
- على النقيض من ذلك، لا تُدخل الدوال التوليدية التكرارية بالضرورة مدخلات أصغر إلى استدعاءاتها التكرارية، لذا فإن إثبات توقفها ليس بالضرورة بهذه البساطة، ويتطلب تجنب الحلقات اللانهائية عناية أكبر. غالبًا ما يمكن تفسير هذه الدوال التوليدية التكرارية على أنها دوال تكرارية مشتركة - حيث تُولّد كل خطوة بيانات جديدة، مثل التقريب المتتالي في طريقة نيوتن - ويتطلب إنهاء هذا التكرار المشترك أن تُحقق البيانات في النهاية شرطًا ما، وهو أمر غير مضمون بالضرورة.
- فيما يتعلق بمتغيرات الحلقة ، فإن التكرار الهيكلي هو عندما يكون هناك متغير حلقة واضح، وهو الحجم أو التعقيد، والذي يبدأ محدودًا ويتناقص في كل خطوة تكرارية.
- وعلى النقيض من ذلك، فإن التكرار التوليدي هو عندما لا يكون هناك متغير حلقة واضح، ويعتمد الإنهاء على دالة، مثل "خطأ التقريب" الذي لا ينخفض بالضرورة إلى الصفر، وبالتالي فإن الإنهاء غير مضمون بدون مزيد من التحليل.
مشاكل التنفيذ
في التطبيق الفعلي، بدلاً من استخدام دالة تكرارية بحتة (تحقق واحد من حالة الأساس، وإلا خطوة تكرارية)، قد تُجرى عدة تعديلات، لأغراض الوضوح أو الكفاءة. وتشمل هذه التعديلات ما يلي:
- دالة التغليف (في الأعلى)
- اختصار الحالة الأساسية، والمعروفة أيضًا باسم "الاستدعاء الذاتي على مسافة الذراع" (في الأسفل)
- الخوارزمية الهجينة (في الأسفل) - يتم التبديل إلى خوارزمية مختلفة بمجرد أن تصبح البيانات صغيرة بما يكفي
تُعتبر الدوال المُغلِّفة مقبولة عمومًا نظرًا لأناقتها، بينما يُستنكر اختصار حالة الأساس، لا سيما في الأوساط الأكاديمية. غالبًا ما تُستخدم الخوارزميات الهجينة لتحسين الكفاءة، وتقليل عبء الاستدعاء الذاتي في الحالات الصغيرة، ويُعدّ الاستدعاء الذاتي غير المباشر حالة خاصة من ذلك.
دالة التغليف
الدالة المغلفة هي دالة يتم استدعاؤها مباشرة ولكنها لا تستدعي نفسها بشكل متكرر، بل تستدعي دالة مساعدة منفصلة تقوم فعليًا بالتكرار.
يمكن استخدام الدوال المُغلِّفة للتحقق من صحة المعاملات (بحيث يمكن للدالة التكرارية تجاوزها)، وإجراء عمليات التهيئة (تخصيص الذاكرة، وتهيئة المتغيرات)، لا سيما للمتغيرات المساعدة مثل "مستوى التكرار" أو العمليات الحسابية الجزئية للتخزين المؤقت ، ومعالجة الاستثناءات والأخطاء. في اللغات التي تدعم الدوال المتداخلة ، يمكن تضمين الدالة المساعدة داخل الدالة المُغلِّفة واستخدام نطاق مشترك. في حال عدم وجود دوال متداخلة، تكون الدوال المساعدة دالة منفصلة، ويفضل أن تكون خاصة (لأنها لا تُستدعى مباشرةً)، وتُشارك المعلومات مع الدالة المُغلِّفة باستخدام تمرير المرجع .
تقصير الدائرة في الحالة الأساسية
| التكرار العادي | التكرار المختصر |
|---|---|
دالة ` fac1` تأخذ عددًا صحيحًا ` n` كمدخل ، وتعيد ` 1` إذا كان ` n` أقل من أو يساوي صفرًا . وإلا ، فإنها تعيد `fac1 ( n - 1 ) * n` . | intfac2(intn){// assert(n >= 2);if(n==2){return2;}else{returnfac2(n-1)*n;}}intfac2wrapper(intn){if(n<=1){return1;}else{returnfac2(n);}} |
Short-circuiting the base case, also known as arm's-length recursion, consists of checking the base case before making a recursive call – i.e., checking if the next call will be the base case, instead of calling and then checking for the base case. Short-circuiting is particularly done for efficiency reasons, to avoid the overhead of a function call that immediately returns. Note that since the base case has already been checked for (immediately before the recursive step), it does not need to be checked for separately, but one does need to use a wrapper function for the case when the overall recursion starts with the base case itself. For example, in the factorial function, properly the base case is 0! = 1, while immediately returning 1 for 1! is a short circuit, and may miss 0; this can be mitigated by a wrapper function. The box shows C code to shortcut factorial cases 0 and 1.
Short-circuiting is primarily a concern when many base cases are encountered, such as Null pointers in a tree, which can be linear in the number of function calls, hence significant savings for O(n) algorithms; this is illustrated below for a depth-first search. Short-circuiting on a tree corresponds to considering a leaf (non-empty node with no children) as the base case, rather than considering an empty node as the base case. If there is only a single base case, such as in computing the factorial, short-circuiting provides only O(1) savings.
من الناحية النظرية، يمكن اعتبار الاختصار إما ذا حالة أساسية وخطوة تكرارية متماثلتين، حيث يتم التحقق من الحالة الأساسية فقط قبل التكرار، أو ذا حالة أساسية مختلفة (خطوة واحدة أبعد عن الحالة الأساسية القياسية) وخطوة تكرارية أكثر تعقيدًا، وهي "التحقق من الصحة ثم التكرار"، كما في اعتبار العقد الورقية بدلاً من العقد الفارغة حالات أساسية في الشجرة. ولأن الاختصار يتميز بتدفق أكثر تعقيدًا، مقارنةً بالفصل الواضح بين الحالة الأساسية والخطوة التكرارية في التكرار القياسي، فإنه يُعتبر غالبًا أسلوبًا برمجيًا رديئًا، لا سيما في الأوساط الأكاديمية. [ 16 ]
البحث العميق أولاً
يُعطى مثال أساسي على اختصار الدائرة في البحث العميق أولاً (DFS) لشجرة ثنائية؛ انظر قسم الأشجار الثنائية للمناقشة التكرارية القياسية.
الخوارزمية التكرارية القياسية للبحث العميق أولاً هي:
- الحالة الأساسية: إذا كانت العقدة الحالية فارغة، فأرجع خطأ
- الخطوة التكرارية: وإلا، تحقق من قيمة العقدة الحالية، وأرجع القيمة "صحيح" إذا تطابقت، وإلا فقم بالتكرار على الأبناء.
في حالة قصر الدائرة، يكون هذا بدلاً من ذلك:
- تحقق من قيمة العقدة الحالية، وأرجع القيمة "صحيح" إذا تطابقت.
- وإلا، فعلى الأطفال، إذا لم يكن فارغًا، فقم بالتكرار.
فيما يتعلق بالخطوات القياسية، ينقل هذا التحقق من الحالة الأساسية قبل الخطوة التكرارية. أو يمكن اعتبار هذه الخطوات شكلاً مختلفاً من التحقق من الحالة الأساسية والخطوة التكرارية، على التوالي. تجدر الإشارة إلى أن هذا يتطلب دالة وسيطة للتعامل مع حالة كون الشجرة نفسها فارغة (العقدة الجذرية تساوي Null).
في حالة الشجرة الثنائية المثالية ذات الارتفاع h، يوجد 2 h +1 − 1 عقدة و 2 h +1 مؤشر فارغ كأبناء (2 لكل ورقة من أوراق 2 h )، لذا فإن الاختصار يقلل عدد استدعاءات الوظائف إلى النصف في أسوأ الحالات.
في لغة C، يمكن تنفيذ الخوارزمية التكرارية القياسية على النحو التالي:
bool tree_contains ( struct BinaryTree * node , int i ) { if ( ! node ) { return false ; // الحالة الأساسية } else if ( node- > data == i ) { return true ; } else { return tree_contains ( node- > left , i ) || tree_contains ( node- > right , i ); } }يمكن تنفيذ الخوارزمية المختصرة على النحو التالي:
#include <assert.h>// دالة تغليف للتعامل مع الشجرة الفارغة bool tree_contains ( struct BinaryTree * node , int i ) { if ( ! node ) { return false ; // شجرة فارغة } else { return tree_contains_aux ( node , i ); // استدعاء الدالة المساعدة } }// يفترض أن node != NULL bool tree_contains_aux ( struct BinaryTree * node , int i ) { assert ( node ); if ( node -> data == i ) { return true ; // تم العثور على العنصر } else { // استدعاء الدالة بشكل متكرر return ( node -> left && tree_contains_aux ( node -> left , i )) || ( node -> right && tree_contains_aux ( node -> right , i )); } }لاحظ استخدام التقييم المختصر لمعاملات الربط المنطقية && ، بحيث يتم استدعاء الدالة التكرارية فقط إذا كانت العقدة صالحة (غير فارغة). لاحظ أنه بينما يمثل الحد الأول في AND مؤشرًا إلى عقدة، فإن الحد الثاني قيمة منطقية، وبالتالي فإن التعبير الكلي يُقيّم إلى قيمة منطقية. هذا أسلوب شائع في التكرار المختصر. هذا بالإضافة إلى التقييم المختصر لمعامل الربط المنطقي ||، للتحقق من الابن الأيمن فقط في حالة فشل الابن الأيسر. في الواقع، يمكن استبدال مسار التحكم الكامل لهذه الدوال بتعبير منطقي واحد في عبارة الإرجاع، ولكن هذا يؤثر على وضوح الكود دون أي فائدة من حيث الكفاءة.
خوارزمية هجينة
غالبًا ما تكون الخوارزميات التكرارية غير فعّالة مع البيانات الصغيرة، نظرًا لتكلفة استدعاءات الدوال المتكررة وعمليات الإرجاع. لهذا السبب، تبدأ التطبيقات الفعّالة للخوارزميات التكرارية عادةً بالخوارزمية التكرارية نفسها، ثم تنتقل إلى خوارزمية أخرى عندما تصبح البيانات صغيرة. ومن الأمثلة المهمة على ذلك فرز الدمج ، الذي يُنفّذ غالبًا بالتحول إلى فرز الإدراج غير التكراري عندما تكون البيانات صغيرة بما يكفي، كما في فرز الدمج المُجزّأ . ويمكن تحسين الخوارزميات التكرارية الهجينة بشكل أكبر، كما في Timsort ، المشتقة من فرز دمج/إدراج هجين.
الاستدعاء الذاتي مقابل التكرار
التكرار والتكرار العكسي متساويان في التعبير: يمكن استبدال التكرار العكسي بالتكرار العكسي مع مكدس استدعاءات صريح ، بينما يمكن استبدال التكرار العكسي بالتكرار العكسي النهائي . يعتمد اختيار الأسلوب الأمثل على المشكلة قيد الدراسة ولغة البرمجة المستخدمة. في البرمجة الإجرائية ، يُفضل التكرار العكسي، خاصةً للتكرار العكسي البسيط، لأنه يتجنب عبء استدعاءات الدوال وإدارة مكدس الاستدعاءات، بينما يُستخدم التكرار العكسي عادةً للتكرار العكسي المتعدد. في المقابل، في لغات البرمجة الوظيفية ، يُفضل التكرار العكسي، حيث يؤدي تحسين التكرار العكسي النهائي إلى تقليل العبء الإضافي. قد لا يكون تنفيذ خوارزمية باستخدام التكرار العكسي أمرًا سهلاً.
قارن القوالب لحساب x n المعرفة بواسطة x n = f(n, x n-1 ) من الأساس x :
دالة تكرارية (n) إذا كان n يساوي الأساس، فأرجع x الأساس، وإلا فأرجع f(n, تكرارية (n - 1)) | دالة تكرارية (ن) x = x الأساس لـ i = الأساس + 1 إلى n x = f(i, x) إرجاع x |
بالنسبة للغة الإجرائية، فإن العبء الإضافي هو تعريف الدالة، وبالنسبة للغة الوظيفية، فإن العبء الإضافي هو تعريف متغير التجميع x.
على سبيل المثال، يمكن تنفيذ دالة المضروب بشكل تكراري في لغة C عن طريق تعيين متغير فهرس الحلقة ومتغير المُجمِّع، بدلاً من تمرير الوسائط وإرجاع القيم عن طريق الاستدعاء الذاتي:
دالة حساب المضروب ( عدد صحيح غير مُوقّع n ) { عدد صحيح غير مُوقّع product = 1 ; // الناتج الفارغ يساوي 1 while ( n > 0 ) { product * = n ; --n ; } return product ; }القدرة التعبيرية
تتيح معظم لغات البرمجة المستخدمة اليوم تحديد الدوال والإجراءات التكرارية بشكل مباشر. عند استدعاء دالة من هذا النوع، يتتبع بيئة تشغيل البرنامج مختلف حالات الدالة (غالبًا باستخدام مكدس الاستدعاءات ، مع إمكانية استخدام طرق أخرى). يمكن تحويل أي دالة تكرارية إلى دالة تكرارية عن طريق استبدال الاستدعاءات التكرارية ببنى تحكم تكرارية ومحاكاة مكدس الاستدعاءات باستخدام مكدس يديره البرنامج بشكل صريح. [ 17 ] [ 18 ]
على النقيض، يمكن التعبير عن جميع الدوال والإجراءات التكرارية التي يمكن تقييمها بواسطة الحاسوب (انظر اكتمال تورينج ) باستخدام الدوال التكرارية؛ حيث تُعاد كتابة بنيات التحكم التكرارية، مثل حلقات while و for، بشكل روتيني في شكل تكراري في اللغات الوظيفية . [ 19 ] [ 20 ] مع ذلك، يعتمد هذا التعديل عمليًا على حذف استدعاءات الذيل ، وهي ميزة غير متوفرة في جميع اللغات. تُعد لغات C و Java و Python من أبرز اللغات الشائعة التي قد تتسبب فيها جميع استدعاءات الدوال، بما في ذلك استدعاءات الذيل ، في تخصيص مساحة في مكدس الاستدعاءات، وهو ما لا يحدث عند استخدام بنيات الحلقات؛ في هذه اللغات، قد يتجاوز برنامج تكراري يعمل، تمت إعادة كتابته بشكل تكراري، سعة مكدس الاستدعاءات، على الرغم من أن حذف استدعاءات الذيل قد لا يكون ميزة مشمولة في مواصفات اللغة، وقد تختلف تطبيقات اللغة نفسها في قدرات حذف استدعاءات الذيل.
مشاكل الأداء
في اللغات (مثل C و Java ) التي تفضل بنيات التكرار الحلقي، عادة ما تكون هناك تكلفة كبيرة من حيث الوقت والمساحة مرتبطة بالبرامج المتكررة، وذلك بسبب الحمل الزائد المطلوب لإدارة المكدس والبطء النسبي لاستدعاءات الدوال؛ في اللغات الوظيفية ، عادة ما يكون استدعاء الدالة (وخاصة استدعاء الذيل ) عملية سريعة جدًا، وعادة ما يكون الفرق أقل وضوحًا.
كمثال عملي، يعتمد الفرق في الأداء بين التنفيذ التكراري والتنفيذ الاستدعائي لمثال "المضروب" المذكور أعلاه بشكل كبير على المُصرّف المُستخدم. في اللغات التي تُفضّل استخدام حلقات التكرار، قد يكون الإصدار الاستدعائي أسرع بعدة مراتب من الإصدار الاستدعائي. أما في اللغات الوظيفية، فقد يكون الفرق الزمني الإجمالي بين التنفيذين ضئيلاً؛ بل إن تكلفة ضرب الأعداد الأكبر أولاً بدلاً من الأعداد الأصغر (وهو ما يفعله الإصدار الاستدعائي المذكور هنا) قد تفوق أي وقت يتم توفيره باختيار الاستدعاء الاستدعائي.
مساحة التخزين
في بعض لغات البرمجة، يكون الحد الأقصى لحجم مكدس الاستدعاءات أقل بكثير من المساحة المتاحة في الذاكرة الديناميكية ، وتميل الخوارزميات التكرارية إلى استهلاك مساحة أكبر في المكدس مقارنةً بالخوارزميات التكرارية. ونتيجةً لذلك، تفرض هذه اللغات أحيانًا حدًا أقصى لعمق الاستدعاء لتجنب تجاوز سعة المكدس ؛ وبايثون إحدى هذه اللغات. [ 21 ] لاحظ التحذير الوارد أدناه بشأن الحالة الخاصة للاستدعاء الذيل .
وهن
نظرًا لأن الخوارزميات التكرارية قد تتعرض لتجاوز سعة المكدس، فقد تكون عرضة للمدخلات غير الطبيعية أو الخبيثة . [ 22 ] تستهدف بعض البرامج الضارة مكدس استدعاءات البرنامج تحديدًا، وتستغل طبيعته التكرارية. [ 23 ] حتى في غياب البرامج الضارة، قد يكون تجاوز سعة المكدس الناتج عن التكرار غير المحدود قاتلًا للبرنامج، وقد لا تمنع آلية معالجة الاستثناءات إنهاء العملية المعنية . [ 24 ]
مسائل التكرار المضاعف
تُعدّ المسائل التكرارية المتعددة تكرارية بطبيعتها، نظرًا لحاجتها إلى تتبع الحالة السابقة. ومن الأمثلة على ذلك اجتياز الشجرة كما في البحث العميق أولًا ؛ فعلى الرغم من استخدام كلٍّ من الطرق التكرارية والتكرارية [ 25 ] ، إلا أنها تختلف عن اجتياز القائمة والبحث الخطي فيها، وهو طريقة تكرارية أحادية، وبالتالي فهي بطبيعتها تكرارية. ومن الأمثلة الأخرى خوارزميات فرق تسد مثل الفرز السريع ، ودوال مثل دالة أكرمان . يمكن تنفيذ جميع هذه الخوارزميات تكراريًا باستخدام مكدس صريح ، ولكن الجهد الذي يبذله المبرمج في إدارة المكدس، وتعقيد البرنامج الناتج، قد يفوقان أي مزايا للحل التكراري.
إعادة هيكلة الاستدعاء الذاتي
يمكن استبدال الخوارزميات التكرارية بنظيراتها غير التكرارية. [ 26 ] إحدى طرق استبدال الخوارزميات التكرارية هي محاكاتها باستخدام ذاكرة الكومة بدلاً من ذاكرة المكدس . [ 27 ] وثمة بديل آخر يتمثل في تطوير خوارزمية بديلة تعتمد كلياً على أساليب غير تكرارية، وهو ما قد يكون صعباً. [ 28 ] على سبيل المثال، كانت الخوارزميات التكرارية لمطابقة الأحرف البديلة ، مثل خوارزمية wildmat لريتش سالز ، [ 29 ] شائعة في السابق. وقد طُوّرت خوارزميات غير تكرارية لنفس الغرض، مثل خوارزمية كراوس لمطابقة الأحرف البديلة ، لتجنب عيوب التكرار [ 30 ] ، ولم تتحسن إلا تدريجياً بالاعتماد على تقنيات مثل جمع الاختبارات وتحليل الأداء . [ 31 ]
الدوال التكرارية الذيلية
الدوال التكرارية الذيلية هي دوال تكون فيها جميع الاستدعاءات التكرارية استدعاءات ذيلية ، وبالتالي لا تُنشئ أي عمليات مؤجلة. على سبيل المثال، دالة القاسم المشترك الأكبر (الموضحة أدناه) هي دالة تكرارية ذيلية. في المقابل، دالة المضروب (الموضحة أدناه أيضًا) ليست دالة تكرارية ذيلية؛ لأن استدعاءها التكراري ليس في موضع الذيل، فإنها تُنشئ عمليات ضرب مؤجلة يجب تنفيذها بعد اكتمال الاستدعاء التكراري الأخير. مع مُصرّف أو مُفسّر يتعامل مع الاستدعاءات التكرارية الذيلية على أنها قفزات وليست استدعاءات دوال، فإن دالة تكرارية ذيلية مثل دالة القاسم المشترك الأكبر ستُنفّذ باستخدام مساحة ثابتة. وبالتالي، يكون البرنامج تكراريًا بشكل أساسي، وهو ما يُعادل استخدام هياكل التحكم في لغة البرمجة الإجرائية مثل حلقات "for" و"while".
| الاستدعاء الذاتي الذيل : | تعزيز التكرار: |
|---|---|
/** * @brief يحسب القاسم المشترك الأكبر باستخدام خوارزمية إقليدس * @param x عدد صحيح * @param y عدد صحيح، بحيث يكون x ≥ y ≥ 0 * @returns القاسم المشترك الأكبر لـ x و y */ int gcd ( int x , int y ) { if ( y == 0 ) { return x ; } else { return gcd ( y , x % y ); } } | /** * @brief يحسب مضروب عدد صحيح غير سالب * @param n عدد صحيح * @returns مضروب n */ unsigned int factorial ( unsigned int n ) { if ( n == 0 ) { return 1 ; } else { return n * factorial ( n - 1 ); } } |
تكمن أهمية الاستدعاء الذيل في أنه عند إجراء استدعاء ذيلي (أو أي استدعاء ذيلي)، لا يلزم حفظ موضع العودة الخاص بالدالة المستدعِية في مكدس الاستدعاءات ؛ فعندما يعود الاستدعاء الذيل، سينتقل مباشرةً إلى موضع العودة المحفوظ مسبقًا. لذلك، في اللغات التي تتعرف على هذه الخاصية للاستدعاءات الذيلية، يوفر الاستدعاء الذيل المساحة والوقت.
ترتيب التنفيذ
لنأخذ هاتين الدالتين كمثال:
الوظيفة 1
#include <stdio.h>void recursiveFunction ( int num ) { printf ( "%d \n " , num ); if ( num < 4 ) { recursiveFunction ( num + 1 ); } }![]()
الوظيفة 2
#include <stdio.h>void recursiveFunction ( int num ) { if ( num < 4 ) { recursiveFunction ( num + 1 ); } printf ( "%d \n " , num ); }![]()
إن ناتج الدالة 2 هو ناتج الدالة 1 مع تبديل الأسطر.
في حالة استدعاء دالة لنفسها مرة واحدة فقط، تُنفَّذ التعليمات الموضوعة قبل الاستدعاء التكراري مرة واحدة لكل تكرار قبل أي من التعليمات الموضوعة بعد الاستدعاء التكراري. وتُنفَّذ هذه التعليمات الأخيرة بشكل متكرر بعد الوصول إلى الحد الأقصى للتكرار.
لاحظ أيضًا أن ترتيب عبارات الطباعة معكوس، وهذا يرجع إلى طريقة تخزين الدوال والعبارات في مكدس الاستدعاءات .
الإجراءات المتكررة
مضروب
من الأمثلة الكلاسيكية على الإجراءات المتكررة الدالة المستخدمة لحساب مضروب عدد طبيعي :
| الشفرة الزائفة (التكرارية): |
|---|
دالة المضروب هي: المدخلات : عدد صحيح n بحيث يكون n ≥ 0، المخرجات : [ n × ( n - 1) × ( n - 2) × ... × 1] . 1. إذا كان n يساوي 0، تُرجع الدالة 1. 2. وإلا، فأرجع [ ن × مضروب( ن - 1)] نهاية المضروب |
يمكن أيضًا كتابة الدالة كعلاقة تكرارية :
يوضح هذا التقييم لعلاقة التكرار الحساب الذي سيتم إجراؤه عند تقييم الشفرة الزائفة أعلاه:
| حساب العلاقة التكرارية لـ n = 4: |
|---|
ب ٤ = ٤ × ب ٣ = ٤ × (٣ × ب ٢ ) = 4 × (3 × (2 × b 1 )) = 4 × (3 × (2 × (1 × b 0 ))) = 4 × (3 × (2 × (1 × 1))) = 4 × (3 × (2 × 1)) = 4 × (3 × 2) = 4 × 6 = 24 |
يمكن أيضًا وصف دالة المضروب هذه دون استخدام الاستدعاء الذاتي من خلال الاستفادة من بنيات التكرار النموذجية الموجودة في لغات البرمجة الإجرائية:
| الشفرة الزائفة (التكرارية): |
|---|
دالة المضروب هي: المدخلات : عدد صحيح n بحيث يكون n ≥ 0، المخرجات : [ n × ( n - 1) × ( n - 2) × ... × 1] . 1. أنشئ متغيرًا جديدًا باسم running_total بقيمة 1. 2. ابدأ الحلقة. 1. إذا كانت قيمة n تساوي 0، فاخرج من الحلقة 2. اضبط قيمة running_total على ( running_total × n ) 3. إنقاص قيمة n 4. تكرار الحلقة 3. إرجاع المجموع الكلي نهاية المضروب |
الكود الإجرائي أعلاه يعادل هذا التعريف الرياضي باستخدام متغير تراكمي t :
يمكن ترجمة التعريف أعلاه بشكل مباشر إلى لغات البرمجة الوظيفية مثل Scheme ؛ وهذا مثال على التكرار الذي يتم تنفيذه بشكل متكرر.
القاسم المشترك الأكبر
يمكن كتابة خوارزمية إقليدس ، التي تحسب القاسم المشترك الأكبر لعددين صحيحين، بشكل متكرر.
تعريف الدالة :
| الشفرة الزائفة (التكرارية): |
|---|
دالة gcd هي: المدخلات : عدد صحيح x ، عدد صحيح y بحيث x > 0 و y >= 0. 1. إذا كان y يساوي 0، فأرجع x. 2. وإلا، فأرجع [gcd( y , (باقي قسمة x على y ))]. نهاية gcd |
العلاقة التكرارية للقاسم المشترك الأكبر، حيثيعبّر عن الباقي من:
- لو
| حساب العلاقة التكرارية لـ x = 27 و y = 9: |
|---|
القاسم المشترك الأكبر (27، 9) = القاسم المشترك الأكبر (9، 27% 9) = القاسم المشترك الأكبر (9، 0) = 9 |
| حساب العلاقة التكرارية لـ x = 111 و y = 259: |
جي سي دي (111، 259) = جي سي دي (259، 111% 259) = القاسم المشترك الأكبر (259، 111) = القاسم المشترك الأكبر (111، 259% 111) = القاسم المشترك الأكبر (111، 37) = القاسم المشترك الأكبر (37، 111% 37) = القاسم المشترك الأكبر (37، 0) = 37 |
البرنامج التكراري أعلاه هو برنامج تكراري ذيلي ؛ وهو مكافئ لخوارزمية تكرارية، وتُظهر العملية الحسابية أعلاه خطوات التقييم التي ستُنفذها لغة برمجة تُزيل الاستدعاءات الذيلية. فيما يلي نسخة من الخوارزمية نفسها باستخدام التكرار الصريح، وهي مناسبة للغة برمجة لا تُزيل الاستدعاءات الذيلية. من خلال الاحتفاظ بحالته بالكامل في المتغيرين x و y واستخدام بنية تكرارية، يتجنب البرنامج إجراء استدعاءات تكرارية وتوسيع مكدس الاستدعاءات.
| الشفرة الزائفة (التكرارية): |
|---|
دالة القاسم المشترك الأكبر (gcd) هي: المدخلات : عدد صحيح x ، عدد صحيح y بحيث يكون x ≥ y و y ≥ 0. 1. أنشئ متغيرًا جديدًا باسم " الباقي". 2. ابدأ الحلقة. 1. إذا كانت قيمة y تساوي صفرًا، فاخرج من الحلقة. 2. اجعل الباقي هو باقي قسمة x على y 3. اجعل قيمة x تساوي قيمة y ٤. اجعل قيمة y هي الباقي. ٥. كرر الحلقة. ٣. أرجع قيمة x. نهاية القاسم المشترك الأكبر. |
تتطلب الخوارزمية التكرارية متغيرًا مؤقتًا، وحتى مع معرفة خوارزمية إقليدس، يصعب فهم العملية بمجرد الفحص البسيط، على الرغم من أن الخوارزميتين متشابهتان جدًا في خطواتهما.
أبراج هانوي

أبراج هانوي لغز رياضي يوضح حله مفهوم الاستدعاء الذاتي. [ 32 ] [ 33 ] توجد ثلاثة أوتاد يمكنها حمل أكوام من الأقراص ذات الأقطار المختلفة. لا يمكن وضع قرص أكبر فوق قرص أصغر. بدءًا من n قرصًا على وتد واحد، يجب نقلها إلى وتد آخر واحدًا تلو الآخر. ما هو أقل عدد من الخطوات اللازمة لنقل الكومة؟
تعريف الدالة :
العلاقة التكرارية لهانوي :
| حساب العلاقة التكرارية لـ n = 4: |
|---|
هانوي(4) = 2×هانوي(3) + 1 = 2×(2×هانوي(2) + 1) + 1 = 2×(2×(2×هانوي(1) + 1) + 1) + 1 = 2×(2×(2×1 + 1) + 1) + 1 = 2×(2×(3) + 1) + 1 = 2×(7) + 1 = 15 |
أمثلة على التطبيقات:
| الشفرة الزائفة (التكرارية): |
|---|
دالة hanoi هي: المدخلات : عدد صحيح n ، بحيث يكون n >= 1. 1. إذا كان n يساوي 1 ، فأرجع 1. 2. أرجع [2 * [ استدعاء hanoi(n-1)] + 1] نهاية hanoi |
على الرغم من أن ليس لكل الدوال التكرارية حل صريح، إلا أنه يمكن اختزال متتالية برج هانوي إلى صيغة صريحة. [ 34 ]
| صيغة صريحة لأبراج هانوي: |
|---|
h 1 = 1 = 2 1 - 1 h 2 = 3 = 2 2 - 1 h 3 = 7 = 2 3 - 1 h 4 = 15 = 2 4 - 1 h 5 = 31 = 2 5 - 1 ح 6 = 63 = 2 6 - 1 h 7 = 127 = 2 7 - 1 على العموم: h n = 2 n - 1، لجميع قيم n ≥ 1 |
البحث الثنائي
خوارزمية البحث الثنائي هي طريقة للبحث في مصفوفة مرتبة عن عنصر واحد، وذلك بتقسيم المصفوفة إلى نصفين في كل دورة تكرارية. يكمن سرّها في اختيار نقطة منتصف قريبة من مركز المصفوفة، ومقارنة البيانات عند تلك النقطة بالبيانات المراد البحث عنها، ثم الاستجابة لأحد الشروط الثلاثة التالية: وجود البيانات عند نقطة المنتصف، أو كون البيانات عند نقطة المنتصف أكبر من البيانات المراد البحث عنها، أو كون البيانات عند نقطة المنتصف أصغر من البيانات المراد البحث عنها.
يُستخدم التكرار في هذه الخوارزمية لأنه مع كل دورة، يتم إنشاء مصفوفة جديدة بتقسيم المصفوفة القديمة إلى نصفين. ثم تُستدعى عملية البحث الثنائي بشكل تكراري، هذه المرة على المصفوفة الجديدة (الأصغر حجمًا). عادةً ما يتم تعديل حجم المصفوفة بتغيير فهرس البداية والنهاية. تُظهر الخوارزمية نموًا لوغاريتميًا لأنها تقسم مجال المشكلة إلى نصفين مع كل دورة.
مثال على تطبيق البحث الثنائي في لغة C:
/** * @brief استدعاء دالة binary_search مع الشروط الابتدائية المناسبة. * @param data مصفوفة من الأعداد الصحيحة مرتبة تصاعديًا * @param target العدد الصحيح المراد البحث عنه * @param count العدد الإجمالي للعناصر في المصفوفة * @returns نتيجة دالة binary_search */ int search ( int data [], int target , int count ) { // Start = 0 (مؤشر البداية) // End = count - 1 (مؤشر النهاية) return binary_search ( data , target , 0 , count - 1 ); }/** * @brief خوارزمية البحث الثنائي. * @param data مصفوفة من الأعداد الصحيحة مرتبة تصاعديًا * @param target العدد الصحيح المراد البحث عنه * @param start أصغر فهرس في المصفوفة * @param end أكبر فهرس في المصفوفة * @returns موضع العدد الصحيح المراد البحث عنه ضمن مصفوفة البيانات، -1 إذا لم يتم العثور عليه */ int binary_search ( int data [], int target , int start , int end ) { // الحصول على نقطة المنتصف. int mid = start + ( end - start ) / 2 ; // قسمة عدديةإذا كانت قيمة `start` أكبر من ` end` ، فسيتم إرجاع القيمة -1. وإلا ، إذا كانت قيمة ` data [ mid ] ` تساوي قيمة `target` ، فسيتم إرجاع قيمة ` mid` . وإلا ، إذا كانت قيمة ` data [ mid ] ` أكبر من قيمة ` target` ، فسيتم البحث في النصف السفلي من البيانات باستخدام الدالة ` binary_search` . وإلا ، إذا كانت قيمة ` data [ mid ] ` أصغر من قيمة ` target` ، فسيتم البحث في النصف العلوي من البيانات باستخدام الدالة ` binary_search` .هياكل البيانات المتكررة (التكرار الهيكلي)
يُعدّ تعريف هياكل البيانات الديناميكية ، مثل القوائم والأشجار ، أحد أهم تطبيقات الاستدعاء الذاتي في علوم الحاسوب. إذ يمكن لهياكل البيانات الاستدعائية أن تنمو ديناميكيًا إلى حجم غير محدود نظريًا استجابةً لمتطلبات وقت التشغيل؛ في المقابل، يجب تحديد حجم المصفوفة الثابتة في وقت الترجمة.
"تعتبر الخوارزميات المتكررة مناسبة بشكل خاص عندما يتم تعريف المشكلة الأساسية أو البيانات المراد معالجتها بعبارات متكررة." [ 35 ]
توضح الأمثلة الواردة في هذا القسم ما يُعرف باسم "التكرار الهيكلي". ويشير هذا المصطلح إلى حقيقة أن الإجراءات التكرارية تعمل على بيانات مُعرَّفة بشكل تكراري.
طالما أن المبرمج يستمد القالب من تعريف البيانات، فإن الدوال تستخدم الاستدعاء الذاتي البنيوي. أي أن الاستدعاءات الذاتية في جسم الدالة تستهلك جزءًا مباشرًا من قيمة مركبة معينة. [ 15 ]
القوائم المرتبطة
فيما يلي تعريف بلغة C لبنية عقدة قائمة مرتبطة. لاحظ تحديدًا كيف تُعرَّف العقدة بدلالة نفسها. العنصر "next" LinkedListهو مؤشر إلى قائمة مرتبطة أخرى، مما يُنشئ فعليًا نوع قائمة.
struct LinkedList { int data ; // بعض البيانات العددية struct LinkedList * next ; // مؤشر إلى عقدة أخرى في القائمة المرتبطة };نظرًا لأن بنية بيانات العقدة struct تُعرَّف بشكل تكراري، يمكن تنفيذ الإجراءات التي تعمل عليها بشكل طبيعي كإجراءات تكرارية. يقوم الإجراء list_print المُعرَّف أدناه بالمرور على عناصر القائمة حتى تصبح فارغة (أي أن مؤشر القائمة يُساوي NULL). لكل عقدة، يطبع عنصر البيانات (عددًا صحيحًا). في تطبيق لغة C، تبقى القائمة دون تغيير بعد تنفيذ الإجراء list_print .
void list_print ( struct LinkedList * list ) { // الحالة الأساسية if ( list ) { printf ( "%d " , list -> data ); // طباعة البيانات العددية متبوعة بمسافة list_print ( list -> next ); // استدعاء متكرر على العقدة التالية } }الأشجار الثنائية
فيما يلي تعريف بسيط لعقدة الشجرة الثنائية. وكما هو الحال مع عقدة القوائم المتصلة، تُعرَّف هذه العقدة بدلالة نفسها، بشكل تكراري. يوجد مؤشران مرجعيان ذاتيان: الأيسر (يشير إلى الشجرة الفرعية اليسرى) والأيمن (يشير إلى الشجرة الفرعية اليمنى).
struct BinaryTree { int data ; // بعض البيانات العددية struct BinaryTree * left ; // مؤشر إلى الشجرة الفرعية اليسرى struct BinaryTree * right ; // مؤشر إلى الشجرة الفرعية اليمنى };يمكن تنفيذ العمليات على الشجرة باستخدام الاستدعاء الذاتي. لاحظ أنه نظرًا لوجود مؤشرين يشيران إلى نفس العنصر (يسار ويمين)، فقد تتطلب عمليات الشجرة استدعاءين ذاتيين.
// اختبار ما إذا كانت العقدة tree_node تحتوي على i؛ إرجاع 1 إذا كانت كذلك، و0 إذا لم تكن كذلك. int tree_contains ( struct BinaryTree * node , int i ) { if ( ! node ) { return 0 ; // الحالة الأساسية } else if ( node -> data == i ) { return 1 ; } else { return tree_contains ( node -> left , i ) || tree_contains ( node -> right , i ); } }سيتم إجراء استدعاءين متكررين على الأكثر لأي استدعاء معين للدالة tree_contains كما هو محدد أعلاه.
// اجتياز الترتيب الداخلي: void tree_print ( struct BinaryTree * node ) { // الحالة الأساسية if ( node ) { tree_print ( node -> left ); // الانتقال إلى اليسار printf ( "%d " , node -> data ); // طباعة العدد الصحيح متبوعًا بمسافة tree_print ( node -> right ); // الانتقال إلى اليمين } }يوضح المثال أعلاه عملية اجتياز الشجرة الثنائية بترتيبها الصحيح . شجرة البحث الثنائية هي حالة خاصة من الشجرة الثنائية حيث تكون عناصر البيانات في كل عقدة مرتبة.
اجتياز نظام الملفات
بما أن عدد الملفات في نظام الملفات قد يختلف، فإن الاستدعاء الذاتي هو الطريقة العملية الوحيدة لاجتيازه وبالتالي تعداد محتوياته. يُشبه اجتياز نظام الملفات إلى حد كبير اجتياز الشجرة ، لذا فإن المفاهيم الكامنة وراء اجتياز الشجرة قابلة للتطبيق على اجتياز نظام الملفات. وبشكل أكثر تحديدًا، يُعد الكود أدناه مثالًا على اجتياز نظام الملفات بترتيب ما قبل الترتيب .
package org.wikipedia.examples ;استيراد java.io.File ؛public class Example { /** * الحصول على جذور نظام الملفات * المتابعة في اجتياز نظام الملفات بشكل متكرر */ private static void traverse () { File [] fs = File . listRoots (); for ( int i = 0 ; i < fs . length ; i ++ ) { System . out . println ( fs [ i ] ); if ( fs [ i ] . isDirectory () && fs [ i ] . canRead ()) { rtraverse ( fs [ i ] ); } } }/** * اجتياز دليل معين بشكل متكرر * * @param fd يشير إلى نقطة بداية الاجتياز */ private static void rtraverse ( File fd ) { File [] fss = fd . listFiles ();for ( int i = 0 ; i < fss.length ; i ++ ) { System.out.println ( fss [ i ] ) ; if ( fss [ i ] .isDirectory ( ) && fss [ i ] .canRead ( ) ) { rtraverse ( fss [ i ] ) ; } } }public static void main ( String [] args ) { traverse (); } }هذا الكود هو عبارة عن تكرار وتكرار - يتم تكرار الملفات والمجلدات، ويتم فتح كل مجلد بشكل متكرر.
تُعد طريقة "rtraverse" مثالاً على الاستدعاء الذاتي المباشر، بينما تُعد طريقة "traverse" دالة تغليف.
السيناريو "الأساسي" هو أنه سيكون هناك دائمًا عدد ثابت من الملفات و/أو الدلائل في نظام ملفات معين.
كفاءة الوقت للخوارزميات المتكررة
يمكن التعبير عن كفاءة الوقت للخوارزميات التكرارية بعلاقة تكرارية باستخدام ترميز Big O. ويمكن (عادةً) تبسيطها إلى حد واحد من Big O.
قاعدة الاختصار (النظرية الرئيسية)
إذا كان التعقيد الزمني للدالة على الصورة
إذن، فإنّ قيمة Big O للتعقيد الزمني هي كالتالي:
- لولبعض الثوابت، ثم
- لو، ثم
- لولبعض الثوابتوإذابالنسبة لثابت ما c < 1 ولجميع قيم n الكبيرة بما فيه الكفاية ، فإن
حيث يمثل a عدد الاستدعاءات المتكررة في كل مستوى من مستويات التكرار، ويمثل b العامل الأصغر الذي يكون به المدخل للمستوى التالي من التكرار (أي عدد الأجزاء التي تقسم إليها المشكلة)، ويمثل f ( n ) العمل الذي تقوم به الدالة بشكل مستقل عن أي تكرار (مثل التقسيم، إعادة التجميع) في كل مستوى من مستويات التكرار.
الاستدعاء الذاتي في البرمجة المنطقية
في التفسير الإجرائي لبرامج المنطق ، تُعامل البنود (أو القواعد) من الشكل كإجراءات، تُختزل أهداف الشكل إلى أهداف فرعية منه . على سبيل المثال، بنود لغة برولوج :A:-BAB
path ( X , Y ) :- arc ( X , Y ). path ( X , Y ) :- arc ( X , Z ), path ( Z , Y ).يُعرّف إجراءً يُستخدم للبحث عن مسار من X إلى Y ، إما بإيجاد قوس مباشر من X إلى Y ، أو بإيجاد قوس من X إلى Z ، ثم البحث بشكل متكرر عن مسار من Z إلى Y. يُنفّذ Prolog هذا الإجراء بالاستدلال من الأعلى إلى الأسفل (أو عكسيًا ) والبحث في فضاء المسارات الممكنة بحثًا عميقًا أولًا، فرعًا تلو الآخر. إذا حاول الخيار الثاني، وفشل نهائيًا في إيجاد مسار من Z إلى Y ، فإنه يتراجع ويحاول إيجاد قوس من X إلى عقدة أخرى، ثم يبحث عن مسار من تلك العقدة الأخرى إلى Y.
مع ذلك، في القراءة المنطقية لبرامج المنطق، تُفهم العبارات بشكل تصريحي على أنها شروط كمية شاملة. على سبيل المثال، تُفهم العبارة التكرارية لإجراء البحث عن المسار على أنها تمثل المعرفة بأنه، لكل X و Y و Z ، إذا كان هناك قوس من X إلى Z ومسار من Z إلى Y، فإنه يوجد مسار من X إلى Y. بالصيغة الرمزية:
يُعفي التفسير المنطقي القارئ من الحاجة إلى معرفة كيفية استخدام العبارة لحل المشكلات. يمكن استخدام العبارة من أعلى إلى أسفل، كما في لغة برولوج، لاختزال المشكلات إلى مشكلات فرعية. أو يمكن استخدامها من أسفل إلى أعلى (أو بشكل مباشر )، كما في لغة داتالوج ، لاستخلاص النتائج من الشروط. يُعدّ هذا الفصل بين الاهتمامات شكلاً من أشكال التجريد ، الذي يفصل المعرفة التصريحية عن أساليب حل المشكلات (انظر: Algorithm#Algorithm = Logic + Control ). [ 36 ]
التكرار اللانهائي
من الأخطاء الشائعة بين المبرمجين عدم توفير طريقة للخروج من دالة تكرارية، وغالبًا ما يكون ذلك عن طريق إغفال حالة التوقف أو التحقق منها بشكل خاطئ، مما يسمح لها بالعمل (نظريًا على الأقل) إلى ما لا نهاية من خلال استدعاء نفسها بشكل تكراري لا نهائي. يُسمى هذا التكرار اللانهائي ، ولن ينتهي البرنامج أبدًا. عمليًا، يؤدي هذا عادةً إلى استنفاد مساحة المكدس المتاحة . في معظم بيئات البرمجة، لن يستمر برنامج ذو تكرار لانهائي في العمل إلى الأبد. في النهاية، سيحدث خلل ما وسيُبلغ البرنامج عن خطأ. [ 37 ] مع ذلك، إذا تم استخدام تحسين استدعاء الذيل ، فيمكن تحسين الاستدعاءات التكرارية إلى حلقة لا نهائية يمكنها العمل إلى الأبد.
فيما يلي كود جافا يستخدم التكرار اللانهائي:
public class InfiniteRecursion {static void recursive () {// دالة تكرارية بدون مخرجدالة تكرارية ();}public static void main ( String [] args ) {دالة تكرارية (); // تُنفّذ الدالة التكرارية أثناء التشغيل}}سيؤدي تشغيل هذا الكود إلى حدوث خطأ في تجاوز سعة المكدس .
انظر أيضاً
ملحوظات
- ↑ غراهام، رونالد ؛ كنوت، دونالد ؛ باتاشنيك، أورين (1990). "1: المسائل المتكررة" . الرياضيات الملموسة . أديسون-ويسلي. ISBN 0-201-55802-5.
- ↑ كحيل، محمد أ.؛ نيغريروس، جواو؛ سيفا، أحمد (2021). "تدريس التفكير التكراري باستخدام أنشطة غير متصلة بالإنترنت". المعاملات العالمية في تعليم الهندسة والتكنولوجيا . 19 (2): 169-175 .
- ↑ إيب، سوزانا (1995). الرياضيات المتقطعة مع التطبيقات ( الطبعة الثانية). شركة بي دبليو إس للنشر. ص 427. رقم ISBN 978-0-53494446-9.
- ↑ ويرث ، نيكلاوس (1976). الخوارزميات + هياكل البيانات = البرامج . برنتيس هول . ص 126. ISBN 978-0-13022418-7.
- ↑ "البرمجة الوظيفية | كلوجر للمبرمجين الشجعان والحقيقيين" . www.braveclojure.com . تاريخ الاسترجاع: ٢١ أكتوبر ٢٠٢٠ .
- 1 2 3 سواري، روبرت آي. (1996). "قابلية الحوسبة والاستدعاء الذاتي". نشرة المنطق الرمزي . 2 (3): 284-321 . doi : 10.2307/420992 . JSTOR 420992 .
- 1 2 3 4 5 6 دايلايت، إدغار ج. (2010). ظهور الاستدعاء الذاتي في البرمجة، الخمسينيات والستينيات (تقرير). سلسلة ما قبل النشر PP-2010-04. جامعة أمستردام، معهد المنطق واللغة والحوسبة.
- 1 2 3 مكارثي، جون (1960). "الدوال التكرارية للتعبيرات الرمزية وحسابها بواسطة الآلة، الجزء الأول". اتصالات ACM . 3 (4): 184-195 . doi : 10.1145/367177.367199 .
- 1 2 نور، بيتر؛ جون باكوس؛ جون مكارثي (1960). "تقرير عن لغة الخوارزميات ALGOL 60". اتصالات ACM . 3 (5): 299-314 . doi : 10.1145/367236.367262 .
- ↑ كونغ، تشينغكاي؛ سياو، تيمي؛ باين، ألكسندر م. (2021). "الاستدعاء الذاتي". برمجة بايثون والأساليب العددية . ص 105-120 . doi : 10.1016/B978-0-12-819549-9.00015-4 . ISBN 978-0-12-819549-9.
- 1 2 3 سويغارت، آل (2022). كتاب الاستدعاء الذاتي: اجتياز مقابلة البرمجة بنجاح باستخدام بايثون وجافا سكريبت . دار نشر نو ستارش. ISBN 978-1-7185-0202-4.
- ↑ هوبكروفت، جون؛ تارجان، روبرت (يونيو 1973). "الخوارزمية 447: خوارزميات فعالة لمعالجة الرسوم البيانية". اتصالات ACM . 16 (6): 372-378 . doi : 10.1145/362248.362272 .
- ↑ برون، سي. (مايو 1972). "الخوارزمية 426: خوارزمية فرز الدمج [M1]". اتصالات ACM . 15 (5): 357-358 . doi : 10.1145/355602.361317 .
- ^ فيليسن وآخرون. 2001 ، المادة الخامسة "العودة التوليدية
- 1 2 فيليسين، ماتياس (2002). "تطوير برامج الويب التفاعلية" . في: جورينغ، يوهان (محرر). البرمجة الوظيفية المتقدمة: المدرسة الدولية الرابعة (ملف PDF) . سبرينغر. ص 108. ISBN 9783540448334.
- ↑ مونغان، جون ؛ جيغير، إريك؛ كيندلر، نوح (2013). مقابلات البرمجة مكشوفة: أسرار الحصول على وظيفتك التالية ( الطبعة الثالثة). وايلي . ص 115. ISBN 978-1-118-26136-1.
- ↑ هيتلاند، ماغنوس لي (2010)، خوارزميات بايثون: إتقان الخوارزميات الأساسية في لغة بايثون ، أبريس، ص 79، ISBN 9781430232384.
- ^ دروزديك ، آدم (2012)، هياكل البيانات والخوارزميات في C++ (الطبعة الرابعة )، Cengage Learning، ص. 197، ردمك 9781285415017.
- ↑ شيفرز، أولين. "تشريح الحلقة - قصة النطاق والتحكم" (ملف PDF) . معهد جورجيا للتكنولوجيا . تم الاطلاع عليه بتاريخ 3 سبتمبر 2012 .
- ↑ لامدا ذا ألتيميت. "تشريح الحلقة" . لامدا ذا ألتيميت . تم الاسترجاع في 3 سبتمبر 2012 .
- ↑ "27.1. sys — معلمات ووظائف خاصة بالنظام — وثائق بايثون الإصدار 2.7.3" . Docs.python.org . تم الاطلاع عليه بتاريخ 3 سبتمبر 2012 .
- ↑ كراوس، كيرك ج. (2014). "مطابقة الأحرف البديلة: طريقة تجريبية لترويض خوارزمية" . مجلة دكتور دوبز .
- ↑ مولر، أوليفر (2012). "تشريح هجوم تحطيم المكدس وكيف يمنعه GCC" . مجلة دكتور دوبز .
- ↑ "فئة StackOverflowException" . مكتبة فئات إطار عمل .NET . شبكة مطوري مايكروسوفت . 2018.
- ↑ "البحث العميق أولاً (DFS): التنفيذ التكراري والتكراري" . Techie Delight. 2018.
- ↑ ميتروفيتش، إيفان. "استبدال الاستدعاء الذاتي بالتكرار" . ثوت ووركس .
- ↑ لا، وونغ غيو (2015). "كيفية استبدال الدوال التكرارية باستخدام المكدس وحلقة while لتجنب تجاوز سعة المكدس" . CodeProject.
- ↑ مورتيل، توم (2013). "حيل المهنة: من التكرار إلى التكرار، الجزء 2: التخلص من التكرار باستخدام خدعة الميزة السرية للسفر عبر الزمن" .
- ^ سالز ريتش (1991). "wildmat.c" . جيثب .
- ↑ كراوس، كيرك ج. (2008). "مطابقة الأحرف البديلة: خوارزمية" . مجلة دكتور دوبز .
- ↑ كراوس، كيرك ج. (2018). "مطابقة الأحرف البديلة: خوارزمية محسّنة للبيانات الضخمة" . التطوير من أجل الأداء.
- ↑ غراهام، كنوت وباتاشنيك 1990 ، §1.1: برج هانوي
- ↑ إيب 1995 ، الصفحات 427-430: برج هانوي
- ↑ إيب 1995 ، الصفحات 447-448: صيغة صريحة لمتتالية برج هانوي
- ↑ ويرث 1976 ، ص 127
- ↑ راسل، ستيوارت ج .؛ نورفيج، بيتر. (2021). الذكاء الاصطناعي: منهج حديث، §9.3، §9.4 ( الطبعة الرابعة). هوبوكين: بيرسون. ISBN 978-0134610993. إل سي سي إن 20190474 .
- ↑ "4.8. التكرار اللانهائي - كيف تفكر كعالم حاسوب - لغة C++" .
مراجع
- بارون، ديفيد ويليام (1968) [1967]. كُتب في كامبريدج، المملكة المتحدة. جيل، ستانلي (محرر). تقنيات التكرار في البرمجة . سلسلة ماكدونالد لدراسات الحاسوب ( الطبعة الأولى). لندن، المملكة المتحدة: ماكدونالد وشركاه (ناشرون) المحدودة. SBN 356-02201-3.(viii+64 صفحة)
- فيليسين، ماتياس؛ فيندلر، روبرت ب.؛ فلات، ماثيو؛ كريشنامورثي، شري رام (2001). كيفية تصميم البرامج: مقدمة في الحوسبة والبرمجة . مطبعة معهد ماساتشوستس للتكنولوجيا . ISBN 0262062186.
- روبيو-سانشيز، مانويل (2017). مقدمة في البرمجة التكرارية . دار نشر سي آر سي . رقم ISBN 978-1-351-64717-5.
- بيفاتش، إيرينا (2016). ممارسة العودية في جافا . إنشاء مساحة مستقلة. رقم ISBN 978-1-5327-1227-2.
- روبرتس، إريك (2005). التفكير التكراري باستخدام جافا . وايلي . ISBN 978-0-47170146-0.
- روهل، جيفري س. (1984). الاستدعاء الذاتي عبر باسكال . مطبعة جامعة كامبريدج . ISBN 978-0-521-26934-6.
- هيلمان، بول؛ فيروف، روبرت. الجدران والمرايا .
- أبيلسون، هارولد ؛ سوسمان، جيرالد جاي ؛ سوسمان، جولي (1996). بنية وتفسير برامج الحاسوب ( الطبعة الثانية). مطبعة معهد ماساتشوستس للتكنولوجيا . ISBN 0-262-51087-1.
- ديكسترا، إدسجر دبليو (1960). “البرمجة العودية”. الرياضيات الرقمية . 2 (1): 312-318 . دوى : 10.1007 / BF01386232 . S2CID 127891023 .
- مكارثي، جون (1960). "الدوال التكرارية للتعبيرات الرمزية وحسابها بواسطة الآلة، الجزء الأول". اتصالات رابطة آلات الحوسبة . 3 (4): 184-195 . doi : 10.1145/367177.367199 .
- نور، بيتر؛ جون باكوس؛ جون مكارثي (1960). "تقرير عن لغة الخوارزميات ALGOL 60". اتصالات ACM . 3 (5): 299-314 . doi : 10.1145/367236.367262 .
- هوار، سي إيه آر (1961). "الفرز السريع". اتصالات رابطة آلات الحوسبة . 4 (7): 321-322 . doi : 10.1145/366622.366644 .
- سواري، روبرت آي. (1996). "قابلية الحوسبة والاستدعاء الذاتي". نشرة المنطق الرمزي . 2 (3): 284-321 . doi : 10.2307/420992 . JSTOR 420992 .
- دايلايت، إدغار ج. (2010). ظهور الاستدعاء الذاتي في البرمجة، الخمسينيات والستينيات (تقرير). سلسلة ما قبل النشر PP-2010-04. جامعة أمستردام، معهد المنطق واللغة والحوسبة.
- إندريس، مادلين؛ ويستلي وايمر؛ أمير كامل (2021). "تحليل أداء المسائل التكرارية والتكرارية". وقائع الندوة الفنية الثانية والخمسين لجمعية آلات الحوسبة حول تعليم علوم الحاسوب (SIGCSE '21) . جمعية آلات الحوسبة. doi : 10.1145/3408877.3432391 .
- كورمين، توماس H.؛ تشارلز إي ليسرسون؛ رونالد ل. ريفست؛ كليفورد شتاين (2009). مقدمة للخوارزميات ( الطبعة الثالثة). مطبعة معهد ماساتشوستس للتكنولوجيا. رقم ISBN 978-0-262-03384-8.
- علوم الحاسوب النظرية
- التكرار
- نظرية الحوسبة
- مصطلحات البرمجة
- البرامج الفرعية
