السلام عليكم يا جماعة الخير، معكم أخوكم أبو عمر.
قبل سنوات طويلة، في بداية طريقي بعالم البرمجة، كنت أعمل على نظام لإدارة مستودع ضخم لشركة أدوية. كان المستودع يحتوي على مئات الآلاف من الأصناف، ولكل صنف رقم تسلسلي فريد (Serial Number). واحدة من المهام المطلوبة كانت إنشاء شاشة بسيطة للموظف، يدخل فيها الرقم التسلسلي للدواء، والنظام يتأكد إذا كان هذا الرقم موجوداً في قاعدة البيانات الضخمة أم لا.
بكل بساطة، كتبت الكود. كانت الخوارزمية التي استخدمتها بديهية جداً: ابدأ من أول دواء في القائمة، قارن رقمه بالرقم المطلوب، إذا لم يتطابق، انتقل للتالي، وهكذا حتى نهاية القائمة. رفعت البرنامج، وجربه الموظف… فضغط “بحث”… وانتظر… وانتظر… وأنا جالس جنبه أشرب كاسة الشاي بالمرمية وأراقب الشاشة المتجمدة. بعد ما يقارب الدقيقة، ظهرت النتيجة! تخيلوا؟ دقيقة كاملة لمعرفة هل الدواء موجود أم لا!
كانت كارثة بكل معنى الكلمة. شعرت بإحراج شديد، وقلت في نفسي: “معقول كل هالتعب وبالآخر يطلع البرنامج بهالبطء؟”. في ذلك اليوم، تعلمت درساً من أهم الدروس في مسيرتي: البساطة والبداهة في البرمجة ليست دائماً الحل الأفضل، خصوصاً عندما يتعلق الأمر بالبيانات الكبيرة.
هذه القصة هي مدخلنا لواحدة من أجمل وأقوى الخوارزميات التي يجب على كل مبرمج أن يتقنها: خوارزمية البحث الثنائي (Binary Search).
المشكلة: جحيم البحث الخطي (Linear Search)
ما فعلته في قصتي يسمى تقنياً “البحث الخطي”. الفكرة، كما ذكرت، هي أن تمر على كل عنصر في القائمة واحداً تلو الآخر وتقارنه بالقيمة التي تبحث عنها. إنه مثل البحث عن كلمة في قاموس لا تعرف ترتيب الحروف فيه، فتضطر لفتحه من الصفحة الأولى وقراءة كل كلمة حتى تجد ضالتك. بسيط؟ نعم. فعال؟ أبداً!
لماذا هو سيء؟
تكمن المشكلة في “التعقيد الزمني” (Time Complexity). في البحث الخطي، أسوأ سيناريو هو أن يكون العنصر الذي تبحث عنه هو آخر عنصر في القائمة، أو ألا يكون موجوداً أصلاً. في هذه الحالة، ستضطر للمرور على كل عناصر القائمة.
- إذا كانت القائمة تحتوي على 1,000 عنصر، قد تحتاج إلى 1,000 خطوة.
- إذا كانت تحتوي على 1,000,000 عنصر (مليون)، قد تحتاج إلى 1,000,000 خطوة!
هذا ما يسمى بالتعقيد O(n)، حيث n هو عدد العناصر. كلما زاد حجم البيانات، زاد الوقت بشكل خطي ومباشر. وهذا بالضبط ما حدث معي.
الحل السحري: البحث الثنائي (Binary Search)
بعد تلك التجربة المحرجة، جلست مع مهندس أقدم مني خبرة وشرحت له المشكلة. ابتسم وقال لي جملة لن أنساها: “يا أبو عمر، ليش بتدوّر زي الأعمى؟ ليش ما ترتّب بياناتك وتدوّر زي ما بتدوّر على اسم في دليل التلفونات؟”.
كانت تلك هي لحظة التنوير! عندما تبحث في قاموس أو دليل هاتف، أنت لا تبدأ من أول صفحة. أنت تفتح من المنتصف تقريباً، تنظر إلى الحرف، وتقرر: هل كلمتك قبل هذا الحرف أم بعده؟ بناءً على قرارك، أنت تتجاهل نصف القاموس بالكامل بلمحة بصر! ثم تكرر العملية مع النصف المتبقي. هذا بالزبط هو جوهر البحث الثنائي.
الشرط الأساسي الذي لا تنازل عنه
نصيحة من أبو عمر: البحث الثنائي لا يعمل إلا على البيانات المرتبة (Sorted). إذا كانت بياناتك غير مرتبة، يجب عليك ترتيبها أولاً. قد يبدو هذا خطوة إضافية، ولكن إذا كنت ستبحث في هذه البيانات مراراً وتكراراً، فإن تكلفة الترتيب مرة واحدة لا تقارن بالوقت الذي ستوفره في كل عملية بحث لاحقة.
كيف يعمل البحث الثنائي خطوة بخطوة؟
لنفترض أن لدينا قائمة أرقام مرتبة ونريد البحث عن الرقم 23:
[4, 8, 10, 15, 18, 21, 23, 29, 34, 40, 45]
- الخطوة 1: جد المنتصف.
القائمة بها 11 عنصراً. العنصر الأوسط هو في الموقع الخامس (الفهرس 5)، وقيمته21. - الخطوة 2: قارن.
هل23(هدفنا) يساوي21(المنتصف)؟ لا.
هل هو أكبر أم أصغر؟23أكبر من21. - الخطوة 3: تجاهل نصف القائمة.
بما أن هدفنا أكبر من المنتصف، فمن المستحيل أن يكون في النصف الأول من القائمة (لأنها مرتبة). لذلك، نتجاهل كل شيء من21وأقل. بحثنا الآن محصور في:
[23, 29, 34, 40, 45] - الخطوة 4: كرر العملية.
جد منتصف القائمة الجديدة. العنصر الأوسط هو34.
قارن:23أصغر من34.
تجاهل النصف الثاني. بحثنا الآن محصور في:
[23, 29] - الخطوة 5: كرر مرة أخرى.
جد منتصف القائمة الجديدة. لنأخذ العنصر الأول23.
قارن:23يساوي23.
لقد وجدنا العنصر! 🥳
لاحظ أننا وجدنا العنصر في 3 خطوات فقط في قائمة من 11 عنصراً. البحث الخطي كان سيحتاج 7 خطوات!
مثال برمجي (باستخدام Python)
هذا هو تطبيق خوارزمية البحث الثنائي بشكلها التكراري (Iterative)، وهو المفضل لدي لأنه لا يستهلك ذاكرة المكدس (Stack Memory) مثل الحل العودي (Recursive).
def binary_search(sorted_list, target):
"""
تبحث هذه الدالة عن قيمة الهدف في قائمة مرتبة باستخدام البحث الثنائي.
:param sorted_list: القائمة المرتبة التي سنبحث فيها.
:param target: القيمة التي نبحث عنها.
:return: فهرس العنصر إذا تم العثور عليه، وإلا تُرجع -1.
"""
low = 0
high = len(sorted_list) - 1
while low target:
# التخمين كان أكبر من الهدف، تجاهل النصف الأيمن
high = mid - 1
else:
# التخمين كان أصغر من الهدف، تجاهل النصف الأيسر
low = mid + 1
# إذا انتهت الحلقة ولم نجد العنصر
return -1
# --- مثال على الاستخدام ---
my_list = [4, 8, 10, 15, 18, 21, 23, 29, 34, 40, 45]
target_number = 23
index = binary_search(my_list, target_number)
if index != -1:
print(f"تم العثور على الرقم {target_number} في الفهرس: {index}")
else:
print(f"الرقم {target_number} غير موجود في القائمة.")
قوة اللوغاريتمات: لماذا هو أسرع بشكل لا يصدق؟
التعقيد الزمني للبحث الثنائي هو O(log n). قد يبدو هذا المصطلح “لوغاريتم” مخيفاً، لكن فكرته بسيطة جداً. إنه يعني: “كم مرة يمكنني قسمة عدد العناصر على 2 حتى أصل إلى 1؟”.
دعونا نقارن بين O(n) و O(log n) بالأرقام:
- قائمة من 1,024 عنصر:
- البحث الخطي: قد يحتاج حتى 1,024 خطوة.
- البحث الثنائي: يحتاج 10 خطوات فقط (لأن 210 = 1024).
- قائمة من 1,048,576 عنصر (حوالي مليون):
- البحث الخطي: قد يحتاج حتى 1,048,576 خطوة.
- البحث الثنائي: يحتاج 20 خطوة فقط (لأن 220 ≈ مليون).
- قائمة من مليار عنصر (1,000,000,000):
- البحث الخطي: مليار خطوة (قد يستغرق دقائق أو ساعات).
- البحث الثنائي: حوالي 30 خطوة فقط (يستغرق أجزاء من الثانية).
الفرق هائل! كلما زادت البيانات، أصبح البحث الثنائي أكثر كفاءة بشكل لا يقارن. وهذا هو الدرس الذي تعلمته بالطريقة الصعبة في قصة مستودع الأدوية.
الخلاصة يا جماعة الخير 💡
البحث الخطي يشبه البحث عن إبرة في كومة قش بفحص كل قشة على حدة. أما البحث الثنائي، فهو أشبه بامتلاك جهاز كشف معادن يخبرك في كل مرة بأي نصف من الكومة عليك أن تبحث، مما يسرّع العملية بشكل مذهل.
تذكر دائماً:
- هل بياناتك كبيرة؟
- هل ستبحث فيها بشكل متكرر؟
- هل يمكنك ترتيبها مرة واحدة؟
إذا كانت إجابتك “نعم” على هذه الأسئلة، فإن استخدام البحث الثنائي ليس خياراً، بل هو ضرورة حتمية لتحسين أداء برنامجك ومنع تلك الشاشات المتجمدة المحرجة.
نصيحة أبو عمر الأخيرة: لا تستهن أبداً بأساسيات علوم الحاسوب مثل الخوارزميات وهياكل البيانات. هي ليست مجرد نظريات أكاديمية، بل هي الأدوات الحقيقية التي تفرق بين المبرمج العادي والمبرمج المحترف الذي يبني أنظمة سريعة وفعالة وقادرة على التوسع. استثمر وقتاً في فهمها، وسترى أثر ذلك في كل سطر كود تكتبه.
أتمنى أن تكون هذه المقالة مفيدة لكم. بالتوفيق في رحلتكم البرمجية!