الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

الأستدعاء الذاتى للدوال - Recursion

رائج
بدأه Omar Eladel في 8 فبراير 2009 · 33 رد · 24,790 مشاهدة · في المواضيع والدروس
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكــم ورحمـة الله وبركاتــه ...

الـ Recursion يعنى الإستدعاء الذاتى للدوال ، بمعنى أن الدالة تستدعى نفسها من داخل الدالة .

لذلك فإن الـ Recursion له الكثير من الفوائد ، كمثال : حساب Factorial لأى رقم و أيضاً داخل دوال الترتيب .

N! = N × (N-1) × (N-2) × ….... × 3 × 2 × 1

5! = 5 × 4 × 3 × 2 × 1 = 120

لذلك ، كتبنا هذه الدالة لحساب الـ Factorial بلغة الـ ++C :

  1.  
  2. long factorial (long a)
  3. {
  4. if (a > 1) {
  5. return ( a * factorial (a-1) ); }
  6. else {
  7. return ( 1 ); }
  8. }
  9.  
  10.  
  11.  
  12.  

مثال على إستخدام الدالة :

  1.  
  2. #include <iostream>
  3. using namespace std;
  4. int main ()
  5. {
  6. long number;
  7. cout << "Please type a number: ";
  8. cin >> number;
  9. cout << number << "! = " << factorial (number);
  10. return 0;
  11. }
  12.  
  13.  
  14.  
  15.  

نتيجة البرنامج :

post-132119-1234119573_thumb.png

لو تلاحظ ، فى دالة factorial ، قمنا بكتابة إستدعاء ذاتى للدالة ، اى الدالة قامت بمناداة نفسها من داخلها .

لو كان لا يوجد شرط داخل الدالة ( و هو هنا ان يكون الرقم أكبر من 1) ، ستقوم الدالة بإستدعاء نفسها مرات لا نهائية متتالية حتى نوقف البرنامج بالقوة مما قد يؤدى إلى أخطاء غير محدودة .

لو تلاحظ أيضاً ، أن الدالة محددة بنوع بيانات واحد و هو long و ذلك للبساطة فى البرنامج ، لذلك النتائج لن تتعدى 10! أو 15! على حسب النظام الذى تترجم البرنامج عليه .

أنتهى الدرس ،،

الدرس على ملف PDF : فى المرفقات

المرجع : Functions (II) - Recursivity

تعديل بناء على رغبة أخى بن العيد :)

Recursion.pdf

تم تعديل هذه المشاركة بواسطة Omar Eladel في 9 فبراير 2009 في 00:09

#2

ممتاز ,

llback.jpg

اشهد ان لا إله إلا الله وان محمدا ً رسول الله

#3

السلام عليكــم ورحمـة الله وبركاتــه

جيد يا عمر في إنتظار المزيد

اقتباس

مرجع و ليس مصدر لأنك لم تنسخ الأمثلة من هناك إنما فقط إستعنت به.

#4

أشكركم أخوتى time1 و بن العيد ،،

-------

"بن العيد" كتب:
مرجع و ليس مصدر لأنك لم تنسخ الأمثلة من هناك إنما فقط إستعنت به.

أغيرها ، ولا تزعل :D :D

#5

لكن تعتبر طريقة الاستدعاء الذاتي عموما اقل كفاءة من التكرار لكنها في المقابل تقلل عدد المتغيرات

موضوع جميل بارك الله فيك.

tvquran_6.gif

#6

أخى فهد ، هل تقصد بالتكرار هو إستخدام loop حلقة داخل الدالة بدلاً من إستخدام Recursion ؟

مثال دالة Factorial بإستخدام حلقة loop كما فهمت :

  1.  
  2. long double factorial(long double num)
  3. {
  4. long double total = 1;
  5. for (int counter = 1 ; counter <= num ; counter++)
  6. {
  7. total *= counter ;
  8.  
  9. }
  10.  
  11. }
  12.  

