المحاضرة 6: البحث بالتنصيف (Bisection Search)
المحاضرة 6: البحث بالتنصيف (Bisection Search)
المصادر والنسبة والترخيص
المادة الأصلية: آنا بيل (Ana Bell)، MIT OpenCourseWare (MIT OCW)، معهد ماساتشوستس للتكنولوجيا، مقرر 6.100L: مقدمة في علوم الحاسوب والبرمجة باستخدام Python، خريف 2022 (Fall 2022).
- صفحة المحاضرة الرسمية.
- صفحة الشرائح الرسمية.
- ملف الشرائح الأصلي، PDF.
- ملف شيفرة المحاضرة الأصلي.
- تفريغ المحاضرة على OCW (النسخة الإنجليزية الرسمية).
هذه ترجمة وتكييف عربي غير رسمي وفق رخصة CC BY-NC-SA 4.0: النسبة–غير التجاري–المشاركة بالمثل، ولا تعني اعتمادًا أو تأييدًا من MIT أو MIT OCW. شروط الاستخدام والاستشهاد.
منهج الترجمة: لكل صفحة في ملف الشرائح عنوان مستقل ورقم مطابق. نُقلت الأشكال المتكرّرة إلى جداول تحافظ على تسلسلها. الشيفرة والشفرة الوهمية (pseudocode) محفوظة بالإنجليزية. لم تُضمَّن صور أو صفحات PDF.
الشريحة 1: البحث بالتنصيف (Bisection Search)
(نزّل الشرائح وملفات .py لمتابعة الشرح).
6.100L، المحاضرة 6 — آنا بيل (Ana Bell).
الشريحة 2: المحاضرة الماضية
-
الأعداد العشرية (floating point numbers) تضيف تحديات جديدة:
- لا يمكن تمثيلها في الذاكرة تمثيلًا تامًّا.
- العمليات على الأعداد العشرية تُدخل أخطاءً ضئيلة.
- عمليات متعدّدة على الأعداد العشرية تُضخّم الأخطاء.
-
التحقق والتخمين (guess-and-check) يعدّ الأعداد الصحيحة واحدًا تلو الآخر بوصفه حلًّا للمشكلة.
-
طرق التقريب (approximation methods) تعدّ باستخدام زيادة عشرية (float increment).
- لا يمكن التحقق من حلّ ما، لكن يمكن التحقق ممّا إذا كان الحلّ يعطي قيمة ضمن إيبسيلون (epsilon).
الشريحة 3: مراجعة: إيجاد الجذر التربيعي مع شرط التوقّف وبزيادة كبيرة (0.01)
- السهم الأزرق هو التخمين (guess).
- السهم الأخضر هو
guess**2.
القيمة x = 54321، ومعها إيبسيلون.
ملاحظة المترجم: يبيّن الرسم على خطّ الأعداد موضع التخمين وموضع guess**2 بالأسهم، وهما متغيّران يتغيّران مع كلّ خطوة في محاضرة 5. أُعيد هنا ذكر المبدأ فقط لأنّ نصّ PDF المستخرَج لا يحفظ مواضع الأسهم على المحور، ولم يُستدلّ على أرقام لم تُذكر في الملف.
الشريحة 4: مراجعة على طريقة التقريب لإيجاد جذر تربيعي «قريب كفاية»
x = 54321
epsilon = 0.01
num_guesses = 0
guess = 0.0
increment = 0.0001
while abs(guess**2 - x) >= epsilon and guess**2 <= x:
guess += increment
num_guesses += 1
print('num_guesses =', num_guesses)
if abs(guess**2 - x) >= epsilon:
print('Failed on square root of', x)
else:
print(guess, 'is close to square root of', x)
الشريحة 5: البحث بالتنصيف (Bisection Search)
شريحة عنوان.
الشريحة 6: فرصة لربح مبلغ كبير
- لنفرض أنّني ألصقت ورقةً من مئة دولار في صفحة معيّنة من كتاب مدرسي طوله 448 صفحة.
- إن استطعت تخمين الصفحة في 8 محاولات أو أقلّ حصلتَ على مبلغ كبير.
- وإن أخفقتَ حصلتَ على تقدير راسب (F).
- هل ترغب في اللعب؟
- والآن لنفرض أنّني في كلّ محاولة أخبرك إن كنتَ مصيبًا، أم أنها أقلّ من المطلوب، أم أعلى من المطلوب.
- هل ترغب في اللعب في هذه الحالة؟
الشريحة 7: البحث بالتنصيف (Bisection Search)
-
يصلح لتطبيقه على المسائل التي تكون فيها إجاباتها الممكنة ذات ترتيب inherent.
-
لنفرض أنّنا نعرف الإجابة تقع داخل مجال (interval) ما.
- خمّن نقطة المنتصف (midpoint) في هذا المجال.
- إن لم تكن الإجابة، فافحص: هل الإجابة أكبر أم أصغر من نقطة المنتصف؟
- غيّر المجال.
- كرّر.
-
يقسم هذا العملية مجموعة الأشياء المطلوب فحصها إلى نصفها في كلّ مرحلة.
-
البحث الشامل (exhaustive search) يقلّصها من N إلى N-1 في كلّ خطوة.
-
البحث بالتنصيف (bisection search) يقلّصها من N إلى N/2.
الشريحة 8: النموّ اللوغاريتمي (Logarithmic Growth) أفضل
-
يقسم هذا العملية مجموعة الأشياء المطلوب فحصها إلى نصفها في كلّ مرحلة.
-
هذه هي الخاصية المميِّزة للنموّ اللوغاريتمي.
-
مقارنة بين خوارزميتين: التحقق والتخمين (guess-and-check) مقابل البحث بالتنصيف (bisection search).
- الفحص واحدًا تلو الآخر بشكل تكراري خطّي (linear) في عدد التخمينات الممكنة.
- الفحص بتخمين نقطة المنتصف لوغاريتمي (logarithmic) في عدد التخمينات الممكنة.
- الخوارزمية اللوغاريتميّة أكثر كفاءة بكثير.
الشريحة 9: تشبيه (An Analogy)
- لنفرض أنّنا أجبرناك على الجلوس في الصفّ مرتّبين أبجديًّا، من الزاوية الأمامية اليسرى إلى الزاوية الخلفية اليمنى.
- للعثور على طالب بعينه، أستطيع أن أسأل الشخص الموجود في منتصف القاعة عن اسمه.
- وبناءً على الإجابة، أستطيع أن أستبعد النصف الخلفي أو النصف الأمامي من القاعة كلها.
- وأكرر هذه العملية حتى أعثر على الشخص الذي أبحث عنه.
الشريحة 10: البحث بالتنصيف للجذر التربيعي
- لنفرض أنّنا نعرف أنّ الإجابة تقع بين 0 و x.
- بدلًا من تجريب الأشياء واحدًا تلو الآخر بدءًا من الصفر، لننتقل بدلًا من ذلك إلى اختيار رقم من منتصف هذا المجال.
على خطّ الأعداد: من 0 إلى x، وعند g نقطة مختارة.
- إن كنّا محظوظين تكون هذه الإجابة قريبة كفاية.
الشريحة 11: البحث بالتنصيف للجذر التربيعي
- إن لم تكن قريبة كفاية: هل التخمين
gكبيرٌ أم صغير؟ - إن كان
g**2 > xنعرف أنّgكبير؛ إذن نبحث الآن في المجال الأصغر.
على خطّ الأعداد: من 0 إلى x، وعند g و new g نقطتان متتاليتان.
الشريحة 12: البحث بالتنصيف للجذر التربيعي
- وإن كان هذا
gالجديد مثلًا بحيثg**2 < x، نعرف أنه صغير؛ إذن نبحث الآن في المجال الأكبر.
على خطّ الأعداد: من 0 إلى x، وعند g و new g و next g.
- في كلّ مرحلة نُقلّص مجال القيم التي نبحث فيها إلى النصف.
الشريحة 13: البحث بالتنصيف للجذر التربيعي
- وإن كان هذا
gالتالي مثلًا بحيثg**2 < x، نعرف أنه صغير؛ إذن نبحث الآن في المجال الأكبر.
على خطّ الأعداد: من 0 إلى x، وعند g و next g و latest g.
- في كلّ مرحلة نُقلّص مجال القيم التي نبحث فيها إلى النصف.
الشريحة 14: الفكرة الكبرى (Big Idea)
البحث بالتنصيف (bisection search) يستفيد من خصائص المسألة:
- فضاء البحث (search space) له ترتيب.
- يمكننا أن نعرف إن كان التخمين أقلّ من اللازم أو أعلى من اللازم.
الشريحة 15: جرّب بنفسك!
- أنت تخمّن رمز دخول من 4 أرقام. كلّ ما يخبرك به الهاتف هو: هل تخمينك صحيح أم لا. هل تستطيع استخدام البحث بالتنصيف لتخمّن الرمز بسرعة وبالصحّة؟
ملاحظة المترجم: السؤال يمثّل حالة لا يصلح فيها البحث بالتنصيف، لأنّ لوحة المفاتيح لا تقدّم ترتيبًا أو تغذية راجعة «أعلى/أدنى» — وهذا هو المقصود من التمرين.
الشريحة 16: جرّب بنفسك!
- أنت تلعب لعبة تخمين قاسية جدًا: أن تخمّن رقمًا تمامًا. صديقٌ في ذهنه رقم عشري بين 0 و 10 (بدقّة غير محدودة). التغذية الراجعة على تخمينك هي: صحيح، أم أعلى، أم أدنى. هل تستطيع استخدام البحث بالتنصيف لتخمّن الرقم بسرعة وبالصحّة؟
الشريحة 17: الجذر التربيعي البطيء باستخدام طرق التقريب
x = 54321
epsilon = 0.01
num_guesses = 0
guess = 0.0
increment = 0.00001
while abs(guess**2 - x) >= epsilon and guess**2 <= x:
guess += increment
num_guesses += 1
print('num_guesses =', num_guesses)
if abs(guess**2 - x) >= epsilon:
print('Failed on square root of', x)
else:
print(guess, 'is close to square root of', x)
الشريحة 18: الجذر التربيعي السريع
x = 54321
epsilon = 0.01
num_guesses = 0
while abs(guess**2 - x) >= epsilon:
num_guesses += 1
print('num_guesses =', num_guesses)
print(guess, 'is close to square root of', x)
الشريحة 19: الجذر التربيعي السريع
x = 54321
epsilon = 0.01
num_guesses = 0
low = 0
high = x
guess = (high + low)/2.0
while abs(guess**2 - x) >= epsilon:
num_guesses += 1
print('num_guesses =', num_guesses)
print(guess, 'is close to square root of', x)
الشريحة 20: الجذر التربيعي السريع
x = 54321
epsilon = 0.01
num_guesses = 0
low = 0
high = x
guess = (high + low)/2.0
while abs(guess**2 - x) >= epsilon:
if guess**2 < x :
else:
num_guesses += 1
print('num_guesses =', num_guesses)
print(guess, 'is close to square root of', x)
الشريحة 21: الجذر التربيعي السريع
x = 54321
epsilon = 0.01
num_guesses = 0
low = 0
high = x
guess = (high + low)/2.0
while abs(guess**2 - x) >= epsilon:
if guess**2 < x :
low = guess
else:
num_guesses += 1
print('num_guesses =', num_guesses)
print(guess, 'is close to square root of', x)
الشريحة 22: الجذر التربيعي السريع
x = 54321
epsilon = 0.01
num_guesses = 0
low = 0
high = x
guess = (high + low)/2.0
while abs(guess**2 - x) >= epsilon:
if guess**2 < x :
low = guess
else:
high = guess
num_guesses += 1
print('num_guesses =', num_guesses)
print(guess, 'is close to square root of', x)
الشريحة 23: الجذر التربيعي السريع
(رابط Python Tutor لمتابعة تنفيذ الشيفرة خطوة بخطوة).
x = 54321
epsilon = 0.01
num_guesses = 0
low = 0
high = x
guess = (high + low)/2.0
while abs(guess**2 - x) >= epsilon:
if guess**2 < x :
low = guess
else:
high = guess
guess = (high + low)/2.0
num_guesses += 1
print('num_guesses =', num_guesses)
print(guess, 'is close to square root of', x)
الشريحة 24: النموّ اللوغاريتمي أفضل
- البحث بالقوة الغاشمة (brute force search) عن جذر 54321 استغرق أكثر من 23 مليون تخمين (23M).
- بالبحث بالتنصيف انخفض العدد إلى 30 تخمينًا فقط!
- سنقضي وقتًا أطول في هذا لاحقًا، لكننا نقول إنّ طريقة القوة الغاشمة خطّية في حجم المسألة، لأنّ عدد الخطوات ينمو خطّيًّا كلّما زاد حجم المسألة.
- البحث بالتنصيف لوغاريتمي في حجم المسألة، لأنّ عدد الخطوات ينمو لوغاريتميًا مع حجم المسألة.
| فضاء البحث | التخمين |
|---|---|
| التخمين الأوّل | |
| التخمين الثاني | |
| التخمين -th |
- التخمين يتقارب في رتبة خطوة.
الشريحة 25: لماذا؟
- نصل إلى ، أي بعد تخمينات يكون متبقٍّ تخمين واحد فقط لفحصه.
- هذا يخبرنا بـ بدلالة .
- نحلّ المعادلة لإيجاد .
- هذا يخبرنا بـ بدلالة .
- إذن: يستغرق الأمر خطوة عند استخدام البحث بالتنصيف.
- أي: يستغرق الأمر خطوة عند استخدام البحث بالتنصيف.
الشريحة 26: هل ينجح هذا دائمًا؟
- جرّب تشغيل الشيفرة عند قيمة بحيث .
- إن كان فنحن نبحث من 0 إلى x.
- لكنّنا نعرف أنّ الجذر التربيعي أكبر من x وأصغر من 1.
- عدّل الشيفرة لتختار فضاء البحث حسب قيمة x.
الشريحة 27: جرّب بنفسك: البحث بالتنصيف — الجذر التربيعي عند 0 < x < 1
x = 0.5
epsilon = 0.01
guess = (high + low)/2
while abs(guess**2 - x) >= epsilon:
if guess**2 < x:
low = guess
else:
high = guess
guess = (high + low)/2.0
print(f'{str(guess)} is close to square root of {str(x)}')
الشريحة 28: البحث بالتنصيف — الجذر التربيعي لكل قيم x
x = 0.5
epsilon = 0.01
if x >= 1:
low = 1.0
high = x
else:
low = x
high = 1.0
guess = (high + low)/2
while abs(guess**2 - x) >= epsilon:
if guess**2 < x:
low = guess
else:
high = guess
guess = (high + low)/2.0
print(f'{str(guess)} is close to square root of {str(x)}')
الشريحة 29: بعض الملاحظات
- البحث بالتنصيف يخفض زمن الحساب بشكل جذري — التميّز في اختيار طريقة توليد التخمينات مهم.
- فضاء البحث يصغر بسرعة في البداية ثم ببطء (بالمقياس المطلق، لكن ليس كنسبة من فضاء البحث).
- ينجح في المسائل التي تملك خاصية «الترتيب» (ordering).
الشريحة 30: جرّب بنفسك!
- اكتب شيفرة تنفّذ البحث بالتنصيف لتجد الجذر التكعيبي لمكعّبات موجبة ضمن إيبسيلون معيّن. ابدأ بـ:
cube = 27
epsilon = 0.01
low = 0
high = cube
الشريحة 31: نيوتن–رافسون (Newton-Raphson)
- خوارزمية تقريبية عامّة لإيجاد جذور كثير الحدود (polynomial) في متغيّر واحد:
- أثبت نيوتن ورافسون أنّه إذا كان تقريبًا للجذر، فإنّ
تقريبٌ أفضل؛ حيث هو مشتقّة .
- جرّب أن تستخدم هذه الفكرة لإيجاد الجذر التربيعي لـ x.
- نريد أن نجد بحيث .
- مثلًا: لإيجاد الجذر التربيعي لـ 24، نجد جذر .
الشريحة 32: حدس — رابط (Intuition - LINK)
شريحة تحوي رابطًا ومشاهد متحرّكة لشرح الفكرة بصريًا.
الشريحة 33: برنامج إيجاد الجذر بنيوتن–رافسون
- حالة بسيطة لكثير الحدود:
- المشتقّة الأولى:
- تقول طريقة نيوتن–رافسون: إن كان لدينا تخمين لجذر ، فإنّ تخمينًا أفضل هو:
- وهذا يجد في النهاية تقريبًا للجذر التربيعي لـ !
الشريحة 34: برنامج إيجاد الجذر بنيوتن–رافسون
- طريقة أخرى لتوليد تخمينات يمكننا فحصها؛ وهي كفؤة جدًّا.
epsilon = 0.01
k = 24.0
guess = k/2.0
num_guesses = 0
while abs(guess*guess - k) >= epsilon:
num_guesses += 1
guess = guess - (((guess**2) - k)/(2*guess))
print('num_guesses = ' + str(num_guesses))
print('Square root of ' + str(k) + ' is about ' + str(guess))
الشريحة 35: الخوارزميات التكرارية (Iterative Algorithms)
-
طرق «خمّن ثم افحص» (guess and check) تقوم على إعادة استخدام الشيفرة نفسها.
- استخدم بنية تكرار (looping construct).
- ولّد تخمينات (هذا هو الفارق المهم بين الخوارزميات).
- افحص وتابع.
-
توليد التخمينات:
- التعداد الشامل (exhaustive enumeration).
- خوارزمية التقريب (approximation algorithm).
- البحث بالتنصيف (bisection search).
- نيوتن–رافسون (لإيجاد الجذور).
الشريحة 36: الخلاصة (Summary)
-
في مسائل كثيرة لا يمكننا إيجاد الإجابة الدقيقة.
- نحتاج إلى السعي وراء إجابة «جيدة كفاية» باستخدام التقريب.
-
عند اختبار الأعداد العشرية:
- من المهمّ أن نفهم كيف يمثّلها الحاسوب في النظام الثنائي.
- من المهمّ أن نفهم لماذا نستخدم «قريب كفاية» ولا نستخدم
==.
-
البحث بالتنصيف (bisection search) سريع، لكنّه يعمل في المسائل التي تملك:
- طرفين.
- ترتيبًا في القيم.
- تغذية راجعة على التخمينات (أدنى، أعلى، صحيح، إلخ).
-
نيوتن–رافسون طريقة ذكية لإيجاد جذور كثير الحدود.
الشرائح 37–42: مقدّمة إلى المحاضرة التالية (التفكيك والتجريد)
ملاحظة المترجم: الشرائح من 37 إلى 42 في ملف PDF هي في الحقيقة مقدّمة للمحاضرة 7 (التفكيك والتجريد)، وقد أدرجها MIT في نهاية ملف المحاضرة 6. نُقلت هنا بترتيبها كما في الملف.
الشريحة 37: التفكيك والتجريد (Decomposition and Abstraction)
شريحة عنوان للمقدّمة.
الشريحة 38: تعلّم إنشاء الشيفرة (Learning to Create Code)
- حتى الآن غطّينا آليات اللغة الأساسية: أوامر أوّلية (primitives)، تعبيرات معقّدة، التفريع (branching)، التكرار (iteration).
- من حيث المبدأ، أنت تعرف كلّ ما تحتاجه لإنجاز أيّ شيء يمكن إنجازه بالحساب.
- لكنّنا في الواقع لم نعلّمك شيئًا عن مفهومين من أهمّ المفاهيم في البرمجة…
الشريحة 39: التفكيك والتجريد
- التفكيك (Decomposition): كيف نقسم برنامجًا إلى أجزاء مكتفية ذاتيًا يمكن دمجها لحلّ المسألة الحالية.
الشريحة 40: التفكيك والتجريد
- التجريد (Abstraction): كيف نتجاهل التفصيل غير الضروري.
الشريحة 41: التفكيك والتجريد
- التفكيك (Decomposition):
- في الحالة المثلى يمكن إعادة استخدام الأجزاء في برامج أخرى.
- «مكتفية ذاتيًا» تعني أنّ الأجزاء ينبغي أن تُنجز الحساب باستخدام المدخلات الممرَّرة إليها والعمليات «الأساسية» فقط.
a = 3.14*2.2*2.2
pi = 3.14
r = 2.2
area = pi*r**2
- التجريد (Abstraction):
- يُستخدم لفصل ما يفعله الشيء عن كيفية فعله له فعلًا.
- إنشاء الأجزاء وتجريد التفاصيل يتيح لنا كتابة شيفرة معقّدة مع إخفاء التفاصيل، حتى لا نغمر بذلك التعقيد.
# calculate the area of a circle
الشريحة 42: الفكرة الكبرى (Big Idea)
اجعل الشيفرة سهلة:
- الإنشاء.
- التعديل.
- الصيانة.
- الفهم.
الشريحة 43: MIT OpenCourseWare
- MIT OpenCourseWare
- 6.100L Introduction to Computer Science and Programming Using Python — Fall 2022.
- للاستعلام عن كيفية الاستشهاد بهذه المواد أو عن شروط الاستخدام، زيارة: https://ocw.mit.edu/terms.