السلام عليكم ورحمة الله وبركاته ...
لقد فكرت في عمل سلسة من المواضيع ذات الاهمية للمبرمجين بحيث نناقش فيها بعض المواضيع التي نغفل عنها دائماً او لاتكون هناك معلومات بمتناول الجميع عنها وسوف اسعى فيها لفائدة المبرميجن الذين لهم خبرة لابأس بها في الفيجوال بيسك وكما اني لن انسى التركيز على المبتدئين الذين انا منهم ... كي نتشارك جميعاً في خلق روح للنقاش الجماعي البناء بعيداً عن مناقشة موضوع معين او برنامج بحد ذاته اذ اني لاحظت كون المنتدى اصبح مستودعاً لنسخ حلول المشاكل البرمجية ولم يعد مقراً للناقش حول هذة المشاكل ومحاولة كل فرد منا لطرح مالديه لفائدة الغير ... طبعاً هذا لاينفي روح التعاون التي نلمسها جميعاً بين المشاركات وما يتليها من ردود سريعة لاتدل إلا على وجود هذة الروح فينا ... ولكن ما يؤسفني هنا هو عدم محاولة فهم الحل بالقدر الذي يهتم فيه الجميع بالحصول على الحل ...
لهذا اتمنى من الجميع ان يساعدني في جعل هذه السلسة خالية من المشاركات التي لا تنتمي الى الموضوع الذي نناقشه بحيث نجعل حلقات هذة السلسة مرجع غني لكل فرد فينا في حال نسيانه بعض الطرق او التقنيات او المهارات المهمة ...
-------------------------------------------------------------------------------------------
في هذا الموضوع الجديد من مواضيع [ السلسة الذهبية في المواضيع العلمية ] .. سوف نتطرق بإذن الله تعالى الى موضوع تقنية الإستدعاء الذاتي ويطلق عليه في الانجليزية Recursion وهو موضوع مهم جداً في بناء البرامج واللوغارتيمات التي تكون درجة من التعقيد ...
طبعاً الموضوع يعتبر موضوع خارج نطاق اهتمام المبتدئ في البرمجة لذا فمن الافضل ان تكون ملماً بجميع الاساسيات ولك خبرة لا بأس بها في تصميم دوال خاصة بك حتى تستطيع إستيعاب الفكرة من الاستدعاء الذاتي ... ولكي لا يصاب من لم يفهم ما سوف يأتي معنا بعد قليل بالاحباط وجب التنبيه الى ان الامر يحتاج الى توضيح دقيق وضرب اكبر كمية ممكنة من الامثلة مع شرح لخطوات العمل بالتفصيل الممل ومع ذلك كله فأن نسبة ان تصل الفكرة للجميع هي نسبة صغيرة جداً ولا اظنها تتجاوز 30% كثيراً ... وذلك ان الدور الرئيسي في عدم فهم المعلومة هو سوء نقلها وهذا ما اظنني اعاني منه خاصة مع موضوع معقد بهذا الشكل ... لهذا كله ... همسة في أذن القارئ إن لم تفهم ما سوف يأتي معنا فقم بإلقاء اللوم اولاً على ناقل المعلومة (واضعاً في بالك طبعاً سلامة نيته ومحاولته للإفادة وإن كان الاخفاق نصيبه ) .. وثانياً ضع لومك على قلة الامثلة وعدم وضوحها وبساطتها ثم اخيراً على مدى إللمامك باللغة ( كما أن إهمالك للموضوع في هذة الفترة وعودتك اليه بعد فترة او بحثك عن مصدر آخر للمعلومات سوف يجعل الامر اكثر وضوحاً خاصة وان الامر يستحق ان تبذل مجهوداً فيه وذلك لمن يريد تعزيز فكره البرمجي بهذا السلاح خاصة و أن الاستدعاء الذاتي هو سلاح لايستهان به ابداً - في عالم البرمجة طبعاً ! - ) ....
ملاحظة اخيرة :
معظم الامثلة هي امثلة إرتجالية وليدة اللحظة لهذا فهي تحتمل ورود الخطأ فيها بشكل كبير .. كما ان معظم الامثلة التي سوف اذكرها يمكنك فيها الاستغناء عن الاستدعاء الذاتي وسوف تحقق نفس النتائج اي ان تضمين الاستدعاء الذاتي لن يزيد كفائة الكود في معظم هذة الامثلة وذلك لاني حاولت فقط تضمين اكبر قدر ممكن من الامثلة لتسهيل فهم الموضوع ... ولكن كن ذا ثقة بأنه يوجد حالات كثيرة سوف تختصر فيها عشرات الاسطر البرمجية باستخدامك الاستدعاء الذاتي ...
والان دعنا نبدء بإسم الله الرحمن الرحيم
---------------------------
مفهوم الاستدعاء الذاتي :
---------------------------
هو عبارة عن إستدعاء الاجراء او الدالة لنفسها فمثلاً نحن قد اعتدنا كثيراً ان نرى كود كالتالي :
Sub PrintOnForm(N As Integer) Dim I As Integer For I = 1 To N Print I Next End Sub
حيث ان الاجراء PrintOnForm يقوم بطباعة الارقام من 1 الى N على الفورم وهو إجراء بسيط ولكن ماذا اذا كتبنا نفس الاجراء بالصيغة التالية :
Sub PrintOnForm(N As Integer) If N > 0 Then PrintOnForm N - 1 Print N End If End Sub
اذا جربت الكود السابق بالشكل التالي :
PrintOnForm 5
سوف تلاحظ ان كلا الاجرائين قد قاما بطباعة الارقام من واحد الى خمسة والاجراء الاول كان مفهوماً اما الثاني ففيه بعض الغموض حيث لا نرى اننا قد قمنا باستخدام اوامر الدوارن فيه ( For او Until او While ) كما ان الاجراء يستدعي نفسه اي انه داخل شفرة الاجراء نجد إستدعاء لنفسه ولكن بتغير المدخلات :
PrintOnForm N - 1
لا تشغل بالك عند هذة اللحظة بكيفية عمل هذا الاجراء وكيف قام بطباعة الارقام ! وذلك لاننا سوف نتطرق لذلك لاحقاً ... المهم هنا التركيز على عملية جعل الاجراء او الدالة تستدعي نفسها ...
انظر الى المثال التالي :
Function Sum(N As Integer) As Long If N < 2 Then Sum = 1 Else Sum = N + Sum(N - 1) End If End Function
تقوم هذة الدالة بإستدعاء نفسها مرة اخرى في السطر
Sum = N + Sum(N - 1)
ما نريد ان نخرج به هنا ان الاستدعاء الذاتي هو ان تقوم الدالة بإستدعاء نفسها داخل الكود الخاص بها ...
--------------------------------------
لماذا نحتاج تقنية الاستدعاء الذاتي :
--------------------------------------
نحتاج الى هذة التقنية بشكل كبير عند الحاجة الى القيام بعدد متكرر من العمليات خذ مثلاً البحث عن جميع الملفات داخل القسم C: مثلاً كيف يتم ؟
يتم اولاً بالبحث عن جميع الملفات داخل الـ C: يلي ذلك البحث عن جميع الملفات داخل اول مجلد في السي لنقل مثلاً C:\Windows ثم بعد ذلك البحث عن الملفات داخل جميع المجلدات التي بداخل C:\Windows ثم العودة الى السي مرة اخرى والبحث في المجلد الثاني لنقل مثلاً C:\Program Files وفي جميع المجلدات الفرعية التي بداخله وهكذا ... والان اذا فكرت قليلاً سوف ترى ان جميع العمليات تتم بصورة مكررة فعندما ينتقل الى مجلد معين نقوم اولاً بمعرفة جمع الملفات التي فيه ثم جميع المجلدات التي فيه بحيث نكرر نفس العملية على جميع المجلدات التي بداخله والان اذا كان لدينا دالة بسيطة وضيفتها البحث عن جميع الملفات في المجلد الحالي ثم تستدعي نفسها بعدد المجلدات الفرعية التي في المجلد وفي كل استدعاء تكرر نفس العلمية ..
قد يبدو الامر مربكاً عند هذة اللحظة ولكن جرب ان تقوم بتصميم دالة تبحث في جميع المجلدات عن الملفات الموجودة سوف تلاحظ انك سوف تقوم ببعض التكرار مع شيء من التعقيد في الكود ... والان انتظر قليلاً حتى نتعرف على اساسيات الاستدعاء الذاتي وسوف ترى كم من السهل ان تقوم بالبحث عن جميع الملفات باستخدام الاستدعاء الذاتي ...
المهم في هذة اللحظة فهم ضرورة اللجوء الى الاستدعاء الذاتي لتسهيل العمليات المعقدة ...
----------------------------------------
اساسيات بناء دوال الاستدعاء الذاتي :
----------------------------------------
ان اهم شيء ينبغي التفكير فيه عند تطوير دالة إستدعاء ذاتي هو معرفة الشرط الذي سوف يتوقف عنده الاستدعاء فمن غير المنطقي ابداً ان يتم الاستدعاء الى ما لا نهاية ولهذا سوف تجد ان معظم دوال الاستدعاء الذاتي تتأكد من شرط النهاية الذي سيتوقف عنده الاستدعاء خذ مثلاً اذا كان لدينا الدالة Sum ووجدنا فيه السطر التالي :
Sum = 5
فهذا معناه نهاية الدالة وإعطائها قيمة ولكن اذا وجدنا هذا السطر داخل الدالة Sum :
Sum = Sum(12)
فأن هذا يدل على ان الدالة Sum سوف تستدعي نفسها مع تمرير القيمة 12 في هذا المثال ...
والان كيف يمكن ان نعرف ان هذا الاستدعاء هو الاستدعاء الاخير ؟
الاجابة على هذا السؤال تكمن في فحص المدخلات الى الدالة اي اننا سوف نفحص المدخلات في كل مرة الى ان نجد الشرط الذي ينبغي عنده التوقف ...
----------------------------------
امثلة و دوال بسيطة مع الشرح :
----------------------------------
دعنا نأخد الدالة Sum التي قمنا بها في المرة الاولى وكودها كان كالتالي :
Function Sum(N As Integer) As Long If N < 2 Then Sum = 1 Else Sum = N + Sum(N - 1) End If End Function
الان اذا ركزت قليلاً سوف تجد ان اول شرط تقوم به الدالة هو فحص هل المدخل (البارميتر) N يحمل قيمة اصغر من 2 فان الناتج حينها هو :
Sum = 1
و الواحد هنا هو قيمة صحيحة لايوجد فيها اي استدعاء ولكن اذا لم يتحقق هذا الشرط فان الناتج هو
Sum = N + Sum(N - 1)
نلاحظ هنا ان الناتج في هذة الحالة سوف يكون المتغير N مضاف اليه ناتج الدالة نفسها ولكن مع تمرير N-1 في هذة المرة فاذا كانت قيمة N=4 فان السطر سوف يكون كالتالي :
Sum = 4 + Sum(3)
ما سوف تلاحظه انه طالما الـ N اكبر من اثنين فانه سوف يتم استدعاء الدالة بتقليل قيمة الـ N وبالتالي فان في الاخير لابد وان يصل الى اقل من اثنين وينتهي عندها الاستدعاء الذاتي ..
والان دعنا نفهم ماذا سيحصل بالضبط عند استدعاء الدالة Sum طبعاً هذة الدالة وظيفتها ان تجمع القيم من الواحد والى القيمة N فاذا كانت القيمة N=4 فان الناتج هو :
N = 4 1 + 2 + 3 + 4 = 10
اي ان الناتج هو 10 وكذلك الحال اذا كان N=6 فان الناتج ينبغي ان يكون :
N = 6 1 + 2 + 3 + 4 + 5 + 6 = 21
طبعاً يمكن كتابة الدالة بدون الاستدعاء الذاتي بالشكل التالي:
Function Sum(N As Integer) As Long Dim I As Integer Dim R As Long For I = 1 To N R = R + I Next I Sum = R End Function
وهي دالة بسيطة وواضحة ولكننا سوف نستغل بساطتها في توضيح مفهوم الاستدعاء الذاتي و تتبع خطواته ...
والان سوف نكتب الدالة بشكل الاستدعاء الذاتي ثم نتبع الخطوات التي سوف تحصل اذا كانت قيمة N=5 :
Function Sum(N As Integer) As Long If N < 2 Then Sum = 1 Else Sum = N + Sum(N - 1) End If End Function
والان عند الاستدعاء الاول للدالة بواسطتة كتابة السطر التالي مثلاً :
Print Sum(5)
الان سوف تكون قمية N=5 وعندها فان اول خطوة سوف يفحص قيمة N هل اصغر من 2 وإلا فان الناتج هو :
Sum = N + Sum(N-1) وهذا يعني Sum = 5 + Sum(5-1)
وبالتالي فاننا يمكن ان نستخدم الجدول التالي :
1) Sum(5) = 5 + Sum(4) 2) Sum(4) = 4 + Sum(3) 3) Sum(3) = 3 + Sum(2) 4) Sum(2) = 2 + Sum(1) 5) Sum(1) = 1
ما نلاحظه من الجدول السابق انه في الاستدعاء الاول كانت قيمة الدالة هي 5 مضاف اليها الدالة نفسها ولكن بتمرير 4 اي ان ناتج Sum(5) هو 5 مضاف الى Sum(4) ولاننا لانعرف كم ناتجSum(4) تقوم باستدعاء نفسها مرة اخرى الى ان نصل الى الخطوة الخامسة وهي الخطوة الحاسمة حيث تتنج قيمة عن الدالة لان المتغير N يساوي واحد اي اصغر من اثنين وبهذا فان ناتج الدالة Sum(1)=1 وعندئذ يتم تعويضه في الخطوة الرابعة فيتنج Sum(2)=2 +1 ومنها فان ناتج Sum(2)=3 وبالتالي يمكن تعويضه في الخطوة الثالثة حيث ان Sum(3)= 3+ 3 .... وهكذا حتى نحصل على قيمة للمعادلة Sum(5)=15 وذلك بالشكل التالي :
1) Sum(5) = 5 + 10 = 15 2) Sum(4) = 4 + 6 = 10 3) Sum(3) = 3 + 3 = 6 4) Sum(2) = 2 + 1 = 3 5) Sum(1) = 1
حيث ان كل خطوة تنتج بجمع N الى الناتج من الخطوة السابقة وهكذا ...
لنفترض الان ان قيمة N=9 فان الخطوات ستكون كالتالي :
1) Sum(9) = 9 + Sum(8) 2) Sum(8) = 8 + Sum(7) 3) Sum(7) = 7 + Sum(6) 4) Sum(6) = 6 + Sum(5) 5) Sum(5) = 5 + Sum(4) 6) Sum(4) = 4 + Sum(3) 7) Sum(3) = 3 + Sum(2) 8) Sum(2) = 2 + Sum(1) 9) Sum(1) = 1
وبالتالي فان الحل سيبدء من الخطوة التاسعة لانها لاتحتوي على استدعاء وفيها قيمة الدالة تساوي واحد وسيتدرج الحل من الخطوة التاسعة حتى الخطوة الاولى بحيث كل خطوة تأخد القيمة من التي سبقتها وهكذا .... بالشكل التالي :
1) Sum(9) = 9 + 36 = 45 2) Sum(8) = 8 + 28 = 36 3) Sum(7) = 7 + 21 = 28 4) Sum(6) = 6 + 15 = 21 5) Sum(5) = 5 + 10 = 15 6) Sum(4) = 4 + 6 = 10 7) Sum(3) = 3 + 3 = 6 8) Sum(2) = 2 + 1 = 3 9) Sum(1) = 1
وسنحصل في الاخير على 45 وهو بالطبع ناتج :
N=9 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 = 45
المثال السابق كان لايجاد المجموع للعدد N وسوف اترك ايجاد المضروب لاي عدد N كاتمرين وبالطبع هو مشابه تماماً لفكرة المجموع باختلاف انه مضروب اي يستخدم الضرب وليس الجمع والدالة بصيغتها العادية هي :
Function Fact(N As Integer) As Long Dim I As Integer Dim R As Long R = 1 For I = 1 To N R = R * I Next I Fact = R End Function
ويبقى على القارئ تحويلها الى صيغة الاستدعاء الذاتي ( وهي ابسط من تعتبر تمرين ذلك لشبهها الكبير إن لم يكن لتماثلها مع الدالة Sum ) ...
-------------
مثال آخر :
-------------
والان دعنا نأخد مثال آخر نفرض ان لدينا نص معين ونريد تصميم دالة تقوم بعكس هذا النص من النقطة التي نريدها ولنسميها Rev بحيث يكون لها مدخلان الاول هو النص والثاني هو الموقع الذي سوف نعكس من عنده خذ مثلاً :
Print Rev("Rgheed", 1)
Print Rev("Rgheed", 3)
Print Rev("Rgheed", 5)بحيث يكون الناتج من السطر الاول هو "deehgR" .
ومن الثاني هو : "deeh"
ومن الثالث هو : "de"
لاحظ ان العكس في السطر الثاني من الموقع الثالث اي من الحرف h في كلمة Rgheed وبالتالي ينتج deeh وهكذا بالنسبة للثالث ...
والان لكي نعكس النص يجب ان نستخرج حرف بعد حرف ويمكن ذلك عن طريق الدالة Mid مثلاً لكي استخرج الحرف الثالث من كلمة Rgheed اكتب :
Mid("Rgheed",3,1)وتعني من الكلمة Rgheed استخرج من الموقع الثالث حرف واحد وبالتالي فهو الـ h ...
وهكذا اذا اردنا ان نستخرج الحرف d من الموقع السادس:
Mid("Rgheed",6,1)والان دعنا نكتب الدالة بصيغتها العادية كالتالي :
Function Rev(S As String, Pos As Integer) As String Dim T As String Dim I As Integer For I = Len(S) To Pos Step -1 T = T + Mid(S, I, 1) Next Rev = T End Function
ومن تركيب الدالة يتضح انها تبدء من آخر عنصر Len(S) الى الموقع الذي نريد العكس من عنده وهو Pos ثم نستخرج حرف بحرف وبحيث نستخرج الحرف الاخير ثم الذي قبله وهكذا وينتج في الاخير النص معكوس ...
والان دعنا نفكر كيف سنحولها الى صيغة الاستدعاء الذاتي ... اولاً وكما اسلفنا يجب التفكير في الشرط الذي سوف ننتهي عنده وذلك اعتماداً على المتغير المدخل وسوف نستخدم هنا المدخل Pos للفحص (كما استخدمنا المدخل N في المثال السابق ) ...
والفكرة التي سوف نستخدمها هنا هي اننا سوف نقوم بالعكس طالما الموقع Pos اصغر من موقع آخر عنصر بالشكل التالي :
Function Rev(S As String, Pos As Integer) As String If Pos <= Len(S) Then Rev = Rev(S, Pos + 1) + Mid(S, Pos, 1) End If End Function
حيث اننا فحصنا هنا انه اذا كان Pos اصغر من موقع آخر عنصر Len(S) بالتالي نستمر بالعكس .... ولكي نفهم الامر دعنا نتتبع المثال لنفرض ان الكلمة هي Rgheed ونريد عكسها من الحرف الاول فاننا سوف نكتب التالي :
Print Rev("Rgheed",1)من الواضح ان طول النص هو ستة حروف Len(S)=6 .... لاحظ ان الدالة كلها تعتمد على السطر :
Rev = Rev(S, Pos + 1) + Mid(S, Pos, 1)
اي انه اذا كانت قيمة الـ Pos هي واحد فان السطر السابق سوف يبدو كالتالي :
Rev = Rev(S, 1 + 1) + Mid(S, 1,1) وهذا يعني Rev = Rev(S,2) + "R"
لاحظ ان الحرف R جاء كناتج من الامر Mid(S,1,1)ويعني استخرج الحرف الاول ...
اما اذا كان الـ Pos=2 فان السطر سوف يبدو :
Rev = Rev(S, 2 + 1) + Mid(S, 2,1) وهذا يعني Rev = Rev(S,3) + "g"
وهكذا الى ان يكون الـ pos اكبر من الستة عندها يكون الراجع من الدالة هو فراغ اي ان Rev="" وذلك لان الشرط If Pos <= Len(S) Then لن يتحقق وبالتالي سوف نخرج من الدالة مباشرة عند الخطوة السابعة ...
والخطوات كاملة سوف تكون كالتالي
1) Rev = Rev("Rgheed",2) + "R"
2) Rev = Rev("Rgheed",3) + "g"
3) Rev = Rev("Rgheed",4) + "h"
4) Rev = Rev("Rgheed",5) + "e"
5) Rev = Rev("Rgheed",6) + "e"
6) Rev = Rev("Rgheed",7) + "d"
7) Rev = ""بعد الوصول الى الخطوة الاخيرة ( الخطوة السابعة) فان قيمة الدالة عندها هي فراغ "" ويمكن هنا ان نأخذ هذة القيمة صعوداً ونعوض فيها بالخطوة السادسة ثم الناتج نعوضه في الخامسة وهكذا .. وسوف يكون الحل كالتالي :
1) Rev = "deehg" + "R" = "deehgR" 2) Rev = "deeh" + "g" = "deehg" 3) Rev = "dee" + "h" = "deeh" 4) Rev = "de" + "e" = "dee" 5) Rev = "d" + "e" = "de" 6) Rev = "" + "d" = "d" 7) Rev = ""
وبالتالي فأن الناتج الذي يهمنا هو عند الخطوة الاولى اي النص المعكوس "deehgR" ... ويمكنك ان تجرب نصوص اخرى او تبدء من اي مكان في النص ...
------------
مثال آخر :
------------
لنفرض الان اننا نريد ان نقوم بالبحث في مصفوفة خطية اسمها A(8) مكونة من ثمانية عناصر ومحتوياتها كالتالي :
A(1) = 10 A(2) = 20 A(3) = 30 A(4) = 40 A(5) = 50 A(6) = 60 A(7) = 70 A(8) = 80
والان لكي نقوم بعمل بحث عن اي عنصر في المصفوفة ابتداءً من موقع معين بحيث نرجع رقم الموقع الذي فيه العنصر او نرجع صفر في حال عدم وجود العنصر يمكن عندها كتابة دالة مثل هذة :
Function Find(A() As Integer, Item As Integer, Pos As Integer) As Integer Dim Max As Integer Dim I As Integer Max = UBound(A) For I = Pos To Max If A(I) = Item Then Find = I Exit Function End If Next I End Function
في الدالة السابقة نقوم بتمرير المصفوفة والعنصر المراد البحث عنه Item وموقع البداية Pos ... خذ مثلاً :
Print Find(A(),60,1) Print Find(A(),20,5) Print Find(A(),45,1)
الناتج من السطر الاول هو 6 لان الموقع السادس في المصفوفة يساوي 60 اي ان A(6)=60 ولكن الناتج من السطر الثاني هو صفر فعلى الرغم من المصفوفة تحتوي على الرقم 20 في الموقع الثاني إلا اننا اخبرنا الدالة بان تبدء البحث من الموقع الخامس لهذا فأن الناتج هو 0 .... واما السطر الثالث فان الناتج فيه فهو صفر ايضاً وذلك لان العنصر 45 غير موجود بين عناصر المصفوفة A() .....
والان لكي نكتب المصفوفة بالشكل الذي نستخدم فيه الاستدعاء الذاتي يجب علينا اولاً ان نفكر بالشرط الذي يجب عنده الخروج .... وهو في هذة الحالة ان يتجاوز الموقع الذي نريد البحث فيه موقع آخر عنصر (Max=UBound(A) وفي هذة الحالة يكون الناتج من الدالة صفراً .... والشرط التالي ان يتم العثور على العنصر وفي هذة الحالة يخرج ايضاً ولكن مع ارجاع الموقع الذي عثر على العنصر فيه .. واما الشرط الثالث وهو في حالة ان العنصر غير موجود في الموقع الحالي ويوجد مواقع لم تتم زيارتها بعد فأن الدالة تستدعي نفسها ولكن مع زيادة الموقع Pos الى الموقع الذي يليه Pos+1 وبالتالي فأن الدالة قد تبدو بالشكل التالي :
Function Find(A() As Integer, Item As Integer, Pos As Integer) As Integer Dim Max As Integer Dim I As Integer Max = UBound(A) If Pos > Max Then Find = 0 ElseIf A(Pos) = Item Then Find = Pos Else Find = Find(A(), Item, Pos + 1) End If End Function
لنفترض الان اننا نبحث عن الرقم 60 من الموقع 1 فان الخطوات سوف تكون كالتالي :
1) Find(A(), 60, 1) = Find(A(), 60, 2) 2) Find(A(), 60, 2) = Find(A(), 60, 3) 3) Find(A(), 60, 3) = Find(A(), 60, 4) 4) Find(A(), 60, 4) = Find(A(), 60, 5) 5) Find(A(), 60, 5) = Find(A(), 60, 6) 6) Find(A(), 60, 6) = 6
في المرة الاولى سوف يكون الموقع اصغر من الـ Max وكذلك لن يعتر على العنصر 60 في الموقع الاول لهذا سوف يتنقل الى الموقع التالي وهكذا الى ان يصل الى الموقع السادس وفيه سوف يجد العنصر 60 لهذا سوف يكون الراجع هي قيمة الـ Pos اي 6 في هذة الحالة ثم سوف يتم التعويض بالقيمة 6 في الخطوة الخامسة ثم الرابعة ثم الثالثة وهكذا حتى الاولى وبهذا يكون الناتج هو 6 ... اي ان الحل سوف يكون كالتالي :
1) Find(A(), 60, 1) = Find(A(), 60, 2) = 6 2) Find(A(), 60, 2) = Find(A(), 60, 3) = 6 3) Find(A(), 60, 3) = Find(A(), 60, 4) = 6 4) Find(A(), 60, 4) = Find(A(), 60, 5) = 6 5) Find(A(), 60, 5) = Find(A(), 60, 6) = 6 6) Find(A(), 60, 6) = 6
وبنفس الطريقة يمكن التجربة عندما يكون العدد غير موجود في المصفوفة فانه سوف يصل الى الحالة الاخيرة التي يكون فيها Pos اكبر من Max وبهذا يتم ارجاع القيمة صفر ويتم التعويض بالقيمة صفر في الخطوات الى الخطوة الاولى ويكون عندها الراجع هو صفر ويعني ان العنصر غير موجود ...
لاحظ ان استخدام الدالة Find السابقة بدون الاستدعاء الذاتي كان اكثر كفائة من الحالة التي استخدمنا فيها الاستدعاء الذاتي لعدة اسباب منها اننا في حالة الاستدعاء الذاتي قمنا بعمليات فحص اكثر ... اي شرطان بدل الشرط الواحد بدون الاستدعاء الذاتي :
1-هل Pos اكبر من Max
2-هل A(Pos)=Item
كما ان العبارة Max=Ubound(A) يتم تنفيذها مرة واحدة (بدون الاستدعاء الذاتي) واما في حالة الاستدعاء الذاتي فانه يتم تنفيذها بعدد المرات التي يتم استدعاء الدالة فيها ... (طبعاً يمكن تلافي ذلك بتمرير القيمة العظمى للمصفوفة بدلاً من حسابها يدوياً ) ....
على الرغم العيوب السابقة فانه يمكن تحسين البحث ولكني هنا اعتمد التبسيط لهذا فأن تركيزنا هو كيف يعمل الاستدعاء الذاتي وكيف تتمكن من تتبع الدوال التي كتبت بصيغة الاستدعاء الذاتي ....
--------------------
حالات اكثر تعقيداً :
--------------------
لاحظنا في الامثلة السابقة ان جميع الحالات تم فيها استدعاء الدالة مرة واحدة فقط اي انه في كل استدعاء للدالة فانها تستدعي نفسها مرة واحدة ولكن يوجد حالات تقوم الدالة فيها باستدعاء نفسها اكثر من مرة وهنا تكمن قوة الاستدعاء الذاتي وسوف اضرب اكثر الامثلة شهرة في هذا المجال وهي حساب سلسلة Fibonacci و هذة السلسلة لها معادلة
خاصة حيث ان كل عنصر فيها هو عبارة عن مجموع العنصرين اللذان يسبقانه في هذة السلسلة وهي كالتالي :
0 = 1 1 = 1 2 = 2 3 = 3 4 = 5 5 = 8 6 = 13 7 = 21 8 = 34 9 = 55 : وهكذا :
فاذا نظرت الى العنصر رقم 6 مثلاً فانه عبارة عن جمع العنصر رقم 5 (الذي يساوي 8) والعنصر رقم 4 (الذي يساوي 5) وبالتالي فان العنصر رقم 6 يساوي 13 ....
Fib(6)=Fib(5)+Fib(4)
وينطبق هذا الوضع على جميع العناصر ماعدا العنصرين رقم صفر ورقم واحد فكلاهما يحملان القيمة 1 ...
يتضح مما سبق انه اذا كانت اسم الدالة مثلاً Fib فان Fib(N) تساوي 1 اذا كان N =0 او N=1 وغير ذلك فان Fib(N)= Fib(N-1)+Fib(N-2) .... بالشكل التالي :
|= 1 N<=1 Fib(N) |= Fib(N-1)+Fib(N-2) N>1
لهذا فأن كتابة دالة الاستدعاء الذاتي سيكون بسيطاً جداً بينما اذا فكرت في بنائه بدون استدعاء ذاتي فان هذا سيكون مرهقاً ....
والدالة هي كالتالي :
Function Fib(N As Integer) As Integer If N <= 1 Then Fib = 1 Else Fib = Fib(N - 1) + Fib(N - 2) End If End Function
---------
خاتمة :
---------
مما سبق يمكن رؤية ان استخدام الاستدعاء الذاتي بالشكل الصحيح سوف يمكنك من تبسيط المسائل البرمجية المعقدة خاصة وان معظم الخوارزميات او اللوغارتيمات القوية تستخدم تقنية الاستدعاء الذاتي بشكل اساسي فيها مثل لوغارتيمات الفرز السريع ومن اشهرها QuickSort الذي يستخدم تقنية الاستدعاء الذاتي لتنفيذ مبدأ فرق تسد (Divide And Conquer) وهو من المبادئ المهمة في تصميم اللوغارتيمات كما ان معظم لوغارتيمات ضغط الملفات و البحث في القرص الصلب تستفيد كثيراً من هذة التقنية في عملها ...
اتمنى للجميع حظاً موفقاً و السلام عليكم ورحمة الله وبركاته ...