#7

نعم استخدام فور اسرع في الاداء.

tvquran_6.gif

#8

امممم موضوع لديد كالعادة

لكن اليس هدا النوع من الاستدعاء يحتوي مشاكل كثيرة كفيض المكدس مثلا

امم ان سي++ يحتوي دوال لمعالجة هده المشاكل??

ارجو ان لا يكون سؤالي غبيا :(

العمل كثير و الوقت قليل أعاننا الله

seo zen SEO Enlightment amazon danbo

#9

بالفعل أخى fkugd2003 ، من ضمن عيوب Recursion او الإستدعاء الذاتى ، هو فيض المكدس Stack Overflow ، فذلك لو يحدث لو حدث خطأ فى كود الشرط الذى يوقف حلقة الإستدعاء ، ففى الكود فى الأعلى ، لو لا يوجد الشرط ، سوف تستمر الدالة إلى ما نهاية و تظل تتناقص حتى تصل إلى الأرقام السالبة

#10

كلامك صحيح اخي عمر

حتى لو لم يكن الشرط فيه اي مشاكل و حاولنا ان نقوم بحساب رقم ضخم فان المكدس سوف يمتلأ بعناوين الدوال المطلوبة و عناوين الرجوع

يعني 653500! تكفي ان ترسل المكدس الى الجحيم :lol:

لكنني اتسائل هل توجد مكتبة في سي++ لمعالجة هدا النوع من المشاكل

العمل كثير و الوقت قليل أعاننا الله

seo zen SEO Enlightment amazon danbo

#11

أخى fkugd2003 ، هل تقصد أن حساب مثلاً 653500! سوف يحدث مشاكل فى Recursion و لا يحدث مشاكل عند حسابه بحلقة for loop عادية :D :D

انا مش فاهم نقطة ان المكدس يمتلأ بعناوين الدوال و عناوين الرجوع ، ممكن توضيح أكثر ؟

#12

حسبما اعرف فان مناداة دالة تستلزم تحميل عنوانها الى المكدس و المتغيرات التي تحتاجها احيانا ثم اضافة عنوان الرحوع

يعني المكدس سوف يمتلأ بدون اي فائدة :lol:

العمل كثير و الوقت قليل أعاننا الله

seo zen SEO Enlightment amazon danbo

#13

عظيم يا عمر , اذكر أني وعدتك بتقديم شيء عن الRecursion لكن أخلفت الوعد :-) متأسف,

عموما , أسلوبك جميل ومحبب .. ولن أكتب أفضل منك .

لكن المشكلة أحيانا , كيف تنصح من يحب ال loop باستخدام الاستدعاء الذاتي ( ومحدثكم مثال :-) ) ؟

ستظهر قوّة Recursion في بعض المسائل .. والتي يستعصي حلها .. خذ مثلا عملية استعراض محتويات شجرة ما , tree-traversal , ستجد أن حلها باستخدام الاستدعاء الذاتي أسهل بمراحل من ال loop .. هذا مثال بالجافا ( قريبة جدا من السي ) وجدته على السريع يوضح الفكرة .. يقوم باستعراض محتويات الشجرة .

	private void preorder(BTNode<T> p){
		if (p != null){
			System.out.println(p.data);
			preorder(p.left);
			preorder(p.right);
		}
	}

تم تعديل هذه المشاركة بواسطة الشمري في 9 فبراير 2009 في 01:43

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#14

انت خبير asm و أدرى منى :lol:

أعتقد ان كل مرة يتم مناداة الدالة ( و بالتالى ملئ المكدس بعنوانها و عناوين متغيراتها) يتم ملئ نفس الأماكن من المتغيرات و إلا امتلأ المكدس بـ Garbage لا فائدة منه اللهم إلا استخدام الدالة مرة واحدة .

مثال :

حساب 5!

يتم مناداة الدالة 5 مرات (صح ولا .. :D )

