مساراتي كانت تتشابك في متاهة: كيف أنقذتني خوارزمية ‘دايكسترا’ من جحيم أقصر الطرق الخاطئة؟

يا جماعة الخير، السلام عليكم ورحمة الله. معكم أخوكم أبو عمر.

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

في البداية، قلت في نفسي: “الموضوع بسيط، ليش نعقّدها؟”. بنيت نظاماً يعتمد على ما نسميه “الخوارزمية الجشعة” (Greedy Algorithm). الفكرة كانت أن السائق عند كل تقاطع، يختار الطريق الأقصر الذي يليه مباشرةً. منطقي، مش هيك؟ казалось бы, логично, не так ли? (It seemed logical, didn’t it?). في الأيام الأولى، ومع عدد طلبات قليل، كانت الأمور تبدو على ما يرام. لكن مع نمو الشركة وزيادة تعقيد شبكة الطرق، بدأت الكارثة تتكشف.

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

ما هي “الرسوم البيانية” (Graphs) أصلاً؟

قبل أن نغوص في حل دايكسترا، دعونا نرجع خطوة للوراء. لكي يفهم الكمبيوتر شبكة طرق أو أي شبكة أخرى، نحن بحاجة لتمثيلها بطريقة يفهمها. هذه الطريقة هي ما نسميه “الرسم البياني” أو Graph.

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

  • العُقد (Nodes/Vertices): وهي النقاط على الخريطة. في مثالنا، كل مدينة هي “عُقدة”. في شبكة كمبيوتر، كل جهاز هو “عُقدة”.
  • الحواف (Edges): وهي الخطوط التي تصل بين هذه النقاط. في مثالنا، كل طريق بين مدينتين هو “حافة”.

لكن هذا ليس كل شيء. الطرق ليست متساوية؛ بعضها أطول من بعض، وبعضها يستغرق وقتاً أكثر بسبب الازدحام. هنا يأتي دور “الحواف الموزونة” (Weighted Edges)، حيث نعطي لكل حافة “وزناً” أو “تكلفة” (Cost). هذه التكلفة يمكن أن تكون مسافة (بالكيلومتر)، أو وقتاً (بالدقائق)، أو حتى تكلفة مالية (رسوم طريق). وهذا هو المفتاح لفهم مشكلتنا.

المشكلة: سحر “الطمع” الخادع

خوارزميتي الأولى كانت “جشعة” أو “طماعة” (Greedy). كانت تنظر فقط إلى الخطوة التالية المباشرة. إذا كان أمامي طريقان، واحد طوله 5 كم والآخر 7 كم، ستختار حتمًا الطريق ذا الـ 5 كم، دون أي اعتبار لما سيأتي بعده.

المشكلة أن هذا الطريق القصير قد يقودك إلى طريق طويل جداً لاحقاً. انظر لهذا المثال البسيط:

للوصول من النقطة A إلى النقطة D:

  • المسار 1 (الجشع): A -> B (تكلفة 2) -> C (تكلفة 10) -> D (تكلفة 2). المجموع: 14. الخوارزمية الجشعة ستختار A -> B لأن تكلفتها (2) أقل من A -> C (التي تكلفتها 4).
  • المسار 2 (الأمثل): A -> C (تكلفة 4) -> D (تكلفة 2). المجموع: 6.

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

المنقذ: دخول السيد إدسخر دايكسترا

في عام 1956، كان العالم الهولندي إدسخر دايكسترا يفكر في كيفية إيجاد أقصر طريق بين مدينتين في هولندا. في 20 دقيقة فقط وهو يحتسي القهوة مع زوجته، وضع أسس خوارزميته العبقرية التي لا نزال نستخدمها حتى اليوم في كل شيء تقريباً: من خرائط جوجل، إلى توجيه حزم البيانات على الإنترنت، وحتى في تصميم شبكات الذكاء الاصطناعي.

الفكرة الجوهرية لدايكسترا

