محتويات الدورة

هياكل البيانات والخوارزميات / العقود والتسلسلات والبحث / الدرس 6

البحث الثنائي وثوابت الحدود

اشتق بحث الحد الأدنى من ثابت يقسم البيانات، وعالج التكرار والغياب دون تخمين قواعد تحديث المنتصف.

وقت القراءة التقريبي: 5 دقيقةتاريخ مراجعة المحتوى
في هذه الصفحة

أهداف الدرس

  • يتطلب البحث شرط ترتيب مثبتا مسبقا
  • احفظ منطقة مجهولة نصف مفتوحة
  • يعد التنصيف المقارنات، وليس كل العمل الممكن

يتطلب البحث شرط ترتيب مثبتا مسبقا

لا يستبعد البحث الثنائي منطقة كبيرة إلا لأن الترتيب يثبت أن تلك المنطقة لا يمكن أن تحوي الحد المطلوب. يبطل التسلسل غير المرتب هذا الاستدلال، حتى لو نجحت بعض الأمثلة المختارة مصادفة. حدّد مفتاح المقارنة واتجاهها وسياسة التعامل مع التكرار. يعيد بحث الحد الأدنى أول موضع لا تقل قيمته عن الهدف، وقد يساوي هذا الموضع طول التسلسل عندما تكون جميع القيم أصغر من الهدف. إنه حد إدراج، وليس دليلا تلقائيا على وجود القيمة. وتحتاج مقارنة القيمة عند ذلك الموضع إلى فحص مستقل للحدود وللمساواة.

احفظ منطقة مجهولة نصف مفتوحة

يحافظ البرنامج الأول على أن جميع المواضع قبل lo تحوي قيما أصغر من target، وأن جميع المواضع عند hi أو بعده تحوي قيما لا تقل عنه. ولا تبقى مجهولة سوى المنطقة [lo,hi). عندما تكون values[mid] أصغر، يمكن استبعاد mid وكل ما قبله بجعل lo=mid+1. وإلا بقي mid مرشحا للحد، فنضع hi=mid. يقلل كل تكرار hi-lo حصرا، فيثبت الانتهاء. وعندما يصبح lo==hi لا يبقى موضع مجهول، ويُثبت حد التقسيم. يفسر هذا الثابت التحديثات بدلا من الاعتماد على أنماط زيادة الواحد المحفوظة.

