تطبيقي كان متاهة: كيف أنقذتني خوارزمية A* (A-Star) من كابوس المسارات الضائعة؟

مقدمة: يوم أن ضاع “أبو أحمد” في تطبيقي!

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

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

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

في هداك اليوم، حسيت بإحباط كبير. الخوارزمية اللي كنت بستخدمها (كانت نسخة معدلة من خوارزمية البحث بالعمق أولاً – DFS) كانت بتلاقي مسار، أي مسار، لكنها ما كانت “ذكية” كفاية لتعرف إنه في مسار أفضل وأقصر. كانت مثل اللي بسوق سيارة وعيونه مغمضة، بمشي وبس. هون تذكرت محاضرة قديمة في الجامعة عن خوارزميات إيجاد المسار الذكية، ولمع في بالي اسم: A* (A-Star). كانت هي اللحظة اللي تغير فيها كل شيء، وأنقذت المشروع من كابوس المسارات غير الفعالة. خلوني أحكيلكم كيف.

ما هي خوارزمية A*؟ ببساطة، هي “البحث الذكي”

لو بدنا نوصف خوارزمية A* بكلمتين، فهي: “بحث مُستنير” (Informed Search). على عكس الخوارزميات “العمياء” مثل BFS (البحث بالعرض أولاً) أو DFS (البحث بالعمق أولاً) اللي بتبحث في كل الاتجاهات الممكنة تقريبًا، خوارزمية A* عندها “حاسة سادسة”. هي بتستخدم طريقة ذكية عشان “تخمّن” أي المسارات واعدة أكثر وتستكشفها أولاً.

هذا الذكاء كله بيجي من معادلة بسيطة لكن عبقرية:

f(n) = g(n) + h(n)

شكلها بخوف؟ ولا يهمكم، بسيطة جداً. خلينا نفصصها حبة حبة:

g(n): التكلفة الفعلية للوصول (المشوار اللي مشيته)

هذا الجزء هو الأسهل. g(n) هي التكلفة الحقيقية للمسار من نقطة البداية إلى النقطة الحالية (اللي بنسميها العقدة ‘n’).

  • مثال تطبيقي: في تطبيق المول تبعنا، لو كل متر بنمشيه هو تكلفة “1”، فـ g(n) هي ببساطة المسافة اللي قطعها المستخدم فعليًا من مدخل المول لحد ما وصل للنقطة ‘n’ الحالية. لو مشي 100 متر، بتكون g(n) = 100.

h(n): التكلفة المُقدّرة للوصول (المشوار اللي ضايل، تخمينًا)

هون بيجي السحر كله! h(n) هي دالة التخمين أو “الحدس” (Heuristic). وظيفتها إنها تقدّر التكلفة من النقطة الحالية ‘n’ إلى نقطة الهدف النهائية. هذا مجرد تخمين، لكن لازم يكون تخمين “ذكي ومتفائل”.

  • مثال تطبيقي: كيف ممكن نخمن المسافة من مكانك الحالي في المول للصيدلية؟ أبسط طريقة هي حساب “المسافة الجوية” أو الخط المستقيم بين النقطتين (As the crow flies). أكيد ما رح تقدر تمشي عبر الحيطان، لكن هذا التخمين بعطينا فكرة ممتازة عن مدى قربك من الهدف. هذا هو الحدس.

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

f(n): التكلفة الإجمالية المُقدّرة (الخلاصة)

ببساطة، الخوارزمية بتجمع التكلفة الفعلية g(n) مع التكلفة المُقدّرة h(n). النقطة (أو العقدة) اللي عندها أقل قيمة f(n) هي اللي بتبدو “واعدة” أكثر، فالخوارزمية بتقرر تستكشفها أولاً.

بهذه الطريقة، A* بتوازن بين المسارات القصيرة اللي قطعتها بالفعل، وبين المسارات اللي بتبدو أقرب للهدف. ما بتنجذب للمسارات اللي صارت طويلة جدًا (g(n) عالية)، ولا بتنجذب لمسارات بعيدة عن الهدف (h(n) عالية).

مثال عملي: كيف يجد “أبو أحمد” الصيدلية باستخدام A*؟

لنتخيل خريطة المول تبعنا على شكل شبكة بسيطة. ‘S’ هي نقطة البداية (موقف السيارات)، ‘E’ هي الهدف (الصيدلية)، والمربعات السوداء هي جدران أو عوائق.


+---+---+---+---+---+
| S |   |   |   |   |
+---+---+---+---+---+
|   |███|   |███|   |
+---+---+---+---+---+
|   |███|   |   | E |
+---+---+---+---+---+
|   |   |   |   |   |
+---+---+---+---+---+

