AlgoVerse

الگوریتم پریم

درخت پوشای کمینه را از یک گره رشد می‌دهد: هر بار سبک‌ترین یالی را که گره‌ای تازه را به درخت وصل می‌کند اضافه می‌کند.

پریم هم درخت پوشای کمینه می‌سازد، ولی برخلاف کروسکال آن را از یک گره رشد می‌دهد. در هر لحظه یک درخت داریم (in_tree). از بین همه‌ی یال‌هایی که گره‌ای از درخت را به گره‌ای بیرون از آن وصل می‌کنند، سبک‌ترین را برمی‌داریم و گره‌ی تازه را به درخت اضافه می‌کنیم.

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

همان گراف نه‌گره‌ای مثال کروسکال، این بار با رشد از A؛ وزن درخت باز هم ۳۷ است.

  1. from heapq import heappop, heappush
  2. INF = float("inf")
  3. def prim(graph, src):
  4. key = {v: INF for v in graph}
  5. parent = {v: None for v in graph}
  6. key[src] = 0
  7. pq = [(0, src)]
  8. in_tree = set()
  9. while pq:
  10. k, u = heappop(pq)
  11. if u in in_tree:
  12. continue
  13. in_tree.add(u)
  14. for v, w in graph[u]:
  15. if v not in in_tree:
  16. if w < key[v]:
  17. key[v] = w
  18. parent[v] = u
  19. heappush(pq, (w, v))
  20. return parent, key

ناوردا: یال‌های parent بین گره‌های in_tree همیشه بخشی از یک درخت پوشای کمینه‌اند.

شروع اجرا

48118247149102617ABCDEFGHI
  • نهایی‌شده
  • گره‌ی جاری (u)
  • درخت پوشای کمینه تا اینجا
  • یالِ در حال بررسی

صف اولویت pq

خالی

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

src'A'

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

graph dict
  • 'A': [('B', 4), ('H', 8)]
  • 'B': [('A', 4), ('H', 11), ('C', 8)]
  • 'C': [('B', 8), ('I', 2), ('F', 4), ('D', 7)]
  • 'D': [('C', 7), ('F', 14), ('E', 9)]
  • 'E': [('D', 9), ('F', 10)]
  • 'F': [('C', 4), ('D', 14), ('E', 10), ('G', 2)]
  • 'G': [('F', 2), ('I', 6), ('H', 1)]
  • 'H': [('A', 8), ('B', 11), ('G', 1), ('I', 7)]
  • 'I': [('C', 2), ('G', 6), ('H', 7)]

Call stack

  1. prim()خط 6
گام ۱ از ۱۷۵
خط سیاه = pop · خط نارنجی = گام کلیدیcompare: 0/14 · push: 0/13 · pop: 0/13 · add: 0/9 · update: 0/12 · skip: 0/4

    رشد تعداد عملیات با اندازه‌ی ورودی

    میانگین ۵ گراف تصادفی برای هر n (با seed ثابت). نشانگر توخالی = ورودی فعلی.

    • comparisons
    • update
    • ورودی فعلی (n=9)
    0204060481216202428n = تعداد گره‌هاcomparisonsupdate
    نمایش به شکل جدول
    ncomparisonsupdate
    45.24.4
    813.811
    1220.816
    162822.8
    2035.428.4
    2437.232.4
    2847.240
    توضیح

    ایده

    پریم هم درخت پوشای کمینه می‌سازد، ولی برخلاف کروسکال آن را از یک گره رشد می‌دهد. در هر لحظه یک درخت داریم (in_tree). از بین همه‌ی یال‌هایی که گره‌ای از درخت را به گره‌ای بیرون از آن وصل می‌کنند، سبک‌ترین را برمی‌داریم و گره‌ی تازه را به درخت اضافه می‌کنیم.

    key و صف اولویت

    برای هر گره‌ی بیرون از درخت، key[v] وزن سبک‌ترین یالی است که تا الان می‌شناسیم و v را به درخت وصل می‌کند. پیدا کردن گره با کمترین key با صف اولویت انجام می‌شود، دقیقاً مثل Dijkstra:

    1. گره با کمترین key از صف بیرون می‌آید. اگر قبلاً در درخت بوده، رد می‌شود.
    2. به درخت اضافه می‌شود.
    3. برای هر همسایه‌ی v بیرون از درخت: اگر یال u — v از key[v] سبک‌تر است، key[v] و parent[v] به‌روز می‌شوند و v با کلید تازه وارد صف می‌شود.

    فرق با Dijkstra

    ساختار دو الگوریتم یکی است و فقط شرط فرق دارد: Dijkstra می‌پرسد dist[u] + w < dist[v] (طول کل مسیر از مبدأ)، ولی پریم فقط w < key[v] (وزن همان یک یال). برای همین درخت Dijkstra کوتاه‌ترین مسیرها را می‌دهد و درخت پریم کمترین وزن کل را.

    هزینه

    با صف اولویت دودویی O((V + E) log V).

    در trace به چه چیزی نگاه کنی

    • عدد کنار هر گره key آن است؛ یال‌های آبی (از parent) درخت تا اینجا هستند.
    • در ورودی «گراف کامل» صف اولویت پر از عضوهای کهنه (خط‌خورده) می‌شود.
    • درخت نهایی را با نتیجه‌ی کروسکال روی همان گراف («مثال به سبک کتاب») مقایسه کن: وزن کل هر دو ۳۷ است.