يتم تخزين قيم جديدة فى متغيرات جديدة

عدد الأماكن المستخدمة = عدد المتغيرات فى الدالة × عدد مرات المناداة

فى مثالنا هذا : عدد الأماكن = 1 × 5 = 5

يعنى يوجد 4 اماكن مليئة بالبيانات و ليس لها اى فائدة تذكر

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

الكلام فى الأعلى هو وجهة نظر شخصية و لا يستند على أسس علمية ، لذلك يحتمل الخطأ (95%) و يحتمل الصواب (5%) :lol:

وفقكم الله ،،

--------------

إضافة :

لم ارى ردك أخى الشمرى :)

أنا و أنت واحد :lol: :D ;)

أنا ايضاً لا أستخدم Recursion إلا نادراً ( من نفس النوع :D )

بالطبع هناك بعض الحالات يكون استخدام loop فيها صعب كما ذكرت أنت و فى هذه الحالات من الأفضل و الأدق إستخدام Recursion

لكن للأسف انا لم افهم المثال ، لأنى لا اعرف ما هى Tree Traversal التى ذكرتها :)

وفقكم الله ،،

تم تعديل هذه المشاركة بواسطة Omar Eladel في 9 فبراير 2009 في 01:48

#15
اقتباس
حساب 5!

في هدا اظن انك مخطئ اخي العزيز

في اغلب الاحيان فان الدالة عندما تنتهي يقوم النظام بتنظيف المجال الدي استعملته في المكدس

stdcall, pascal call, c call

في حالة pascal call فان المبرمج لا يهتم بالتنظيف و انما نظام التشغيل

اما c call فان المبرمج ينظف :lol:

std call مزيج من النوعين

المشكلة في النداء التراجعي ان النظام لا يمكنه ان ينضف لان الدالة مازالت لم تصل لعنوان خروجها

لدلك فهو يبقي المتغيرات الخاصة بها في المكدس مما يجسد المشكلة التي تكلمت عنها

نحن نتناقش من اجل التسلية و التعلم :lol:

حساب 5! :

حساب 5! ----> 5* حساب 4! -----> 5*4*حساب 4! ----> etc

اقتباس
يعنى يوجد 4 اماكن مليئة بالبيانات و ليس لها اى فائدة تذكر

الحقيقة يوجد اكثر من 4 :lol:

لا تنس ايضا ان مكدسنا مملوء اصلا بمعلومات عن دوال النظام المستخدمة في البرنامج

تم تعديل هذه المشاركة بواسطة fkugd2003 في 9 فبراير 2009 في 02:05

العمل كثير و الوقت قليل أعاننا الله

seo zen SEO Enlightment amazon danbo

#16
اقتباس
لكن للأسف انا لم افهم المثال ، لأنى لا اعرف ما هى Tree Traversal التى ذكرتها

مجرد المرور على محتويات الشجرة .. لطباعتها مثلا ,

بالمناسبة .. الموضوع الان في كتاب المنتدى .. ويبدو أننا (كأعضاء) قطعنا شوطا كبيرا في تغطية أغلب المواضيع المهمة في السي بلس .. حسب فهرسة الكتاب .

الحمد لله ,

تم تعديل هذه المشاركة بواسطة الشمري في 9 فبراير 2009 في 02:28

logo1.png تطبيق طمأنينة ، نسخة بيتا على أندرويد

عبدالله الشمّري - Al-Shammari

CodingAlone.com

twitter @abshammeri

abshammeri AT gmail.com

github : abshammeri

#17

السلام عليكم ,,

بارك الله فيك يا عمر :)

هناك لغات تسمى pure functional languages :wacko: ليس فيها حلقات, الحلقات تتبع التوجه المسمى structured programming

الطريقة التي اتبعها مصممو تلك اللغات هي الـ recursion لتطبيق مفهوم التكرار.