الخوارزمية بتشتغل هيك:

  1. تبدأ من S: تنظر إلى المربعات المجاورة لها (فوق، تحت، يمين، يسار).
  2. تحسب f(n) لكل جار:
    • للمربع اللي على يمين S:
      • g(n) = 1 (لأننا تحركنا خطوة واحدة).
      • h(n) = نحسب المسافة المستقيمة منه إلى E (لنقل أنها 4).
      • f(n) = 1 + 4 = 5.
    • للمربع اللي تحت S:
      • g(n) = 1.
      • h(n) = نحسب المسافة المستقيمة منه إلى E (لنقل أنها 5).
      • f(n) = 1 + 5 = 6.
  3. تختار الأفضل: الخوارزمية ترى أن المربع اللي على اليمين (f=5) واعد أكثر من اللي تحت (f=6)، فتتحرك إليه وتضعه في قائمة “المسار الحالي”.
  4. تكرر العملية: من المربع الجديد، تعيد حساب f(n) لجيرانه الجدد، ودائمًا تختار المسار صاحب أقل قيمة f(n) من بين كل المسارات المتاحة التي لم تستكشفها بعد.

لاحظ كيف أنها لم تستكشف المسار السفلي لأنه “بدا” أبعد عن الهدف، وهذا بفضل دالة الحدس h(n). ستستمر الخوارزمية في سلوك المسارات التي تقربها من الهدف بشكل عام، وتتجنب بذكاء الجدران، حتى تصل إلى ‘E’ بأقصر مسار ممكن. هذا بالضبط ما كان يحتاجه “أبو أحمد”!

شوية كود: تطبيق A* بلغة بايثون

الكلام النظري حلو، بس إحنا المبرمجين بنحب نشوف الكود. إليكم مثال مبسط جدًا لخوارزمية A* بلغة بايثون. هذا الكود ليس للإنتاج مباشرة، لكنه يوضح الفكرة الأساسية بشكل ممتاز.


import heapq

# دالة الحدس (Heuristic): هنا نستخدم مسافة مانهاتن
def heuristic(a, b):
    # a و b هما زوج من الإحداثيات (x, y)
    return abs(a[0] - b[0]) + abs(a[1] - b[1])

def a_star(grid, start, goal):
    # الجيران الممكن التحرك إليهم (أعلى، أسفل، يمين، يسار)
    neighbors = [(0, 1), (0, -1), (1, 0), (-1, 0)]
    
    close_set = set() # مجموعة العقد التي تم تقييمها
    came_from = {} # قاموس لتتبع المسار
    
    # g_score هو التكلفة من البداية إلى العقدة الحالية
    g_score = {start: 0}
    
    # f_score هو التكلفة الإجمالية المتوقعة (g_score + heuristic)
    f_score = {start: heuristic(start, goal)}
    
    # قائمة الأولوية (Priority Queue) للعقد التي سيتم تقييمها
    # هيك بنضمن إنه دايماً بنختار العقدة اللي الها أقل f_score
    open_heap = []
    heapq.heappush(open_heap, (f_score[start], start))
    
    while open_heap:
        # احصل على العقدة في القائمة المفتوحة التي لديها أقل f_score
        current = heapq.heappop(open_heap)[1]
        
        # إذا وصلنا للهدف، قم بإعادة بناء المسار وإرجاعه
        if current == goal:
            path = []
            while current in came_from:
                path.append(current)
                current = came_from[current]
            path.append(start)
            return path[::-1] # نعكس المسار ليكون من البداية للنهاية
            
        close_set.add(current)
        
        for i, j in neighbors:
            neighbor = current[0] + i, current[1] + j
            
            # نحسب التكلفة المبدئية للمسار إلى الجار
            # هنا نفترض أن تكلفة كل خطوة هي 1
            tentative_g_score = g_score[current] + 1
            
            # تحقق من أن الجار داخل حدود الشبكة وأنه ليس عائقًا
            if 0 <= neighbor[0] < len(grid) and 0 <= neighbor[1] = g_score[neighbor]:
                continue
            
            # هذا هو أفضل مسار حتى الآن. سجله!
            came_from[neighbor] = current
            g_score[neighbor] = tentative_g_score
            f_score[neighbor] = g_score[neighbor] + heuristic(neighbor, goal)
            
            # أضف الجار إلى القائمة المفتوحة ليتم تقييمه
            if neighbor not in [i[1] for i in open_heap]:
                 heapq.heappush(open_heap, (f_score[neighbor], neighbor))

    return False # لم يتم العثور على مسار

# --- مثال للاستخدام ---
# 0 = ممر, 1 = جدار
grid = [
    [0, 0, 0, 0, 0],
    [0, 1, 0, 1, 0],
    [0, 1, 0, 0, 0],
    [0, 0, 0, 1, 0],
    [0, 0, 0, 1, 0]
]
start_node = (0, 0)
goal_node = (2, 4)

path = a_star(grid, start_node, goal_node)
print("المسار الأفضل هو:", path)

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