فكرة دايكسترا ليست طماعة، بل هي “منهجية” و”متفائلة بحذر”. لا تتخذ قراراً وتنسى الباقي. بدلاً من ذلك، هي تحتفظ بقائمة من المسارات المحتملة وتستكشفها بذكاء. تقول الخوارزمية:

  1. ابدأ من نقطة البداية (المصدر). المسافة إليها هي صفر، والمسافة إلى كل النقاط الأخرى هي “لانهاية” (لأننا لم نصلها بعد).
  2. احتفظ بقائمة لـ “العُقد التي لم تتم زيارتها بعد”، واختر منها العُقدة ذات أقصر مسافة معروفة حالياً من المصدر.
  3. عند هذه العُقدة، انظر إلى كل جيرانها. لكل جار، احسب المسافة الجديدة (المسافة للوصول إلى العقدة الحالية + المسافة من العقدة الحالية إلى هذا الجار).
  4. إذا كانت هذه المسافة الجديدة أقصر من أي مسافة أخرى سجلناها لهذا الجار من قبل، قم بتحديثها.
  5. ضع علامة “تمت الزيارة” على العُقدة الحالية وأزلها من قائمة “لم تتم الزيارة”.
  6. كرر الخطوات من 2 إلى 5 حتى تتم زيارة كل العُقد أو حتى نصل إلى وجهتنا.

بهذه الطريقة، تضمن الخوارزمية أنها في كل خطوة، تكون المسافة التي سجلتها لأي عقدة تمت زيارتها هي بالفعل أقصر مسافة ممكنة للوصول إليها من المصدر. لا يوجد “يا ليتني اخترت الطريق الآخر!”.

مثال عملي مع الكود

هالحكي النظري جميل، لكن “الشغل بنعرفه من فعله”. خلينا نأخذ مثالاً عملياً ونكتب الكود. تخيل لدينا شبكة طرق بسيطة بين بعض المدن الفلسطينية (الأوزان هنا هي مجرد أرقام افتراضية للتوضيح).

  • القدس -> أريحا (10)
  • القدس -> رام الله (5)
  • رام الله -> نابلس (15)
  • أريحا -> نابلس (12)
  • رام الله -> الخليل (20)

نريد إيجاد أقصر مسار من القدس إلى كل المدن الأخرى.

تمثيل الرسم البياني في بايثون

أولاً، سنمثل هذه الشبكة باستخدام قاموس (Dictionary) في بايثون:


graph = {
    'القدس': {'أريحا': 10, 'رام الله': 5},
    'أريحا': {'القدس': 10, 'نابلس': 12},
    'رام الله': {'القدس': 5, 'نابلس': 15, 'الخليل': 20},
    'نابلس': {'أريحا': 12, 'رام الله': 15},
    'الخليل': {'رام الله': 20}
}

تطبيق خوارزمية دايكسترا

سنستخدم مكتبة heapq في بايثون، والتي توفر لنا بنية بيانات فعّالة جداً تسمى “طابور الأولوية” (Priority Queue) لاختيار العقدة ذات المسافة الأقل في كل مرة بكفاءة عالية.


import heapq

def dijkstra(graph, start_node):
    # 1. التهيئة
    # قاموس لتخزين أقصر مسافة من المصدر لكل عقدة
    distances = {node: float('infinity') for node in graph}
    distances[start_node] = 0
    
    # طابور الأولوية لتخزين (المسافة, العقدة)
    priority_queue = [(0, start_node)]
    
    # قاموس لتتبع المسار
    shortest_path_tree = {}

    # 2. الحلقة الرئيسية
    while priority_queue:
        # استخراج العقدة ذات المسافة الأقصر
        current_distance, current_node = heapq.heappop(priority_queue)

        # إذا وجدنا مساراً أقصر بالفعل، نتجاهل هذا المسار
        if current_distance > distances[current_node]:
            continue

        # 3. استكشاف الجيران
        for neighbor, weight in graph[current_node].items():
            distance = current_distance + weight

            # 4. تحديث المسافة إذا وجدنا مساراً جديداً أقصر
            if distance  ".join(reversed(path))

print("nأقصر مسار من القدس إلى نابلس:")
print(get_path(path, 'القدس', 'نابلس'))

ماذا يفعل الكود؟