و تم استعارة هذا المفهوم في ++C/C و معظم اللغات الحديثة من ذلك التوجه, دون التخلي عن الحلقات بالطبع.

تذكر أن ما يمكن فعله عن طريق الـ recursion يمكن فعله عن طريق الحلقات :)

في الحقيقة المثال الذي طرحته أكثر من رائع لكي نفهم الـ recursion و لكنه يعتبر أسوأ مثال لتبيان محاسن هذا المفهوم :lol:

سلسة فيبوناتشي و مضروب العدد يمكن كتابتهم عن طريق الحلقات بكفاءة عالية جداً على عكس الـ recursion.

كما قال الشمري, عندما تتحدث عن الـ tree, و عمليات الإضافة والحذف و التحرك و البحث , فبضعة سطور من الكود تكفي لفعل كل هذا بالـ recursion و بدون أخطاء غالباً,

إذا أردت كتابة تلك الدوال عن طريق الحلقات فستحتاج إلى مبرمج خبير و ضربة حظ :lol: و سوف تعرف ما هي فائدة الـ recursion.

المبرمج المصري الشهير فعلها هنا :P

Fast Binary Tree Operations

عموماً الحلقات أكثر كفاءة "غالبا" و ليس دائماً, و لكن الـ recursion أبسط بمسافات ضوئية في بعض المسائل :)

تحياتي,, و شكراً على الموضوع الجميل ..

#18
اقتباس
هناك لغات تسمى pure functional languages wacko.gif ليس فيها حلقات, الحلقات تتبع التوجه المسمى structured programming

الطريقة التي اتبعها مصممو تلك اللغات هي الـ recursion لتطبيق مفهوم التكرار.

عمرك اطول من عمرى Functional Programming

الموضوع جميل ياعمر keep going

تم تعديل هذه المشاركة بواسطة ahmed_youssef في 9 فبراير 2009 في 04:28

(map share people)

فضلا لاتقم بمراسلتي من أجل أسئلة لها أقسامها في المنتدى حتى تعم الفائدة على الجميع وللحصول على إجابات أفضل من أعضاء أكثر خبرة.
Weblog
@bitbucket
@xmonader

#19
اقتباس
تذكر أن ما يمكن فعله عن طريق الـ recursion يمكن فعله عن طريق الحلقات

أعتقد أن هذا الكلام ليس صحيحاً تماماً :P عذراً على المعلومات المغلوطة :)

http://en.wikipedia.org/wiki/Ackermann_function

#20

يفضل عدم تخزين الـObjects الكبيرة او arrays في الـstack بل من الافضل حجز الذاكرة ديناميكيا واستخدام مؤشرات لها .

باختصار اذا كان الـlocal variable حجمه اكبر من حجم PVOID فقم باستخدام الـdynamic allocation .

يمكن اضافة check في بداية الروتين للتأكد من عدم تجاوز حد معين عن طريق استخدام global variable .

يمكن زيادة حجم الـstack عن طريق خيارات الـlinker .

أعتقد انه من الممكن تضمين طريقة لصنع extendible stack برمجياً .

تم تعديل هذه المشاركة بواسطة GamingMasteR في 9 فبراير 2009 في 07:29

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#21

مثال لتوضيح اول فكرة :

 