بعد ما استخدمت A* في مشاريع كثيرة، من الألعاب لتطبيقات الخرائط واللوجستيات، تعلمت كم شغلة “من الكيس” زي ما بحكوها. تفضلوا:

  • اهتم بهياكل البيانات (Data Structures): استخدام قائمة عادية للبحث عن العقدة ذات أقل f_score في كل مرة هو أمر كارثي من ناحية الأداء. استخدام “قائمة الأولوية” (Priority Queue/Heap) هو الحل الأمثل والأسرع، زي ما عملنا في كود البايثون باستخدام مكتبة heapq.
  • دالة الحدس (Heuristic) هي فن وعلم: في الشبكات المربعة، “مسافة مانهاتن” (Manhattan Distance) غالبًا ما تكون أفضل من “المسافة الإقليدية” (Euclidean Distance) إذا كان مسموحًا لك فقط بالحركة أفقيًا وعموديًا. اختر الدالة التي تعكس طبيعة الحركة في مشكلتك.
  • A* ليست دائمًا الحل: إذا كانت كل تكاليف الحركة متساوية ولا تحتاج لدالة حدس (يعني كل الطرق زي بعض)، فإن خوارزمية BFS ستعطيك أقصر مسار وبكود أبسط. أما إذا كنت تريد إيجاد أقصر مسار من نقطة واحدة إلى كل النقاط الأخرى، فخوارزمية “دايكسترا” (Dijkstra) هي خيارك الأفضل. A* تتألق عندما يكون لديك نقطة بداية ونقطة نهاية محددتان.
  • العالم الحقيقي أكثر تعقيدًا: في تطبيقي، لم تكن التكلفة مجرد مسافة. أضفنا عوامل أخرى مثل “ازدحام الممرات” في أوقات معينة. يمكنك تعديل تكلفة الحركة بين عقدتين (الـ “1” اللي افترضناها في الكود) لتكون ديناميكية وتعكس هذه العوامل. هذا ما يجعل A* مرنة وقوية جدًا.

الخلاصة: من متاهة إلى طريق سريع 🚀

في النهاية، بعد ما طبقنا خوارزمية A* في تطبيق المول، كانت النتائج مذهلة. الشكاوى اختفت، وتقييمات التطبيق ارتفعت. “أبو أحمد” وكل المستخدمين صاروا يوصلوا لوجهتهم بأسرع وأذكى الطرق. الدرس اللي تعلمته كان أعمق من مجرد كود وخوارزمية.

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

نصيحتي لكم يا جماعة: لا تخافوا من الخوارزميات والنظريات. افهموا المبدأ الأساسي وراءها، وجربوا تطبيقها على مشاكل بسيطة. كل خوارزمية هي قصة، أداة، وسلاح في ترسانتك كمطور. وكلما زادت أسلحتك، أصبحت قادرًا على مواجهة تحديات أكبر وأكثر تعقيدًا. يلا، شدوا حيلكم!

أبو عمر

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

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

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

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

آخر المدونات

ذكاء اصطناعي

بحثنا كان يعثر على الكلمات، لا على النوايا: كيف أنقذتنا قواعد بيانات المتجهات من جحيم البحث الدلالي الأعمى؟

أشارككم قصة من قلب المعركة البرمجية، يوم كان نظام البحث لدينا أصمًا وأعمى، لا يفهم سوى تطابق الكلمات. سنغوص في عالم قواعد بيانات المتجهات (Vector...

14 أبريل، 2026 قراءة المزيد
خوارزميات

قاعدة بياناتنا كانت تجيب على ‘هل هذا موجود؟’ ببطء قاتل: كيف أنقذنا ‘مرشح بلوم’ (Bloom Filter) من جحيم الاستعلامات المكلفة؟

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

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

موقعنا كان يستبعد الملايين بصمت: كيف أنقذتنا ‘معايير الوصولية’ (Accessibility) من جحيم التصميم الإقصائي؟

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

14 أبريل، 2026 قراءة المزيد
برمجة وقواعد بيانات

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

أشارككم قصة حقيقية من قلب المعركة البرمجية، حين كاد تطبيقنا أن ينهار بسبب بطء الاستعلامات. اكتشفوا معنا كيف كانت "فهارس قاعدة البيانات" هي طوق النجاة...

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

أنظمتنا كانت تسأل ‘هل هناك جديد؟’ كل ثانية: كيف أنقذتنا ‘الخطافات الشبكية’ (Webhooks) من جحيم الاستقصاء المستمر (Polling)؟

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

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

تطبيقنا كان رهينة منطقة جغرافية واحدة: كيف أنقذتنا استراتيجية ‘متعددة المناطق’ (Multi-Region) من جحيم الانقطاع الكامل؟

أشارككم قصة حقيقية عن يوم توقف فيه تطبيقنا بالكامل بسبب انقطاع في منطقة سحابية واحدة، وكيف كانت استراتيجية "متعددة المناطق" (Multi-Region) هي طوق النجاة. سأشرح...

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

حسابي في GitHub كان مقبرة صامتة: كيف أنقذني ‘ملف التعريف المميّز’ من جحيم التجاهل؟

كنتُ أرى حسابي في GitHub كمقبرة لمشاريعي، مجرد أرشيف لا يلتفت إليه أحد. في هذه المقالة، سأشارككم قصتي، يا جماعة، كيف حوّلت هذا المستودع الصامت...

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

عطل خدمة واحدة كاد ينسف النظام: كيف أنقذنا نمط “قاطع الدائرة” من جحيم الأعطال المتتالية؟

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

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