عند تشغيل هذا الكود، سيبدأ من “القدس” (المسافة 0). يرى أن لديه جارين: “أريحا” (مسافة 10) و”رام الله” (مسافة 5). سيختار “رام الله” لأنها الأقرب (5). من “رام الله”، يرى “نابلس” (5+15=20) و”الخليل” (5+20=25). الآن، أقرب عقدة غير مكتشفة هي “أريحا” (مسافتها 10 من البداية). من “أريحا”، يرى “نابلس” (10+12=22). انتظر! لقد سجلنا مسافة 20 للوصول إلى نابلس عبر رام الله. بما أن 20 ليست أقل من 22، فلن يقوم بتحديث المسار إلى نابلس. سيستمر هكذا حتى يجد أقصر مسار للجميع.

الناتج سيكون:

  • القدس -> رام الله: 5
  • القدس -> أريحا: 10
  • القدس -> نابلس: 17 (عبر القدس -> أريحا -> نابلس، وليس عبر رام الله)
  • القدس -> الخليل: 25 (عبر القدس -> رام الله -> الخليل)

لاحظ كيف أن الخوارزمية اكتشفت أن الطريق إلى نابلس عبر أريحا (10+12=22) هو أطول من الطريق عبر رام الله (5+15=20). لا، لحظة، هناك خطأ في حسابي اليدوي. دعنا نرى:
القدس -> رام الله (5) -> نابلس (15) = 20
القدس -> أريحا (10) -> نابلس (12) = 22
إذن المسار الأقصر هو عبر رام الله. الكود سيصل لهذه النتيجة الصحيحة. خطأي اليدوي يثبت أهمية وجود خوارزمية دقيقة!

تحديث بعد المراجعة: في مثالي الأول، كتبت أن المسافة من رام الله لنابلس 15، ومن أريحا لنابلس 12. دعنا نعدّل مثالنا قليلاً لنجعل الاختيار أكثر إثارة. لو كانت المسافة من رام الله لنابلس 15، ومن أريحا لنابلس 6.
الآن:
– المسار عبر رام الله: القدس -> رام الله (5) + رام الله -> نابلس (15) = 20.
– المسار عبر أريحا: القدس -> أريحا (10) + أريحا -> نابلس (6) = 16.
في هذه الحالة، ستكتشف دايكسترا أن المسار عبر أريحا (رغم أن الخطوة الأولى إليه أطول) هو الأقصر إجمالاً. هذا هو جمالها وقوتها.

متى لا تكون دايكسترا هي الحل؟

لكل بطل نقطة ضعف. نقطة ضعف دايكسترا هي “الأوزان السالبة” (Negative Weights). إذا كان لديك رسم بياني حيث الانتقال عبر حافة معينة يمنحك “رصيداً” بدلاً من أن يكلفك (أي الوزن سالب)، فإن منطق دايكسترا ينهار تماماً. هي تفترض أنه بمجرد زيارة عقدة، فقد وجدت أقصر طريق إليها. لكن مع الأوزان السالبة، قد تكتشف لاحقاً حلقة سالبة يمكن أن تقلل المسافة إلى ما لا نهاية. في هذه الحالات، نلجأ إلى خوارزميات أخرى مثل “Bellman-Ford” أو “Floyd-Warshall”.

نصائح من خبرة أبو عمر 🧔

نصيحة 1: اختر هياكل البيانات الصحيحة

سرعة خوارزمية دايكسترا تعتمد بشكل هائل على كيفية اختيارك “للعقدة التالية ذات المسافة الأقل”. استخدام مصفوفة بسيطة والبحث فيها في كل مرة سيجعل الخوارزمية بطيئة جداً (تعقيد O(V²)). استخدام “طابور الأولوية” (Min-Heap) يحسن الأداء بشكل جذري (إلى O(E log V))، وهذا هو المعيار في التطبيقات الحقيقية. لا تستهن بهذه النقطة!

نصيحة 2: دايكسترا ليست للخرائط فقط

