🔍 البحث الخطي (Linear Search): أول خوارزمية بحث في حياتك!

مرحباً بك في عالم الخوارزميات! 🎯 تخيل أن لديك كومة من الكتب المبعثرة على الطاولة وتريد العثور على كتاب معين بعنوان "تعلم بايثون". ما هي الطريقة الأبسط التي ستتبعها؟ على الأرجح، ستبدأ بأول كتاب، ثم الثاني، ثم الثالث... وهكذا حتى تجده. هذه الطريقة البسيطة والبديهية هي جوهر خوارزمية البحث الخطي (Linear Search).

في هذا الدرس، سنتعلم معاً كيف نترجم هذه الفكرة البسيطة إلى كود بايثون، خطوة بخطوة ومن دون أي تعقيد. هيا بنا! 🚀


🤔 ما هو البحث الخطي (Linear Search)؟

البحث الخطي هو أبسط خوارزمية بحث على الإطلاق. فكرتها تشبه تماماً البحث يدوياً في قائمة.

تعريف بسيط: هي طريقة للعثور على عنصر معين داخل مجموعة من البيانات (مثل قائمة list) عن طريق فحص كل عنصر على حدة، واحداً تلو الآخر، من البداية إلى النهاية، حتى نجد العنصر المطلوب أو ننتهي من فحص جميع العناصر.

مثال من الحياة الواقعية: عندما تبحث عن اسم صديق في دفتر الهاتف الورقي القديم، وتبدأ من الصفحة الأولى وتقلب الصفحات sequentially حتى تجد الاسم.


✍️ كتابة خوارزمية البحث الخطي في بايثون

لنترجم الفكرة إلى كود. سنفترض أن لدينا قائمة أرقام ونريد البحث عن رقم معين فيها.

الخطوة 1: تعريف الدالة والبيانات

نبدأ بتعريف دالة تأخذ معطيين: القائمة التي نريد البحث فيها، والعنصر الذي نبحث عنه.

def linear_search(my_list, target):
    """
    دالة للبحث الخطي عن عنصر في قائمة.
    المعطيات:
        my_list (list): القائمة التي نبحث فيها.
        target: العنصر الذي نريد إيجاده.
    المخرجات:
        int: مؤشر (index) العنصر إذا وُجد، أو -1 إذا لم يُوجد.
    """
    # الكود سيأتي هنا

الخطوة 2: استخدام حلقة للتكرار على العناصر

نحتاج لزيارة كل عنصر في القائمة. أفضل طريقة لذلك هي استخدام حلقة for مع الدالة range() و len().

def linear_search(my_list, target):
    for index in range(len(my_list)):  # نمر على كل مؤشر في القائمة
        current_item = my_list[index]  # نحصل على العنصر الحالي
        # الخطوة التالية: المقارنة

الخطوة 3: مقارنة العنصر الحالي بالعنصر المطلوب

في كل دورة من الحلقة، نفحص: هل العنصر الحالي current_item يساوي العنصر المطلوب target؟

def linear_search(my_list, target):
    for index in range(len(my_list)):
        current_item = my_list[index]
        if current_item == target:  # 🎯 وجدناها!
            return index  # نرجع مؤشر (موقع) العنصر في القائمة
    # إذا انتهت الحلقة ولم نرجع أي قيمة، يعني العنصر غير موجود
    return -1

🎉 مبروك! لقد كتبت أول خوارزمية بحث لك. الدالة linear_search ترجع index العنصر إذا وجد، أو ترجع الرقم -1 كإشارة على أنه غير موجود (لأن -1 ليس مؤشراً صالحاً في قائمة بايثون).


🧪 دعنا نجرب الأمثلة معاً

لنختبر الدالة التي كتبناها مع قوائم بسيطة.

المثال 1: البحث عن عنصر موجود

# تعريف الدالة linear_search هنا (كما كتبناها أعلاه)

numbers = [10, 23, 45, 70, 11, 15]
result = linear_search(numbers, 70)

print(f"نتيجة البحث عن الرقم 70: {result}")
# المخرجات: نتيجة البحث عن الرقم 70: 3
# لأنه موجود في الموقع (المؤشر) رقم 3 في القائمة.

المثال 2: البحث عن عنصر غير موجود

names = ["أحمد", "سارة", "خالد", "فاطمة"]
result = linear_search(names, "محمد")

print(f"نتيجة البحث عن الاسم 'محمد': {result}")
# المخرجات: نتيجة البحث عن الاسم 'محمد': -1

⏱️ فهم تعقيد خوارزمية البحث الخطي (Time Complexity)

من المهم أن نفهم كفاءة الخوارزمية. دعنا نسأل: "في أسوأ حالة، كم مرة ستقوم الدالة بمقارنة العناصر؟"

  • أفضل حالة: العنصر المطلوب هو أول عنصر في القائمة. ستتم مقارنة واحدة فقط. O(1).
  • أسوأ حالة: العنصر المطلوب هو آخر عنصر في القائمة، أو غير موجود فيها أصلاً. سنضطر لمقارنة كل عنصر في القائمة. إذا كان عدد العناصر n، فسنقوم بـ n مقارنة.
  • متوسط الحالة: في المتوسط، سنبحث عن حوالي نصف القائمة قبل أن نجد العنصر.

نقول أن التعقيد الزمني للبحث الخطي في أسوأ حالة هو O(n) (تُقرأ "أوه إن"). هذا يعني أن الوقت المستغرق ينمو بشكل خطي (مباشر) مع نمو حجم القائمة n. إذا تضاعف حجم القائمة، يتضاعف وقت البحث في أسوأ الحالات.


✅ مميزات وعيوب البحث الخطي

الميزة ✅ العيب ❌
بسيط جداً للفهم والكتابة. بطيء للقوائم الكبيرة جداً (تعقيد O(n)).
لا يتطلب ترتيب البيانات مسبقاً. غير عملي عندما يكون لديك كميات هائلة من البيانات.
يعمل على أي نوع من القوائم (مرتبة أو غير مرتبة).
مثالي للقوائم الصغيرة أو للبحث لمرة واحدة.

🎯 خلاصة الدرس

تعلمنا اليوم الأساسيات القوية:

  1. البحث الخطي هو فحص كل عنصر في القائمة بالتسلسل حتى نجد المطلوب.
  2. ننفذه في بايثون باستخدام دالة تحتوي على حلقة for وجملة شرطية if.
  3. نرجع index العنصر إذا وجد، أو -1 إذا لم يوجد.
  4. تعقيده الزمني هو O(n)، مما يجعله بسيطاً لكن ليس الأسرع للبيانات الكبيرة.

تذكر: كل الخوارزميات المعقدة مبناة على أفكار بسيطة مثل هذه. أنت الآن على الطريق الصحيح! 💪


🔮 ماذا سنتعلم في الدرس القادم؟

لقد رأينا أن البحث الخطي قد يكون بطيئاً إذا كانت قائمتنا كبيرة. ماذا لو أخبرتك أن هناك طريقة أسرع بكثير؟ 🚀 في الدرس القادم، سنتعرف على خوارزمية بحث أكثر ذكاءً وفعالية: البحث الثنائي (Binary Search). ولكن هناك شرط واحد لتعمل هذه الخوارزمية السحرية: يجب أن تكون القائمة مرتبة من الأصغر إلى الأكبر أولاً! هل أنت مستعد للقفزة التالية في عالم الخوارزميات؟