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

هياكل البيانات والخوارزميات / البنى المرتبة والاستدلال على الرسوم / الدرس 11

أقصر المسارات الموزونة والإرخاء

اختر الخوارزمية وفقا لافتراضات الأوزان، وحافظ على مرجعية المسافة الحالية عندما تتقادم إدخالات الأولوية.

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

أهداف الدرس

  • تمثل المسافة ادعاء عن طريق يقوم على افتراضات
  • يعتمد Dijkstra على كلف امتداد غير سالبة
  • يعتمد إرخاء Bellman-Ford على برهان مختلف

تمثل المسافة ادعاء عن طريق يقوم على افتراضات

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

يعتمد Dijkstra على كلف امتداد غير سالبة

يأخذ Dijkstra مرارا أصغر مسافة مؤقتة معروفة حاليا. ومع الحواف غير السالبة، لا يستطيع طريق يمر بجبهة لاحقة ليست أصغر أن يحسن مسافة استقرت بصورة صحيحة. يفحص البرنامج الأول شرط الأوزان المسبق، ويدرج عنصرا جديدا في الكومة بعد التحسن. وتبقى الإدخالات الأقدم فيها؛ لذلك يتخطى الإدخال المسحوب ما لم تساو مسافته المخزنة المسافة في الخريطة المعتمدة. تتجنب هذه الاستراتيجية الكسولة عملية خفض مفتاح مخصصة. وقد تحوي كومتها O(E) إدخالا، فيناسب هذا التنفيذ حد O((V+E) log(V+E))، بدلا من افتراض كومة مفهرسة بحجم V.

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

def dijkstra(graph, source):
    if source not in graph:
        raise ValueError("Unknown source")
    if any(weight < 0 for edges in graph.values() for _, weight in edges):
        raise ValueError("Dijkstra requires nonnegative weights")
    distance = {vertex: inf for vertex in graph}
    distance[source] = 0
    heap = [(0, source)]
    while heap:
        cost, vertex = heapq.heappop(heap)
        if cost != distance[vertex]:
            continue
        for neighbor, weight in graph[vertex]:
            candidate = cost + weight
            if candidate < distance[neighbor]:
                distance[neighbor] = candidate
                heapq.heappush(heap, (candidate, neighbor))
    return distance

graph = {"A": [("B", 9), ("C", 2)], "B": [], "C": [("B", 3)], "D": []}
print(sorted(dijkstra(graph, "A").items()))
الناتج المتوقع
[('A', 0), ('B', 5), ('C', 2), ('D', inf)]

تستقبل B أولا المسافة 9، ثم المسافة 5 عبر C. ويُتخطى الإدخال القديم ذي المسافة 9؛ وتبقى D غير قابلة للوصول.

يعتمد إرخاء Bellman-Ford على برهان مختلف

يفحص Bellman-Ford جميع الحواف مرارا. وبعد عدد كاف من الجولات، تكون قد أُتيحت فرصة الانتشار لكل أقصر مسار بسيط لا يتجاوز V-1 حافة؛ ويشير تحسن إضافي قابل للوصول إلى دورة سالبة يمكن بلوغها. يستخدم البرنامج الثاني إرخاءات في موضعها، وقد تنشر عدة حواف في جولة واحدة؛ لذلك يكون ثابته ضمان تغطية على الأقل، وليس حافة إضافية واحدة بالضبط في كل جولة. يرفض دورة سالبة قابلة للوصول، ويترك المكونات غير القابلة للوصول غير مؤثرة في مسافات المصدر. الحد المعتاد O(VE) للعمل، وO(V) لتخزين المسافات، دون قائمة حواف المدخل.

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

def bellman_ford(vertices, edges, source):
    distance = {vertex: inf for vertex in vertices}
    if source not in distance:
        raise ValueError("Unknown source")
    distance[source] = 0
    for _ in range(len(vertices) - 1):
        changed = False
        for start, end, weight in edges:
            if distance[start] != inf and distance[start] + weight < distance[end]:
                distance[end] = distance[start] + weight
                changed = True
        if not changed:
            break
    for start, end, weight in edges:
        if distance[start] != inf and distance[start] + weight < distance[end]:
            raise ValueError("Reachable negative cycle")
    return distance

print(sorted(bellman_ford(["A", "B", "C"],
    [("A", "B", 4), ("A", "C", 5), ("B", "C", -2)], "A").items()))
الناتج المتوقع
[('A', 0), ('B', 4), ('C', 2)]

تصح الحافة السالبة هنا لأن الرسم لا يحوي دورة سالبة قابلة للوصول. لكنها تخالف الشرط المسبق المعلن لـ Dijkstra.

اجعل استعادة الطريق وحالة عدم الوصول صريحتين

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

تحقّق من شكل الرسم قبل تفسير حالات الفشل

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

قارن الخوارزميات حيث تتقاطع افتراضاتها

لرسوم صغيرة غير سالبة، قارن Dijkstra وBellman-Ford بمرجع مستقل لجميع أزواج الرؤوس، مثل Floyd-Warshall. أدرج طريقا غير مباشر أرخص، وحوافا صفرية، وبدائل متساوية، ورؤوسا منفصلة، وإدخالات كومة متقادمة. اختبر رفض الحافة السالبة مستقلا عن كشف Bellman-Ford للدورة الواصلة، بما في ذلك دورة سالبة غير قابلة للوصول ينبغي ألا تفسد مسافات المصدر. تكمل مجموعة الاختبارات المحدودة براهين الإرخاء. تستخدم جميع الفحوص رسوما مصطنعة في الذاكرة دون خدمة رسوم أو قاعدة بيانات أو عملية شبكة أو اختبار تحميل. سجّل حدود التنفيذ الفعلي، بدلا من ادعاء نموذج آخر للكومة أو الأوزان.

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

قبول حافة سالبة في عقد Dijkstra

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

تسبب إدخالات الكومة القديمة في معالجة متقادمة متكررة

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

رفض استعلام مصدر صحيح بسبب دورات سالبة غير قابلة للوصول

لماذا يحدث؟
تجاهل كشف الدورات إمكان الوصول من المصدر.
كيف تصلحه؟
اشترط مسافة محدودة من المصدر قبل فحص تحسن إضافي.
كيف تتجنب تكراره؟
اختبر مكونات منفصلة تحوي دورات سالبة.

اختبر فهمك

1. لماذا تصبح مسافة B مساوية 5، بدلا من 9، في الرسم الأول؟

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

تكلف الحافة المباشرة من A إلى B مقدار 9، بينما يكلف الطريق من A عبر C إلى B مقدار 2+3=5. يحسن الإرخاء المسافة المعتمدة ويدرج أولوية جديدة مقدارها 5. وعندما تُسحب 9 لاحقا تكون متقادمة، ويجب ألا تستبدل النتيجة الحالية أو تعيد معالجتها.

2. لماذا تختلف الدورة السالبة القابلة للوصول عن دورة في مكوّن منفصل؟

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

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

أهم ما تعلمته

  • تمثل المسافة ادعاء عن طريق يقوم على افتراضات
  • قارن الخوارزميات حيث تتقاطع افتراضاتها

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