AlgoVerse

کوتاه‌ترین مسیر همه‌ی جفت‌ها با مربع‌سازی مکرر

کوتاه‌ترین مسیر بین همه‌ی جفت‌ها را با «ضرب» ماتریس وزن‌ها در خودش پیدا می‌کند، با min به جای جمع و + به جای ضرب؛ با مربع کردن مکرر، log n ضرب کافی است. Θ(n³ log n).

فرض کنید L^(m)[i][j] طول کوتاه‌ترین مسیر از i به j با حداکثر m یال باشد. مسیری با حداکثر 2m یال را می‌شود به دو نیمه‌ی حداکثر m یالی شکست:

ادامه‌ی توضیح ↓
ورودی:
V=5 · E=9 · src=Aخروجی PDF

گراف پنج‌رأسی CLRS با یال‌های منفی (بدون دور منفی)؛ بعد از دو مربع‌سازی (m = ۴ = n − ۱) همه‌ی فاصله‌ها نهایی‌اند. سطرها و ستون‌ها به ترتیب A تا E اند.

  1. def all_pairs(graph, src):
  2. nodes = sorted(graph)
  3. n = len(nodes)
  4. inf = float("inf")
  5. # L[i][j]: the shortest path from i to j using at most m edges; first m = 1, the edges themselves.
  6. L = [[0 if i == j else inf for j in range(n)] for i in range(n)]
  7. for u in graph:
  8. for v, w in graph[u]:
  9. L[nodes.index(u)][nodes.index(v)] = w
  10. m = 1
  11. while m < n - 1:
  12. L = square(L, nodes)
  13. m = 2 * m
  14. return L
  15. def square(L, nodes):
  16. # A min-plus product: like matrix multiplication with + as "·" and min as "+".
  17. n = len(L)
  18. C = [[float("inf")] * n for _ in range(n)]
  19. for i in range(n):
  20. for j in range(n):
  21. for k in range(n):
  22. if L[i][k] + L[k][j] < C[i][j]:
  23. C[i][j] = L[i][k] + L[k][j]
  24. return C

ناوردا: بعد از هر مربع‌سازی، L[i][j] طول کوتاه‌ترین مسیر i به j با حداکثر m یال است.

شروع اجرا

L

جدول هنوز خالی است.

C

جدول هنوز خالی است.

متغیرهای محلی

src'A'

نارنجی = در همین گام عوض شد

graph dict
  • 'A': [('B', 3), ('C', 8), ('E', -4)]
  • 'B': [('D', 1), ('E', 7)]
  • 'C': [('B', 4)]
  • 'D': [('A', 2), ('C', -5)]
  • 'E': [('D', 6)]

Call stack

  1. all_pairs()خط 1
گام ۱ از ۷۳۸
خط سیاه = squared · خط نارنجی = گام کلیدیstart: 0/1 · better: 0/65 · squared: 0/2
    توضیح

    ایده

    فرض کنید L^(m)[i][j] طول کوتاه‌ترین مسیر از i به j با حداکثر m یال باشد. مسیری با حداکثر 2m یال را می‌شود به دو نیمه‌ی حداکثر m یالی شکست:

    L^(2m)[i][j] = min over k of  L^(m)[i][k] + L^(m)[k][j]
    

    این دقیقاً ضرب ماتریس است، با min به جای جمع و + به جای ضرب (ضرب min-plus).

    مربع‌سازی مکرر

    اگر دور منفی نباشد، کوتاه‌ترین مسیر حداکثر n − 1 یال دارد. به جای n − 2 بار ضرب (L, L², L³, …) ماتریس را پشت سر هم مربع می‌کنیم: L, L², L⁴, L⁸, … تا وقتی m ≥ n − 1. پس ⌈log₂(n − 1)⌉ ضرب، هر کدام Θ(n³).

    مقایسه

    فلوید–وارشال همین کار را در Θ(n³) انجام می‌دهد؛ این روش بیشتر برای نشان دادن پیوند کوتاه‌ترین مسیر و ضرب ماتریس مهم است.

    ورودی

    یک گراف جهت‌دار؛ وزن منفی مجاز است، دور منفی نه. جدول L ماتریس فعلی و C حاصل ضرب در حال ساخت است؛ ترتیب سطرها و ستون‌ها ترتیب الفبایی رأس‌هاست.