أجمل ما في الخوارزميات هو قابليتها للتطبيق في مجالات مختلفة. استخدمت دايكسترا وما شابهها في:

  • توجيه الشبكات (Network Routing): لإيجاد أسرع مسار لحزم البيانات عبر الإنترنت. هنا “التكلفة” قد تكون زمن الاستجابة (latency).
  • تحليل الشبكات الاجتماعية: لحساب “درجات الانفصال” بين شخصين.
  • تسلسل المهام (Dependency Resolution): إذا كانت المهمة B تعتمد على المهمة A، يمكنك نمذجة هذا كرسم بياني وإيجاد المسار “الأقل تكلفة” لإنجاز مشروع ما.

نصيحة 3: ارسمها قبل أن تبرمجها

قبل كتابة سطر كود واحد، أحضر ورقة وقلماً (أو افتح أي أداة رسم) وارسم الرسم البياني الخاص بمشكلتك. ضع العقد، والحواف، والأوزان. تتبع الخوارزمية يدوياً على مثال بسيط. هذه الدقائق القليلة ستوفر عليك ساعات من تصحيح الأخطاء لاحقاً، وستمنحك فهماً أعمق للمشكلة. زي ما بنحكي، “الفهم نص الشغل”.

الخلاصة: من المتاهة إلى الطريق الواضح 🗺️

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

أتمنى لكم كل التوفيق في رحلتكم مع الخوارزميات. والسلام عليكم.

أبو عمر

سجل دخولك لعمل نقاش تفاعلي

كافة المحادثات خاصة ولا يتم عرضها على الموقع نهائياً

آراء من النقاشات

لا توجد آراء منشورة بعد. كن أول من يشارك رأيه!

آخر المدونات

​معمارية البرمجيات

خدماتي كانت متشابكة كخيوط العنكبوت: كيف أنقذتني ‘المعمارية القائمة على الأحداث’ (EDA) من جحيم تأثير الدومينو؟

أنا أبو عمر، وفي هذه المقالة أشارككم قصة حقيقية من معاناتي مع الأنظمة المتشابكة وتأثير الدومينو المدمر. سأشرح لكم كيف كانت "المعمارية القائمة على الأحداث"...

30 مارس، 2026 قراءة المزيد
تجربة المستخدم والابداع البصري

كنت أبني ميزات لا يستخدمها أحد: كيف أنقذتني ‘خرائط رحلة المستخدم’ من جحيم الجهد الضائع؟

هل شعرت يومًا أنك تبني تطبيقات رائعة... لكن لا أحد يستخدم ميزاتها؟ هذا ما حدث معي بالضبط. في هذه المقالة، أشارككم كيف أنقذتني أداة بسيطة...

30 مارس، 2026 قراءة المزيد
الشبكات والـ APIs

تطبيقي كان يسأل الخادم كل ثانية: كيف أنقذتني ‘خطافات الويب’ (Webhooks) من جحيم استنزاف الموارد؟

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

30 مارس، 2026 قراءة المزيد
الحوسبة السحابية

خوادمي كانت تعمل 24/7: كيف أنقذتني “الحوسبة بدون خوادم” (Serverless) من جحيم الفواتير؟

أشارككم قصتي مع الفواتير السحابية المنتفخة لمشروعي الجانبي، وكيف كان التحول إلى معمارية "الحوسبة بدون خوادم" (Serverless) هو طوق النجاة الذي وفر عليّ المال والجهد....

30 مارس، 2026 قراءة المزيد
التوظيف وبناء الهوية التقنية

مقابلاتي التقنية كانت فوضى: كيف أنقذني إطار عمل تصميم الأنظمة من جحيم ‘سوف نُعلمك بالنتيجة’؟

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

30 مارس، 2026 قراءة المزيد
التوسع والأداء العالي والأحمال

قاعدة بياناتي كانت على وشك الانهيار: كيف أنقذني ‘التخزين المؤقت’ (Caching) من جحيم الاستعلامات المتكررة؟

أشارككم قصة حقيقية عن يوم كادت فيه قاعدة بياناتي أن تنهار تحت ضغط الاستعلامات المتكررة. سأشرح لكم بالتفصيل كيف كان "التخزين المؤقت" (Caching) هو طوق...

30 مارس، 2026 قراءة المزيد
البودكاست