python مثال مستقل لخوارزمية بلغة Python 3، يستخدم بيانات محلية مصطنعة.
def lower_bound(values, target):
    lo, hi = 0, len(values)
    while lo < hi:
        mid = lo + (hi - lo) // 2
        if values[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo

print(lower_bound([1, 3, 3, 8], 3))
print(lower_bound([1, 3, 3, 8], 9))
print(lower_bound([], 3))
الناتج المتوقع
1
4
0

تبدأ القيم المكررة 3 عند الفهرس 1. ويكون موضع إدراج الهدف 9 بعد العنصر الأخير، ويملك التسلسل الفارغ حد إدراج 0.

يعد التنصيف المقارنات، وليس كل العمل الممكن

ينخفض حجم المجال المجهول إلى النصف تقريبا في كل تكرار، فتكون المقارنات لوغاريتمية لبيانات مرتبة تدعم الوصول العشوائي. أما في بنية مرتبطة فقد يتطلب الوصول إلى المنتصف مرورا؛ ولذلك لا يحفظ نسخ شكل الحلقة كلفة الوصول نفسها. ويضيف فرز مدخل غير مرتب أولا عملا خاصا به، وقد لا يكون ضروريا لبحث واحد. وقد يكلف استخراج المفتاح أيضا إذا تكرر. حلل التهيئة والمقارنات والوصول والناتج معا وفقا للتمثيل المختار. تتجنب أعداد Python الصحيحة فيضان حساب المنتصف ذي العرض الثابت، لكن الصيغة lo+(hi-lo)//2 تعبّر بوضوح عن المجال المتقلص، وتنتقل بأمان أكبر إلى بيئات الأعداد محدودة العرض.

استخدم حدين معا لعد القيم المتكررة

يستخدم البرنامج الثاني دوال bisect القياسية لإيجاد الحدين الأيسر والأيمن للقيم المتساوية. ويعد الفرق بينهما مرات الظهور دون مسح المقطع المتساوي كله. تعيد المكتبة مواضع إدراج، وتعتمد أساسا على مقارنات الترتيب، ولا تنفذ بحث عضوية عاما قائما على المساواة. يجب أن يبقى المدخل مرتبا وثابتا طوال العملية. وقد يظل الإدراج عند ذلك الحد في قائمة Python محتاجا إلى تحريك عناصر كثيرة؛ ولذلك لا تكون كلفة insort الكلية لوغاريتمية لمجرد أنها تجد الموضع بطريقة لوغاريتمية. تظل كلفة البحث وكلفة التعديل منفصلتين.

python مثال مستقل لخوارزمية بلغة Python 3، يستخدم بيانات محلية مصطنعة.
from bisect import bisect_left, bisect_right

def occurrences(values, target):
    return bisect_right(values, target) - bisect_left(values, target)

values = [1, 3, 3, 8]
print(occurrences(values, 3))
print(occurrences(values, 4))
position = bisect_left(values, 4)
print(position < len(values) and values[position] == 4)
الناتج المتوقع
2
0
False

موضع إدراج القيمة 4 صحيح بوصفه حدا، لكنه لا يعني وجود 4. ويعيد الحدان معا عدد ظهور صفريا بصورة صحيحة.

عمّم البحث فقط عندما يكون شرط الاختبار رتيبا

يمكن تطبيق بحث الحدود على شرط يكون كاذبا حتى حد معين ثم صادقا بعده، مثل عتبة جدوى موثقة. أثبت هذه الرتابة قبل استعمال النمط. لا تكفي ملاحظات نجاح وفشل اعتباطية، وقد تبطل الأوزان السالبة الافتراضات في بعض مسائل السعة. حدّد مجال البحث، وتأكد من وجود الإجابة، أو أدرج قيمة صريحة تدل على عدم العثور عليها. لا تخلط برهانا لمجال مغلق بتحديثات لمجال نصف مفتوح. بحث الحد مبرهنة عن تقسيم، وليس حيلة تجعل كل مسألة تحسين عددي لوغاريتمية.

افحص خصائص التقسيم بصورة شاملة على مدخلات صغيرة

ولّد مصفوفات صغيرة غير متناقصة تحوي قيما مكررة، وقارن lower_bound بـ bisect_left أو بمرجع خطي مباشر. لكل إجابة p تحقّق من أن كل قيمة قبلها أصغر من الهدف، وكل قيمة ابتداء منها لا تقل عنه. أدرج أهدافا أصغر من جميع القيم، ومساوية لقيم متكررة، وواقعة بين قيم، وأكبر من جميع القيم، إضافة إلى المدخل الفارغ. افحص الانتهاء بحجة تقلص المجال، لا بمهلة تنفيذ وحدها. تثبت الاختبارات سلوك الحدود تحت الشرط المسبق، ولا تجعل المدخل غير المرتب صالحا. وافصل فرض الشرط المسبق عن البحث اللوغاريتمي إذا كان التحقق من الترتيب يحتاج إلى مسح كامل.

الأخطاء الشائعة وإصلاحها

استمرار البحث إلى ما لا نهاية عند وجود عنصرين

لماذا يحدث؟
لا يتقلص المجال المجهول حصرا في أحد الفرعين.
كيف تصلحه؟
اشتق التحديثات من الثابت، وافحص المقدار hi-lo.
كيف تتجنب تكراره؟
تتبّع مجالات من عنصرين ومجالات من عنصر واحد.

الإبلاغ عن حد إدراج بوصفه إثبات عضوية

لماذا يحدث؟
لم يُفحص الفهرس المعاد للتحقق من الحدود والمساواة.
كيف تصلحه؟
تحقّق من index<len ومن مقارنة القيمة قبل إعلان وجودها.
كيف تتجنب تكراره؟
اختبر أهدافا بين القيم وأهدافا أعلى من القيمة الكبرى.

تطبيق البحث الثنائي على بيانات غير مرتبة

لماذا يحدث؟
افتُرض شرط التقسيم المسبق من مثال ناجح واحد.
كيف تصلحه؟
أثبت الترتيب، أو اختر خوارزمية أخرى.
كيف تتجنب تكراره؟
وثّق مفتاح الفرز وملكية لقطة المدخل.

اختبر فهمك

1. لماذا ينقل فرع «أصغر من الهدف» المتغير lo إلى mid+1؟

اعرض الحل المشروح

ثبت أن قيمة الموضع الأوسط المختبر أصغر من المطلوب، فلا يمكن أن يكون أول موضع لا تقل قيمته عن الهدف. يحفظ استبعاده التقسيم الأيسر ويضمن التقدم. أما إبقاء lo عند mid فقد يعيد المنتصف نفسه عندما لا يبقى سوى مرشحين.

2. هل تبلغ الكلفة الكلية للإدراج في قائمة Python باستخدام bisect مقدار O(log n)؟

اعرض الحل المشروح

لا. يستخدم إيجاد الحد عددا لوغاريتميا من المقارنات، لكن الإدراج قد يزيح عددا خطيا من المراجع. احسب المرحلتين معا. وعند الحاجة إلى تحديثات مرتبة ديناميكية كثيرة، انظر في بنية يناسب عقد تحديثها الكامل نمط العمل، بدلا من اقتباس كلفة مرحلة البحث وحدها.

أهم ما تعلمته

  • يتطلب البحث شرط ترتيب مثبتا مسبقا
  • افحص خصائص التقسيم بصورة شاملة على مدخلات صغيرة

المراجع الرسمية