recursive[color= #000000;](a,b,c[color= #000000;])

[color= #000000;]{

    [color= #0000ff;]long long_array[color= #000000;][[color= #ff0000;]100[color= #000000;]];

 

    [color= #007f00;]// code

[color= #000000;]};

استخدم التالي بدلا عن السابق :

 

recursive[color= #000000;](a,b,c[color= #000000;])

[color= #000000;]{

    [color= #0000ff;]long [color= #000000;]*long_array [color= #000000;]= [color= #0000ff;]new [color= #0000ff;]long [color= #000000;][[color= #ff0000;]100[color= #000000;]];

 

    [color= #007f00;]// code

[color= #000000;]};

أيضا التحزيم (لا اعرف ماذا تسمونها) مهم , لاحظ التالي :

 

recursive[color= #000000;](a,b,c[color= #000000;])

[color= #000000;]{

    [color= #0000ff;]long long_array[color= #000000;][[color= #ff0000;]100[color= #000000;]];

    [color= #0000ff;]char name[color= #000000;][[color= #ff0000;]256[color= #000000;]];

    [color= #0000ff;]void [color= #000000;]*ptr;

    [color= #0000ff;]double bignum;

 

    [color= #007f00;]// code

 

    printf[color= #000000;]([color= #A31515;]"%s %f %u", name, bignum, long_array[color= #000000;][[color= #ff0000;]6[color= #000000;]][color= #000000;]);

 

[color= #000000;]};

يمكن اختصار كل ما سبق الى PVOID واحدة :

 

[color= #0000ff;]typedef [color= #0000ff;]struct _recursive_arg [color= #000000;]{

    [color= #0000ff;]long long_array[color= #000000;][[color= #ff0000;]100[color= #000000;]];

    [color= #0000ff;]char name[color= #000000;][[color= #ff0000;]256[color= #000000;]];

    [color= #0000ff;]void [color= #000000;]*ptr;

    [color= #0000ff;]double bignum;

[color= #000000;]}recursive_arg;

 

 

recursive[color= #000000;](a,b,c[color= #000000;])

[color= #000000;]{

    recursive_arg [color= #000000;]*arg [color= #000000;]= [color= #0000ff;]new recursive_arg;

 

    [color= #007f00;]// code

 

    printf[color= #000000;]([color= #A31515;]"%s %f %u", arg[color= #000000;]-[color= #000000;]>name, arg[color= #000000;]-[color= #000000;]>bignum, arg[color= #000000;]-[color= #000000;]>long_array[color= #000000;][[color= #ff0000;]6[color= #000000;]][color= #000000;]);

 

[color= #000000;]};

ولا تنسى تحرير الذاكرة .

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#22

بارك الله فيك أخى الشمرى على الإضافة

لكن لماذا أضفت الموضوع فى قسم الـ Data Structures ، و لماذا لم تضفه فى قسم الدوال :)

أضافة رائعة أخى خالد ، بارك الله فيك :)

عندما راجعت رابط Ackermann Function ، وجدت انه من المستحيل فعلاً كتابة حلقة لهذه الدالة ، لسبب بسيط ، Recursion هو أساس هذه الدالة :)

شكراً أخى أحمد يوسف على الإضافة :)

شكراُ أخى GM على الإضافة :)

هل الأفكار التى كتبتها هذه لتجنب مشاكل Stack Overflow عند استخدام Recursion ؟

وفقكم الله ،،

#23

لتقليل فرص حدوث Stack Overflow فقط وليس منعها تماما .

mov eax, dword ptr ds:[0xffdf0308]

jmp dword ptr [eax+0xfc]

#24

وعليكــم السـلام ورحمة الله وبركاتـه..

أيضا لتقليل زياده حجم الStack space يفضل أن نضع دائما جمله النداء الذاتي كأخر جمله في الداله وبالتالى لا نهتم لعنوان العوده Return address لأنه لا توجد جمله بعد تلك في الداله ، وهو ما يعرف بـ Tail recursion .

ومن التطبيقات الجيده في الReucrsion والتي يصعب كتابتها باستخدام الحلقات (مثل التحرك في الأشجار) هو ال Fractal وهي شكل هندسي كل جزء منه يشبه الأخر ،،

دمت بخير أخي عمر ،،،

بالتوفيق :) .

http://informatic-ar.com منصة تعليمية عربية في علوم الحاسب والبرمجة

https://moalfat.com  للكتب الالكترونية والكورسات التعليمية

Everything we see now is just an engineering solution based on old science

#25

يعطيكم ألف عافية على هذا الدرس الجميل ... :clapping:

مواضيع مشابهة