در دنیای پیچیده ارتباطات، یکی از مهم‌ترین مسائل، بهینه‌سازی و کاهش هزینه‌ها است. نظریه گراف، به عنوان یکی از ابزارهای قدرتمند در علوم کامپیوتر و ریاضیات، به ما کمک می‌کند تا این مسئله را به طور علمی و دقیق حل کنیم. درخت پوشای کمینه (Minimum Spanning Tree – MST) یکی از مفاهیم کلیدی در نظریه گراف است که به ما اجازه می‌دهد تا شبکه‌ها را با کمترین هزینه ممکن به هم متصل کنیم. با استفاده از MST می‌توانیم مسیرهایی بهینه برای ارتباطات، حمل و نقل و حتی طراحی مدارهای الکتریکی پیدا کنیم. در این مطلب، با تعاریف، ویژگی‌ها، الگوریتم‌ها و کاربردهای درخت پوشای کمینه آشنا خواهیم شد و خواهیم دید که چگونه می‌توانیم از این مفهوم برای بهینه‌سازی سیستم‌های مختلف استفاده کنیم.

فهرست مطالب پنهان‌کردن فهرست
  1. 1. درخت پوشای کمینه چیست؟
  2. 2. مقدمه‌ای بر نظریه گراف
  3. 3. تعریف گراف و اجزای آن
  4. 4. انواع گراف
    1. 4.1. گراف جهت‌دار
    2. 4.2. گراف بدون جهت
    3. 4.3. گراف وزن‌دار
    4. 4.4. گراف بدون وزن
  5. 5. تعریف دقیق درخت پوشای کمینه
  6. 6. یکتایی درخت پوشای کمینه
    1. 6.1. گراف با یال‌هایی شامل وزن‌های منحصر به فرد
    2. 6.2. گراف با یال‌هایی شامل وزن‌های تکراری
  7. 7. الگوریتم پریم
    1. 7.1. تاریخچه الگوریتم پریم
    2. 7.2. الگوریتم پریم چطور کار می‌کند؟
    3. 7.3. بررسی گام‌به‌گام مراحل اجرای الگوریتم پریم
      1. 7.3.1. انتخاب راس F
      2. 7.3.2. انتخاب راس G
      3. 7.3.3. انتخاب راس D
      4. 7.3.4. انتخاب راس C
      5. 7.3.5. انتخاب راس B
      6. 7.3.6. انتخاب راس A
      7. 7.3.7. انتخاب راس E
    4. 7.4. مرتبه زمانی الگوریتم پریم
      1. 7.4.1. استفاده از ماتریس مجاورتی
      2. 7.4.2. استفاده از لیست مجاورتی
        1. 7.4.2.1. استفاده از هیپ دودویی
        2. 7.4.2.2. استفاده از فیبوناچی هیپ
    5. 7.5. پیاده‌سازی الگوریتم پریم در پایتون
    6. 7.6. مزایا و معایب الگوریتم پریم
  8. 8. الگوریتم کراسکال
    1. 8.1. تاریخچه الگوریتم کراسکال
    2. 8.2. الگوریتم کراسکال چطور کار می‌کند؟
    3. 8.3. بررسی گام‌به‌گام مراحل اجرای الگوریتم کراسکال
      1. 8.3.1. مرتب‌سازی یال‌ها بر اساس وزن
      2. 8.3.2. ایجاد مجموعه‌های مجزا
      3. 8.3.3. انتخاب یال FG
      4. 8.3.4. انتخاب یال AB
      5. 8.3.5. انتخاب یال DF
      6. 8.3.6. انتخاب یال CG
      7. 8.3.7. انتخاب یال AF
      8. 8.3.8. انتخاب یال EF
    4. 8.4. مرتبه زمانی الگوریتم کراسکال
    5. 8.5. پیاده‌سازی الگوریتم کراسکال در پایتون
    6. 8.6. مزایا و معایب الگوریتم‌ کراسکال
  9. 9. مقایسه الگوریتم‌های پریم و کراسکال
  10. 10. کاربردهای درخت پوشای کمینه
  11. 11. کلام آخر درباره درخت پوشای کمینه
  12. 12. سوالات متداول
    1. 12.1. چرا درخت پوشای کمینه در طراحی شبکه‌های کامپیوتری مهم است؟
    2. 12.2. تفاوت‌های اصلی بین الگوریتم پریم و الگوریتم کراسکال چیست؟
    3. 12.3. چگونه می‌توان از درخت پوشای کمینه در بهینه‌سازی مسیرهای حمل و نقل استفاده کرد؟
    4. 12.4. چه عواملی بر یکتایی (Uniqueness) درخت پوشای کمینه تاثیر می‌گذارند؟
    5. 12.5. چگونه الگوریتم‌های MST در بهینه‌سازی سیستم‌های الکتریکی مورد استفاده قرار می‌گیرند؟
  13. 13. یادگیری تحلیل داده را از امروز شروع کنید!

درخت پوشای کمینه چیست؟

درخت پوشای کمینه یا MST درختی است که تمامی رئوس گراف را با کمترین مجموع وزن یال‌ها به هم متصل می‌کند. به عبارت دیگر، MST درختی است که هر دو راس را بدون ایجاد حلقه به هم متصل می‌کند و وزن مجموع یال‌ها را به حداقل می‌رساند.

یکی از ویژگی‌های مهم MST این است که برای یک گراف معین ممکن است چندین درخت پوشای کمینه وجود داشته باشد، اما مجموع وزن یال‌های آن‌ها همیشه یکسان است. همچنین، MST تنها برای گراف‌های همبند قابل تعریف است.

مقدمه‌ای بر نظریه گراف

نظریه گراف (Graph Theory) یکی از شاخه‌های مهم و بنیادین ریاضیات و علوم کامپیوتر است که به مطالعه گراف‌ها و ویژگی‌های آن‌ها می‌پردازد. این نظریه به ما کمک می‌کند تا مسائل پیچیده و متنوعی را که در سیستم‌های متصل به وجود می‌آیند، با استفاده از مدل‌های ریاضی تحلیل و حل کنیم.

تعریف گراف و اجزای آن

گراف مجموعه‌ای از راس‌ها (گره‌ها) و یال‌ها (لبه‌ها) است که در آن هر یال به دو راس متصل است. راس‌ها می‌توانند نمایانگر اشیاء مختلفی مانند شهرها، کامپیوترها، ایستگاه‌های مترو یا هر چیزی که نیاز به اتصال با دیگران دارد، باشند. یال‌ها نیز نشان‌دهنده روابط یا مسیرهای بین این رئوس هستند. به عنوان مثال، در یک شبکه کامپیوتری، رئوس می‌توانند کامپیوترها و یال‌ها کابل‌های اتصال بین آن‌ها باشند.

انواع گراف

گراف‌ها انواع مختلفی دارند که ما در اینجا به مهم‌ترین آن‌ها که در ان مقاله به درک آن‌ها نیاز داریم، می‌پردازیم:

گراف جهت‌دار

در گراف‌های جهت‌دار (Directed Graph)، یال‌ها دارای جهت هستند و نشان می‌دهند که ارتباط تنها از یک راس به راس دیگر وجود دارد. این نوع گراف‌ها برای مدل‌سازی روابط یک‌طرفه مناسب هستند.

گراف بدون جهت

در گراف‌های بدون جهت (Undirected Graph)، یال‌ها جهت ندارند و ارتباط دوطرفه بین رئوس را نشان می‌دهند. این نوع گراف‌ها برای مدل‌سازی روابط متقابل مناسب هستند.

گراف وزن‌دار

در گراف‌های وزن‌دار (Weighted Graph)، هر یال دارای وزنی است که می‌تواند نمایانگر هزینه، فاصله، زمان یا هر معیار دیگری باشد. این نوع گراف‌ها برای مسائلی که در آن‌ها کمیت‌ها مهم هستند، مناسب‌اند.

گراف بدون وزن

در گراف‌های بدون وزن (Unweighted Graph)، یال‌ها وزنی ندارند و تنها وجود یا عدم وجود یک ارتباط را نشان می‌دهند. این نوع گراف‌ها برای مسائلی که در آن‌ها تنها ساختار روابط مهم است، مناسب‌اند.

برای آشنایی بیشتر با گراف و انواع آن پیشنهاد می‌کنیم مقاله نظریه گراف: پلی بین ریاضیات و دنیای واقعی را بخوانید.

باید توجه کرد که ما در مبحث درخت پوشای کمینه، تنها با گراف‌های همبند، بدون جهت و وزن‌دار سر و کار داریم، زیرا در این نوع گراف‌ها، هدف یافتن مسیری است که تمامی رئوس گراف را با کمترین مجموع وزن یال‌ها به هم متصل کند. گراف‌های ناهمبند اصلا درخت پوشا ندارند و در گراف‌های جهت‌دار، مفهوم درخت پوشا به دلیل وجود جهت‌ها پیچیده‌تر می‌شود و در گراف‌های بدون وزن نیز، اگرچه می‌توان درخت پوشا را تعریف کرد، اما مفهوم «کمینه» در این گراف‌ها چندان معنا ندارد.

پیش از تعریف درخت پوشای کمینه، بیایید نگاهی به تعریف درخت پوشا در گراف‌ها بیاندازیم:

درخت پوشا یک زیرگراف از گراف اصلی است که شامل همه رئوس گراف اصلی باشد و حاوی هیچ حلقه‌ای نباشد. همچنین تعداد یال‌های درخت پوشا برابر با n−1 است، که در آن n تعداد راس‌های گراف اصلی است. این ویژگی نشان‌دهنده این است که درخت پوشا کمترین تعداد یال‌های ممکن را برای حفظ همبندی دارد.

تعریف دقیق درخت پوشای کمینه

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

  • شامل تمامی رئوس گراف اصلی است: درخت پوشای کمینه باید تمامی رئوس گراف را شامل شود تا اطمینان حاصل شود که هیچ راسی نادیده گرفته نشده است.
  • یک درخت است: درخت پوشای کمینه نباید حاوی هیچ حلقه‌ای باشد، به عبارت دیگر، بین هر دو راس تنها یک مسیر وجود دارد.
  • کمترین مجموع وزن یال‌ها را دارد: هدف اصلی درخت پوشای کمینه این است که مجموع وزن یال‌های آن حداقل باشد، به گونه‌ای که ارتباط بین رئوس با کمترین هزینه ممکن برقرار شود.

در واقع درخت پوشای کمینه درخت پوشایی است که مجموع وزن‌های آن نسبت به سایر درخت‌های پوشای گراف اصلی، کمینه باشد. برای مثال در مورد گراف زیر:

Graph

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

All MST

اگر مجموع وزن یال‌های درخت پوشای کمینه را حساب کنید، می‌بیند که برابر چهار و از مجموع وزن یال‌های سایر درخت‌های پوشا کمتر است.

یکتایی درخت پوشای کمینه

یکی از ویژگی‌های مهم و قابل تحقیق درخت پوشای کمینه در نظریه گراف، یکتایی آن است. یکتایی MST به عوامل مختلفی بستگی دارد و در شرایط متفاوت می‌تواند تغییر کند. به طور کلی، برای یک گراف معین ممکن است چندین درخت پوشای کمینه وجود داشته باشد، اما مجموع وزن یال‌های آن‌ها همیشه یکسان است. در این بخش به بررسی جزئیات یکتایی درخت پوشای کمینه و شرایط مختلف آن می‌پردازیم.

گراف با یال‌هایی شامل وزن‌های منحصر به فرد

در صورتی که تمامی یال‌های یک گراف وزن‌دار دارای وزن‌های منحصر به فرد (متمایز) باشند، درخت پوشای کمینه آن گراف حتما یکتا خواهد بود. به این معنا که تنها یک مجموعه از یال‌ها وجود دارد که مجموع وزن آن‌ها کمترین است و تمامی رئوس گراف را به هم متصل می‌کند. این ویژگی از آنجا ناشی می‌شود که در هر مرحله از انتخاب یال‌ها، یال با کمترین وزن به طور قطعی انتخاب می‌شود و هیچ تردیدی در انتخاب وجود ندارد. بنابراین، یک گراف با یال‌های وزن‌دار منحصر به فرد همیشه یک MST یکتا خواهد داشت.

برای مثال در گراف زیر که وزن‌ همه یال‌هایش منحصر به فرد است، تنها یک درخت پوشای کمینه داریم که می‌توانید آن را در شکل زیر ببنید:

MST Of Main

گراف با یال‌هایی شامل وزن‌های تکراری

در گراف‌هایی که برخی از یال‌ها دارای وزن‌های تکراری هستند، ممکن است بیش از یک درخت پوشای کمینه داشته باشیم. به عنوان مثال، اگر چند یال با وزن یکسان وجود داشته باشند که می‌توانند رئوس گراف را به هم متصل کنند، انتخاب هر یک از این یال‌ها می‌تواند به درخت‌های پوشای کمینه متفاوتی منجر شود، اما مجموع وزن آن‌ها همچنان کمترین خواهد بود. در این حالت، MST‌ها ممکن است ترکیبات مختلفی از یال‌های با وزن یکسان داشته باشند.

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

A Graph

سه درخت پوشای کمینه داریم که می‌توانید آن‌ها را در شکل زیر ببنید:

Multi MST

همان طور که می‌بینید، مجموع وزن همه یال‌های این سه درخت یکسان و برابر با پنج است.

البته این توضیحات به این معنی نیست که اگر درخت پوشای کمینه یک گراف یکتا باشد، آن گراف حتما دارای وزن‌های متمایز است. زیرا شاید در یک گراف با وزن‌ها تکراری بتوانیم از بین دو یال با وزن یکسان یکی را انتخاب کنیم اما این موضوع همیشه صادق نیست. به مثال زیر توجه کنید:

Unique MST

همان طور که مشخص است تنها درخت پوشای کمینه این گراف در شکل سمت راست رسم شده و مجموع وزن یال‌هایش برابر سه است. با این حال گراف اصلی دارای سه یال هم‌وزن است و تمایزی بین آن‌ها نیست.

این موضوع نشان می‌دهد که داشتن یک MST یکتا به تنهایی نمی‌تواند دلیلی بر منحصر به فرد بودن وزن یال‌های گراف باشد.

احتمالا با توجه به مصال‌های بالا متوجه شده‌اید که پیدا کردن درخت پوشای کمینه در گراف‌های بزرگ چندان کار آسانی نیست. به همین دلیل باید از الگوریتم‌های خاصی که می‌توانند درخت پوشای کمینه گراف‌ها را پیدا کنند، استفاده کنیم. در ادامه دو تا از اصلی‌ترین و بهینه‌ترین این الگوریتم‌ها را بررسی می‌کنیم.

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

الگوریتم پریم (Prim’s Algorithm) یکی از الگوریتم‌های معروف برای پیدا کردن درخت پوشای کمینه در گراف‌های وزن‌دار است. الگوریتم پریم به صورت تکراری عمل می‌کند و در هر مرحله یالی را به درخت اضافه می‌کند که کمترین وزن را دارد و یک راس جدید را به درخت اضافه می‌کند. این فرآیند تا زمانی ادامه می‌یابد که تمامی رئوس به درخت اضافه شوند و درخت پوشای کمینه تشکیل شود.

تاریخچه الگوریتم پریم

توسعه اولیه الگوریتم پریم به سال ۱۹۳۰ برمی‌گردد، زمانی که ریاضی‌دان چک به نام وارتن (Vojtěch Jarník) اولین نسخه از این الگوریتم را در مقاله‌ای منتشر کرد. این مقاله در یکی از ژورنال‌های ریاضیاتی منتشر شد و الگوریتمی برای پیدا کردن درخت پوشای کمینه در گراف‌های وزن‌دار ارائه داد. این الگوریتم بعدها در کشورهای اروپایی به عنوان الگوریتم Jarník شناخته شد.

اما این پایان کار نبود و در سال ۱۹۵۷، رابرت پریم (Robert C. Prim) ریاضی‌دان و دانشمند کامپیوتر آمریکایی، به طور مستقل الگوریتمی مشابه الگوریتم Jarník را کشف و آن را به صورت گسترده‌تری معرفی کرد. پریم در مقاله خود این الگوریتم را به عنوان یک روش کارآمد برای یافتن درخت پوشای کمینه در گراف‌های وزن‌دار توضیح داد. از آن زمان، این الگوریتم در ایالات متحده و دیگر کشورها به نام الگوریتم پریم شناخته می‌شود.

الگوریتم پریم چطور کار می‌کند؟

در این بخش نحوه عملکرد الگوریتم پریم را بررسی می‌کنیم:

  • ایجاد مجموعه رئوس بازدید شده و بازدید نشده: ابتدا یک مجموعه خالی برای نگهداری رئوس بازدیدشده و یک مجموعه نیز برای رئوس بازدیدنشده ایجاد می‌کنیم. مجموعه رئوس بازدیدشده به ما کمک می‌کند تا از ایجاد حلقه جلوگیری کنیم.
  • انتخاب راس شروع: ابتدا یک راس دلخواه را به عنوان راس شروع انتخاب می‌کنیم. این راس می‌تواند هر راسی از گراف باشد و در نتیجه نهایی تاثیری ندارد، زیرا الگوریتم پریم تضمین می‌کند که در نهایت به درخت پوشای کمینه خواهیم رسید. سپس راس شروع را به مجموعه بازدیدشده‌ها اضافه می‌کنیم.
  • پیدا کردن یال با کمترین وزن: از بین یال‌هایی که یک سر آن‌ها در مجموعه رئوس بازدیدشده و سر دیگر آن‌ها در مجموعه رئوس بازدیدنشده قرار دارد، یالی که کمترین وزن را دارد پیدا می‌کنیم.
  • اضافه کردن یال و راس به درخت: یال پیدا شده و راس متصل به آن را به درخت پوشا و مجموعه رئوس بازدید شده اضافه می‌کنیم. این کار به ما اطمینان می‌دهد که راسی جدید به درخت پوشا اضافه شده و همبندی گراف حفظ شده است.
  • تکرار مراحل قبل: مراحل ۳ و ۴ را تکرار می‌کنیم تا زمانی که تمامی رئوس به مجموعه رئوس بازدید شده اضافه شوند و درخت پوشای کمینه تشکیل شود. هر بار که یالی با کمترین وزن را پیدا کرده و به درخت پوشا اضافه می‌کنیم، یک راس جدید به مجموعه رئوس بازدید شده اضافه می‌شود. این فرآیند تا زمانی ادامه می‌یابد که تمامی رئوس گراف بازدید شده و درخت پوشای کمینه کامل شود.

بررسی گام‌به‌گام مراحل اجرای الگوریتم پریم

برای انجام این کار بیایید الگوریتم پریم را روی گراف زیر اجرا کنیم تا درخت پوشای کمینه آن را بدست آوریم:

Main Graph

انتخاب راس F

برای این کار، همان‌طور که توضیح دادیم ابتدا از یک راس دلخواه شروع می‌کنیم و آن را در مجموعه بازدیدشده‌های قرار می‌دهیم. ما راس F را انتخاب می‌کنیم اما فرقی نمی‌کند که از کدام راس شروع کنیم و در هر صورت پریم جواب درست را بر می‌گرداند. سایر راس‌ها به مجموعه بازدیدنشده‌ها انتقال می‌یابند.

انتخاب راس G

حال پریم وزن یال بین راس F و همه همسایه‌های آن را بررسی و کمترین آن‌ها را انتخاب می‌کند. همان طور که در شکل مشخص است، یال‌های متصل به F عبارتند از:

FA (7), FB (8), FC (7), FD (5), FE (8), FG (4)

که کمترین وزن مربوط به یال FG با وزن چهار است. پس، یال FG به عنوان اولین یال درخت پوشای کمینه ما انتخاب می‌شود. سپس راس G را از مجموعه بازدیدنشده‌ها به مجموعه بازدیدشده‌ها انتقال می‌دهیم و به این ترتیب مجموعه رئوس بازدیدشده به صورت {F, G} و مجموعه رئوس بازدیدنشده به صورت  {A, B, C, D, E} درمی‌آید:

Select G Node

انتخاب راس D

حال پریم وزن یال بین راس‌های F و G و همه همسایه‌های آن‌ها را بررسی و کمترین آن‌ها را انتخاب می‌کند. همان طور که در شکل مشخص است، یال‌های انتخاب‌نشده متصل به F و G عبارتند از:

FA (7), FB (8), FC (7), FD (5), FE (8), GD (5), GC (6)

که کمترین وزن مربوط به یال‌های FD و GD با وزن پنج است. اما سوال اینجاست که کدام یک توسط الگوریتم پریم انتخاب می‌شود؟

پاسخ این سوال به نحوه پیاده‌سازی دقیق الگوریتم پریم بستگی دارد. در چنین شرایطی اگر چند یال با وزن‌های یکسان وجود داشته باشد، ترتیب انتخاب آن‌ها می‌تواند به ترتیب پیمایش مجموعه بازدیدشده بستگی داشته باشد. در پیاده‌سازی‌های مختلف این الگوریتم، نحوه اولویت‌بندی یال‌ها با وزن‌های مساوی ممکن است متفاوت باشد اما نتیجه تفاوتی نخواهد داشت و مجموع وزن یال‌های درخت پوشای کمینه در هر دو حالت یکسان خواهد بود.

ما در اینجا به دلخواه یال GD را انتخاب می‌کنیم. سپس راس D را از مجموعه بازدیدنشده‌ها به مجموعه بازدیدشده‌ها انتقال می‌دهیم و به این ترتیب مجموعه رئوس بازدیدشده به صورت {F, G, D} و مجموعه رئوس بازدیدنشده به صورت {A, B, C, E} درمی‌آید:

Select D Node

انتخاب راس C

در مرحله بعدی، الگوریتم پریم همسایه‌های همه رئوس مجموعه بازدیدشده F، G و D را بررسی و کمترین وزن یال متصل به راسی که در مجموعه بازدیدنشده قرار دارد را انتخاب می‌کند. همان طور که در شکل مشخص است، یال‌های انتخاب‌نشده متصل به رئوسF، G و D عبارتند از:

FA (7), FB (8), FC (7)، FE (8), ED (9), GC (6)

که کمترین وزن مربوط به یال GC با وزن شش است. پس، یال GC به عنوان یال بعدی درخت پوشای کمینه انتخاب می‌شود. سپس راس C را از مجموعه بازدیدنشده‌ها به مجموعه بازدیدشده‌ها انتقال می‌دهیم و به این ترتیب مجموعه رئوس بازدیدشده به صورت {F, G, D, C} و مجموعه رئوس بازدیدنشده به صورت {A, B, E} درمی‌آید:

Select B Node

انتخاب راس B

در گام بعدی، الگوریتم پریم همسایه‌های همه رئوس مجموعه بازدیدشده را بررسی و کمترین وزن یال متصل به راسی که در مجموعه بازدیدنشده قرار دارد را انتخاب می‌کند. همان طور که در شکل مشخص است، یال‌های متصل به رئوس F، G، D و C عبارتند از:

FA (7), FB (8), FE (8), CB (7), ED (9)

که کمترین وزن مربوط به یال‌های AB و CB با وزن هفت است. ما از بین آن‌ها به دلخواه یال CB را به عنوان یال بعدی درخت پوشای کمینه انتخاب می‌شود. سپس راس B را از مجموعه بازدیدنشده‌ها به مجموعه بازدیدشده‌ها انتقال می‌دهیم و به این ترتیب مجموعه رئوس بازدیدشده به صورت {F, G, D, C, B} و مجموعه رئوس بازدیدنشده به صورت {A, E} درمی‌آید:

Select B Node

انتخاب راس A

حال الگوریتم پریم همسایه‌های همه رئوس مجموعه بازدیدشده را بررسی و کمترین وزن یال متصل به راسی که در مجموعه بازدیدنشده قرار دارد را انتخاب می‌کند. همان طور که در شکل مشخص است، یال‌های متصل به رئوس F، G، D، C و B عبارتند از:

FA (7), FE (8), BA (5), ED (9)

که کمترین وزن مربوط به یال BA با وزن پنج است. پس، یال BA به عنوان یال بعدی درخت پوشای کمینه انتخاب می‌شود. سپس راس A را از مجموعه بازدیدنشده‌ها به مجموعه بازدیدشده‌ها انتقال می‌دهیم و به این ترتیب مجموعه رئوس بازدیدشده به صورت {F, G, D, C, B, A} و مجموعه رئوس بازدیدنشده به صورت {E} درمی‌آید:

Select A Node

انتخاب راس E

در اخرین مرحله الگوریتم پریم همسایه‌های همه رئوس مجموعه بازدیدشده را بررسی و کمترین وزن یال متصل به راسی که در مجموعه بازدیدنشده قرار دارد را انتخاب می‌کند. همان طور که در شکل مشخص است، یال‌های متصل به رئوسF، G، D، C، B و A عبارتند از:

FE (8), ED (9)

که کمترین وزن مربوط به یال FE با وزن هشت است. پس، یال FE به عنوان یال بعدی درخت پوشای کمینه انتخاب می‌شود. سپس راس E را از مجموعه بازدیدنشده‌ها به مجموعه بازدیدشده‌ها انتقال می‌دهیم و به این ترتیب مجموعه رئوس بازدیدشده به صورت {F, G, D, C, B, A, E} و مجموعه رئوس بازدیدنشده خالی می‌شود. در این مرحله، الگوریتم به پایان می‌رسد و درخت پوشای کمینه کامل می‌شود:

Final MST

مرتبه زمانی الگوریتم پریم

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

استفاده از ماتریس مجاورتی

اگر از ماتریس مجاورتی برای نمایش گراف استفاده شود، پیچیدگی زمانی‌اش برابر با O(V2) خواهد بود، که V تعداد رئوس گراف است.

استفاده از لیست مجاورتی

اگر از لیست مجاورتی برای نمایش گراف استفاده شود، بسته به اینکه از هیپ دودویی استفاده کنیم یا هیپ فیبوناچی مرتبه مانی این الگوریتم متفاوت است:

استفاده از هیپ دودویی

در صورت استفاده از هیپ دودویی (Binary Heap) برای پیاده‌سازی این الگوریتم، پیچیدگی زمانی آن به صورت زیر خواهد بود:

  • مرحله‌ی ابتدایی: زمان لازم برای مقداردهی اولیه صف اولویت با V راس برابر است با O(V) است.
  • عملیات انتخاب راس‌ها: این عملیات از طریق صف اولویت انجام می‌شود. در بدترین حالت، برای هر کدام از V راس این عملیات انجام می‌شود و هر بار انتخاب کمترین مقدار از صف اولویت با زمان O(logV) قابل انجام است. بنابراین این قسمت دارای زمان O(Vlog⁡V) است.
  • به‌روزرسانی درخت: این عملیات برای هر یال از E یال یک بار انجام می‌شود. هر بار که یک راس از صف اولویت خارج می‌شود، تمام یال‌های متصل به آن بررسی می‌شود و هر به‌روزرسانی صف اولویت زمان O(log⁡V) نیاز دارد. بنابراین این قسمت دارای زمان O(Elog⁡V) است.

با جمع‌بندی این موارد، پیچیدگی زمانی کلی الگوریتم پریم با استفاده از هیپ دودویی برابر خواهد بود با:

O((V + E) \log V) = O(E \log V)

استفاده از فیبوناچی هیپ

اگر از هیپ فیبوناچی (Fibonacci Heap) برای پیاده‌سازی این الگوریتم استفاده شود، پیچیدگی زمانی الگوریتم بهبود می‌یابد:

  • مرحله‌ی ابتدایی: زمان لازم برای مقداردهی اولیه صف اولویت با V راس برابر است با O(V).
  • عملیات انتخاب راس‌ها: این عملیات با استفاده از صف اولویت فیبوناچی انجام می‌شود که زمان O(log⁡V) برای هر انتخاب دارد. برای هر V راس این عملیات انجام می‌شود، بنابراین پیچیدگی زمانی این قسمت برابر با O(Vlog⁡V) است.
  • به‌روزرسانی درخت: به‌روزرسانی صف اولویت فیبوناچی برای هر یال از E یال یک بار انجام می‌شود. هر به‌روزرسانی صف اولویت زمان O(1) نیاز دارد، بنابراین پیچیدگی زمانی این قسمت برابر با O(E) است.

با جمع‌بندی این موارد، پیچیدگی زمانی کلی الگوریتم پریم با استفاده از هیپ فیبوناچی برابر خواهد بود با:

O(V \log V + E)

پیاده‌سازی الگوریتم پریم در پایتون

برای این منظور، ابتدا یک تابع به نام prim_algorithm ایجاد می‌کنیم. سپس مجموعه‌ای به نام visited برای ذخیره رئوس بازدیدشده ایجاد و راس شروع را در آن قرار می‌دهیم. سپس تمامی رئوس گراف به جز راس شروع را در مجموعه‌ای به نام unvisited قرار می‌دهیم. سپس لیستی به نام mst_edges برای ذخیره یال‌های درخت پوشای کمینه تعریف می‌کنیم. سپس الگوریتم تا زمانی که تمامی رئوس بازدید نشده‌اند، ادامه پیدا می‌کند. در هر تکرار، یالی با کمترین وزن که یک سر آن در مجموعه بازدیدشده و سر دیگر آن در مجموعه بازدیدنشده قرار دارد، انتخاب می‌شود. این یال به mst_edges اضافه شده و راس مقصد به مجموعه بازدیدشده‌ها افزوده و از مجموعه بازدیدنشده‌ها حذف می‌شود. این فرآیند تا زمانی که تمامی رئوس بازدید شوند، ادامه پیدا می‌کند و درخت پوشای کمینه تولید می‌شود:

def prim_algorithm(graph, start_node):
  # Initialize the visited set with the start node
  visited = set(start_node)
  # All nodes except the start node are unvisited
  unvisited = set(graph.keys()) - visited
  # List to store the edges of the MST
  mst_edges = []
  # Continue until all nodes are visited
  while unvisited:
    min_edge = None
    for node in visited:
      for neighbor, weight in graph[node].items():
        # Consider only unvisited neighbors
        if neighbor in unvisited:
          # Find the edge with minimum weight
          if min_edge is None or weight < min_edge[2]:
            min_edge = (node, neighbor, weight)
    # Exit if no more edges can be added
    if min_edge is None:
      break
    # Unpack the minimum edge
    source, destination, weight = min_edge
    # Add the edge to the MST
    mst_edges.append((source, destination, weight))
    # Mark the neighbor as visited
    visited.add(destination)
    # Remove the neighbor from unvisited
    unvisited.remove(destination)
    # Return MST after each edge adding
    print(mst_edges)

برای اجرای این الگوریتم روی گراف شکلی که در بخش قبلی آن را بررسی کردیم، به صورت زیر عمل کرده و می‌بینیم که نتیجه با چیزی که ما بدست آوردیم یکسان است:

graph = {
  'A': {'B': 5, 'E': 10, 'F': 7},
  'B': {'A': 5, 'C': 7, 'F': 8},
  'C': {'B': 7, 'F': 7, 'G': 6},
  'D': {'E': 9, 'F': 5, 'G': 5},
  'E': {'A': 10, 'D': 9, 'F': 8},
  'F': {'A': 7, 'B': 8, 'C': 7, 'D': 5, 'E': 8, 'G': 4},
  'G': {'C': 6, 'D': 5, 'F': 4}
}

prim_algorithm(graph, 'F')

[(‘F’, ‘G’, 4)]
[(‘F’, ‘G’, 4), (‘G’, ‘D’, 5)]
[(‘F’, ‘G’, 4), (‘G’, ‘D’, 5), (‘G’, ‘C’, 6)]
[(‘F’, ‘G’, 4), (‘G’, ‘D’, 5), (‘G’, ‘C’, 6), (‘C’, ‘B’, 7)]
[(‘F’, ‘G’, 4), (‘G’, ‘D’, 5), (‘G’, ‘C’, 6), (‘C’, ‘B’, 7), (‘B’, ‘A’, 5)]
[(‘F’, ‘G’, 4), (‘G’, ‘D’, 5), (‘G’, ‘C’, 6), (‘C’, ‘B’, 7), (‘B’, ‘A’, 5), (‘F’, ‘E’, 8)]

مزایا و معایب الگوریتم پریم

این الگوریتم مزایای متعددی دارد که در زیر به برخی از مهم‌ترین آن‌ها اشاره می‌کنیم:

  • پیچیدگی زمانی مناسب: با استفاده از ساختارهای داده‌ای مناسب مانند هرم‌های دودویی (Binary Heaps)، پیچیدگی این الگوریتم O(ElogV) خواهد بود.
  • کارایی در گراف‌های چگال: در گراف‌هایی که تعداد یال‌ها نسبت به راس‌ها زیاد است، الگوریتم پریم به‌طور معمول کارایی بهتری نسبت به الگوریتم‌های دیگر مانند الگوریتم کراسکال (Kruskal’s Algorithm) دارد.
  • سادگی پیاده‌سازی: الگوریتم پریم به‌دلیل ساختار ساده و تکراری خود، به‌راحتی قابل فهم و پیاده‌سازی است. این امر باعث می‌شود که این الگوریتم در بسیاری از کاربردهای عملی و آموزشی مورد استفاده قرار گیرد.
  • مقایسه با دیگر الگوریتم‌ها: در مقایسه با الگوریتم کراسکال که به مرتب‌سازی یال‌ها نیاز دارد، الگوریتم پریم نیازی به مرتب‌سازی اولیه ندارد و این موضوع می‌تواند در برخی از کاربردها مزیت باشد.
  • کاربردهای گسترده: الگوریتم پریم در مسائل مختلفی از جمله طراحی شبکه‌ها، بهینه‌سازی مسیرها و مسائل مربوط به اتصال زیرساخت‌ها کاربرد دارد.

پریم با وجود مزایای متعدد، معایبی نیز دارد که در زیر به برخی از مهم‌ترین آن‌ها اشاره می‌کنیم:

  • عدم کارایی در گراف‌های پراکنده: در گراف‌های پراکنده (Sparse Graphs)، که تعداد یال‌ها نسبت به تعداد راس‌ها کمتر است، الگوریتم کراسکال ممکن است کارایی بهتری نسبت به پریم داشته باشد. الگوریتم کراسکال به دلیل استفاده از تکنیک‌های مرتب‌سازی و ساختارهای داده‌ای متفاوت، در این نوع گراف‌ها می‌تواند سریع‌تر عمل کند.
  • حساسیت به ساختار گراف: الگوریتم پریم ممکن است به ساختار و ترتیب یال‌ها در گراف حساس باشد، به‌ویژه اگر از لیست مجاورت (Adjacency List) یا ماتریس مجاورت (Adjacency Matrix) استفاده شود. این موضوع می‌تواند بر کارایی الگوریتم تأثیر بگذارد.
  • فضای حافظه مورد نیاز: در صورتی که از ماتریس مجاورت برای نگهداری گراف استفاده شود، فضای حافظه مورد نیاز برای گراف‌های بزرگ بسیار زیاد خواهد بود. این می‌تواند در محیط‌های با محدودیت حافظه مشکل‌ساز شود.

الگوریتم کراسکال

الگوریتم کراسکال (Kruskal’s Algorithm)، یکی دیگر از الگوریتم‌های معروف برای پیدا کردن درخت پوشای کمینه در گراف‌های وزن‌دار است. ایده اصلی الگوریتم کراسکال بر اساس مرتب‌سازی یال‌های گراف بر اساس وزن آن‌ها و اضافه کردن یال‌ها به درخت پوشا به ترتیب کمترین وزن، بدون ایجاد حلقه است. این روش باعث می‌شود که در نهایت درخت پوشایی تشکیل شود که مجموع وزن یال‌های آن حداقل باشد.

تاریخچه الگوریتم کراسکال

الگوریتم کراسکال توسط جوزف کراسکال (Joseph Kruskal)، ریاضی‌دان و آمارشناس آمریکایی، در سال ۱۹۵۶ معرفی شد. این الگوریتم به سرعت به یکی از ابزارهای مهم در نظریه گراف و علوم کامپیوتر تبدیل شد. ایده اصلی الگوریتم کراسکال بر اساس مرتب‌سازی یال‌های گراف بر اساس وزن و اضافه کردن یال‌ها به ترتیب کمترین وزن بدون ایجاد حلقه است. این روش ساده و کارآمد به سرعت مورد توجه قرار گرفت و در طراحی و بهینه‌سازی شبکه‌ها و سیستم‌های مختلف به کار رفت.

الگوریتم کراسکال چطور کار می‌کند؟

در این بخش نحوه عملکرد الگوریتم کراسکال را بررسی می‌کنیم:

  • مرتب‌سازی یال‌ها: در اولین مرحله، تمامی یال‌های گراف بر اساس وزن آن‌ها به صورت صعودی مرتب می‌شوند. این مرحله به ما کمک می‌کند تا یال‌ها را به ترتیب از کوچک‌ترین به بزرگ‌ترین وزن بررسی کنیم و ابتدا یال‌هایی را که وزن کمتری دارند به درخت پوشا اضافه کنیم.
  • ایجاد مجموعه‌های مجزا: برای هر راس، یک مجموعه مجزا (Disjoint Sets) ایجاد می‌کنیم. این مجموعه‌ها برای پیگیری رئوس و اطمینان از عدم ایجاد حلقه در درخت پوشا استفاده می‌شوند.
  • بررسی یال‌ها: در این مرحله برای هر یال، بررسی می‌کنیم که آیا اضافه کردن آن به درخت پوشا باعث ایجاد حلقه می‌شود یا خیر. اگر اضافه کردن یال حلقه‌ای ایجاد نکند، آن یال به درخت پوشا اضافه می‌شود. برای انجام این کار، بررسی می‌کنیم که آیا دو راسی که این یال به آن‌ها متصل است، در مجموعه‌های جداگانه قرار دارند یا خیر. اگر دو راس عضو یک مجموعه نبودند، یعنی اضافه کردن ان یال، باعث ایجاد حلقه نمی‌شود.
  • اجتماع مجموعه‌ها: اگر اضافه کردن یک یال باعث ایجاد حلقه نشود، مجموعه‌های مجزای مربوط به دو راس آن یال با هم ترکیب می‌شوند. این کار به ما کمک می‌کند تا رئوس متصل شده را پیگیری کنیم و از ایجاد حلقه جلوگیری کنیم.
  • تکرار مراحل: مراحل ۳ و ۴ را تکرار می‌کنیم تا زمانی که تمامی رئوس به هم متصل شوند و درخت پوشای کمینه تشکیل شود.

بررسی گام‌به‌گام مراحل اجرای الگوریتم کراسکال

برای انجام این کار بیایید الگوریتم کراسکال را روی همان گرافی که الگوریتم پریم را روی آن اجرا کردیم، اجرا کنیم تا درخت پوشای کمینه آن را بدست آوریم:

Graph for Kruskal

مرتب‌سازی یال‌ها بر اساس وزن

ابتدا، تمام یال‌ها را بر اساس وزن مرتب می‌کنیم و حاصل مرتب‌سازی به صورت زیر است:

(G, F, 4)
(A, B, 5)
(F, D, 5)
(G, D, 5)
(G, C, 6)
(B, C, 7)
(A, F, 7)
(F, C, 7)
(F, B, 8)
(E, F, 8)
(E, D, 9)
(A, E, 10)

ایجاد مجموعه‌های مجزا

ابتدا هر راس را در مجموعه جداگانه‌ای قرار می‌دهیم:

\large \{A\}\{B\}\{C\}\{D\}\{E\}\{F\}\{G\}

انتخاب یال FG

در این مرحله یال کمینه FG با وزن چهار را انتخاب می‌کنیم. این یال از بین سایر یال‌ها کمترین وزن را دارد و راس‌های دو سر آن در دو مجموعه جدا قرار دارد، بنابراین انتخاب آن باعث ایجاد حلقه نمی‌شود و می‌توانیم آن را به درخت پوشای کمینه اضافه کنیم. پس مجموعه‌های F و G را ادغام کرده و مجموعه‌های جدید به صورت زیر خواهند بود:

\large \{A\}, \{B\}, \{C\}, \{D\}, \{E\}, \{F, G\}

گراف ما تا این لحظه به شکل زیر است:

Selecting FG

انتخاب یال AB

اکنون یال AB با وزن ۵ را انتخاب می‌کنیم. این یال از بین سایر یال‌ها کمترین وزن را دارد و راس‌های دو سر آن در دو مجموعه جدا قرار دارد، بنابراین می‌توانیم آن را به درخت پوشای کمینه اضافه ‌کنیم. پس مجموعه‌های A و B را ادغام کرده و مجموعه‌های جدید به صورت زیر خواهند بود:

\large \{A, B\}, \{C\}, \{D\}, \{E\}, \{F, G\}

گراف ما تا این لحظه به شکل زیر است:

Selecting AB

انتخاب یال DF

سپس یال DF با وزن ۵ را انتخاب می‌کنیم. این یال از بین سایر یال‌ها کمترین وزن را دارد و راس‌های دو سر آن در دو مجموعه جدا قرار دارد، بنابراین می‌توانیم آن را به درخت پوشای کمینه اضافه ‌کنیم. پس مجموعه‌ D را با مجموعه F و G ادغام کرده و مجموعه‌های جدید به صورت زیر خواهند بود:

\large \{A, B\}, \{C\}, \{E\}, \{F, G, D\}

گراف ما تا این لحظه به شکل زیر است:

Selecting DF

انتخاب یال CG

در این مرحله باید یال CG با وزن ۶ را انتخاب ‌کنیم. این یال از بین سایر یال‌ها کمترین وزن را دارد و راس‌های دو سر آن در دو مجموعه جدا قرار دارد، بنابراین می‌توانیم آن را به درخت پوشای کمینه اضافه ‌کنیم. پس مجموعه‌ C را با مجموعه F و G و D ادغام کرده و مجموعه‌های جدید به صورت زیر خواهند بود:

\large \{A, B\}, \{E\}, \{F, G, D, C\}

گراف ما تا این لحظه به شکل زیر است:

Selecting CG

انتخاب یال AF

در این مرحله باید یال AF با وزن ۷ را انتخاب ‌کنیم. این یال از بین سایر یال‌ها کمترین وزن را دارد و راس‌های دو سر آن در دو مجموعه جدا قرار دارد، بنابراین می‌توانیم آن را به درخت پوشای کمینه اضافه ‌کنیم. پس مجموعه‌ A و B را با مجموعه F و G و D و C ادغام کرده و مجموعه‌های جدید به صورت زیر خواهند بود:

\large \{A, B, F, G, D, C\}, \{E\}

گراف ما تا این لحظه به شکل زیر است:

Selecting AF

انتخاب یال EF

در این مرحله باید یال EF با وزن ۸ را انتخاب ‌کنیم. این یال از بین سایر یال‌ها کمترین وزن را دارد و راس‌های دو سر آن در دو مجموعه جدا قرار دارد، بنابراین می‌توانیم آن را به درخت پوشای کمینه اضافه ‌کنیم. پس مجموعه‌ E را با مجموعه A و B و F و G و D و C ادغام کرده و به این ترتیب تمامی رئوس در یک مجموعه قرار می‌گیرند:

\large \{A, B, F, G, D, C, E\}

به این ترتیب همه راس‌ها به هم متصل شده‌اند و درخت پوشای کمینه ما بدست می‌آید:

Selecting EF

همان طور که می‌بینید شکل درخت پوشای کمینه حاصل از الگوریتم کراسکال با الگوریتم پریم متفاوت است اما اگر مجموع وزن یال‌های این درخت‌ها را جمع بزنید می‌بینید که هر دو برابر ۳۵ هستند. دلیل این امر وجود یال‌هایی با وزن یکسان در گراف اصلی است. در واقع اگر وزن همه یال‌های گراف متمایز باشد، خروجی الگوریتم‌های کراسکال و پریم دقیقا یکسان خواهد بود.

نکته دیگری که خوب است به آن توجه کنید، این است که چون الگوریتم پریم در هر مرحله فقط یال‌هایی که به راس‌های بازدیدشده متصل هستند را انتخاب می‌کند، همواره درون یک گراف همبند پیش می‌رود اما کراسکال چون یال‌ها را به ترتیب وزنشان انتخاب می‌کند، به صورت ناهمبند پیش می‌رود تا در نهایت درخت پوشای کمینه کامل شود.

مرتبه زمانی الگوریتم کراسکال

پیچیدگی زمانی کراسکال به مراحل مختلف الگوریتم بستگی دارد:

  • مرتب‌سازی یال‌ها: اگر از الگوریتم‌های مرتب‌سازی کارآمد مانند مرتب‌سازی ادغامی (Merge Sort) یا مرتب‌سازی سریع (Quick Sort) استفاده شود، پیچیدگی زمانی این مرحله برابر با O(ElogE) خواهد بود که E تعداد یال‌ها است و در بدترین حالت تعداد یال‌ها از مرتبهO(V2)  (برای گراف‌های کامل) می‌باشد. می‌توان اثبات کرد که پیچیدگی زمانی این مرحله به O(ElogV) کاهش می‌یابد.
  • عملیات ادغام: عملیات ادغام و پیدا کردن ریشه مجموعه‌ها با استفاده از ساختار داده Union-Find و با پیچیدگی زمانی O(logV) انجام می‌شود. چون برای هر یال از E یال گراف این عملیات تکرار می‌شود، در مجموع پیچیدگی زمانی این مرحله برابر با O(ElogV) خواهد بود.

با جمع‌بندی مراحل بالا می‌توان گفت به طور کلی، پیچیدگی زمانی الگوریتم کراسکال برابر خواهد بود با:

پیاده‌سازی الگوریتم کراسکال در پایتون

برای این کار ابتدا باید کلاس UnionFind را تعریف کنیم که برای مدیریت مجموعه‌های مجزا استفاده می‌شود. این کلاس شامل دو تابع اصلی find و union است. تابع find برای پیدا کردن ریشه مجموعه‌ای که یک عنصر در آن قرار دارد و تابع union برای ادغام دو مجموعه جداگانه استفاده می‌شود:

# Union-Find class
class UnionFind:
  # Initialize
  def __init__(self, n):
    self.parent = list(range(n))
    self.rank = [0] * n
  # Find root
  def find(self, u):
    if self.parent[u] != u:
      self.parent[u] = self.find(self.parent[u])
    return self.parent[u]
  # Union sets
  def union(self, u, v):
    root_u = self.find(u)
    root_v = self.find(v)
    if root_u != root_v:
      if self.rank[root_u] > self.rank[root_v]:
        self.parent[root_v] = root_u
      elif self.rank[root_u] < self.rank[root_v]:
        self.parent[root_u] = root_v
      else:
        self.parent[root_v] = root_u
        self.rank[root_u] += 1

سپس در تابع kruskal_algorithm، ابتدا نگاشت بین رئوس و اندیس‌ها را ایجاد کرده و یال‌ها را با استفاده از این نگاشت استخراج و بر اساس وزنشان مرتب می‌کنیم. سپس، با استفاده از کلاس UnionFind، یال‌های مرتب‌شده را بررسی و اگر یال انتخابی باعث ایجاد حلقه نشود، آن را به درخت پوشای کمینه اضافه می‌کنیم:

# Kruskal's algorithm
def kruskal_algorithm(graph):
  # List of nodes
  nodes = list(graph.keys())
  # Map each node to an index
  node_index = {node: idx for idx, node in enumerate(nodes)}
  # Create a list of edges with weights
  edges = [(weight, node_index[u], node_index[v]) for u in graph for v, weight in graph[u].items()]
  # Sort edges by weight
  edges.sort()
  # Number of nodes in the graph
  n = len(graph)
  # Initialize Union-Find structure
  uf = UnionFind(n)
  # List to store the edges of the MST
  mst_edges = []
  # Process edges
  for weight, u, v in edges:
    # If nodes u and v are not connected, add the edge to the MST
    if uf.find(u) != uf.find(v):
      uf.union(u, v)
      mst_edges.append((nodes[u], nodes[v], weight))
      # Print the current MST edges
      print(mst_edges)

برای اجرای این الگوریتم روی گراف شکلی که در بخش قبلی آن را بررسی کردیم، به صورت زیر عمل کرده و می‌بینیم که نتیجه با چیزی که ما بدست آوردیم یکسان است:

graph = {
  'A': {'B': 5, 'E': 10, 'F': 7},
  'B': {'A': 5, 'C': 7, 'F': 8},
  'C': {'B': 7, 'F': 7, 'G': 6},
  'D': {'E': 9, 'F': 5, 'G': 5},
  'E': {'A': 10, 'D': 9, 'F': 8},
  'F': {'A': 7, 'B': 8, 'C': 7, 'D': 5, 'E': 8, 'G': 4},
  'G': {'C': 6, 'D': 5, 'F': 4}
}

kruskal_algorithm(graph)

[(‘F’, ‘G’, 4)]
[(‘F’, ‘G’, 4), (‘A’, ‘B’, 5)]
[(‘F’, ‘G’, 4), (‘A’, ‘B’, 5), (‘D’, ‘F’, 5)]
[(‘F’, ‘G’, 4), (‘A’, ‘B’, 5), (‘D’, ‘F’, 5), (‘C’, ‘G’, 6)]
[(‘F’, ‘G’, 4), (‘A’, ‘B’, 5), (‘D’, ‘F’, 5), (‘C’, ‘G’, 6), (‘A’, ‘F’, 7)]
[(‘F’, ‘G’, 4), (‘A’, ‘B’, 5), (‘D’, ‘F’, 5), (‘C’, ‘G’, 6), (‘A’, ‘F’, 7), (‘E’, ‘F’, 8)]

مزایا و معایب الگوریتم‌ کراسکال

الگوریتم کراسکال مزایای منحصر به فردی دارد که در ادامه آن‌ها را بررسی می‌‌کنیم:

  • کارایی در گراف‌های پراکنده: الگوریتم کراسکال در گراف‌های پراکنده که تعداد یال‌ها نسبت به تعداد راس‌ها کمتر است، عملکرد بهتری دارد و معمولاً سریع‌تر از الگوریتم پریم عمل می‌کند.
  • پیاده‌سازی ساده: الگوریتم کراسکال به‌دلیل ساختار ساده و مراحل مشخص، به‌راحتی قابل پیاده‌سازی است. این سادگی آن را به یکی از گزینه‌های محبوب در آموزش و مسائل عملی تبدیل کرده است.
  • استفاده از مجموعه‌ها: استفاده از ساختار داده‌ای مجموعه‌های مجزا (Disjoint Set) در الگوریتم کراسکال، کارایی آن را در تشخیص و جلوگیری از تشکیل حلقه‌ها بهبود می‌بخشد.

این الگوریتم نیز علاوه بر مزایای خود، دارای معایبی است که در ادامه به آن‌ها اشاره می‌کنیم:

  • نیاز به مرتب‌سازی یال‌ها: الگوریتم کراسکال برای شروع نیاز به مرتب‌سازی یال‌ها بر اساس وزن دارد. این مرحله پیچیدگی زمانی O(ElogE) را تحمیل می‌کند که برای گراف‌های بزرگ با تعداد یال‌های زیاد ممکن است مشکل‌ساز باشد.
  • پیچیدگی زمانی بالا در گراف‌های چگال: در گراف‌های چگال (Dense Graphs) که تعداد یال‌ها نسبت به راس‌ها زیاد است، پیچیدگی زمانی O(ElogE) ممکن است به کارایی پایین‌تری نسبت به الگوریتم پریم منجر شود.
  • نیاز به حافظه بیشتر: برای نگهداری یال‌ها و مجموعه‌ها، الگوریتم کراسکال ممکن است به حافظه بیشتری نسبت به الگوریتم پریم نیاز داشته باشد، به‌ویژه در گراف‌های بزرگ و پیچیده.
  • حساسیت به دقت محاسباتی: در مواردی که وزن یال‌ها به‌صورت اعشاری یا بسیار نزدیک به هم باشد، دقت محاسباتی می‌تواند بر نتایج الگوریتم تأثیر بگذارد و درخت پوشای کمینه نهایی ممکن است به‌درستی تشکیل نشود.

مقایسه الگوریتم‌های پریم و کراسکال

ویژگی‌هاالگوریتم پریمالگوریتم کروسکال
نوع الگوریتمحریصانهحریصانه
کاربرد اصلییافتن درخت پوشای کمینهیافتن درخت پوشای کمینه
پیچیدگی زمانی (ماتریس مجاورت)O(V2)O(ElogE)
پیچیدگی زمانی (لیست مجاورت و هیپ باینری)O(ElogV)O(ElogE)
شروع از یک راس دلخواهلبه‌ها با کمترین وزن
روش انتخاب لبه‌هااضافه کردن لبه‌ها به گراف در حال ساختاضافه کردن لبه‌ها به ترتیب وزن
قابلیت استفاده با گراف‌های متراکمعملکرد بهترعملکرد بهتر با گراف‌های پراکنده
ساختار داده مورد نیازماتریس مجاورت یا لیست مجاورتلیست لبه‌ها
عملکرد کلیساده‌تر و قابل درک‌تر برای گراف‌های متراکممناسب‌تر برای گراف‌های با تعداد زیاد لبه‌ها
تعداد مقایسه لبه‌هاکمتربیشتر
پشتیبانی از گراف‌های جهت‌دارخیرخیر
قابلیت استفاده با گراف‌های وزن‌داربلهبله
استفاده از هیپبله، در پیاده‌سازی با هیپ باینریخیر، به طور مستقیم
مدیریت جنگل‌های گرافیک درخت پوشای منفردچندین جنگل که در نهایت یکی می‌شوند
گام‌های افزایشیاضافه کردن یال به درخت پوشااضافه کردن یال به مجموعه جنگل‌ها
ایده اصلیگسترش یک زیرمجموعه از گرافترکیب زیرمجموعه‌ها به تدریج
تضمین درخت پوشای کمینهبلهبله

کاربردهای درخت پوشای کمینه

درخت پوشای کمینه کاربردهای گسترده‌ای دارد که از طراحی شبکه‌های کامپیوتری و سیستم‌های حمل و نقل گرفته تا مدارهای الکتریکی و شبکه‌های توزیع آب و برق را شامل می‌شود. این الگوریتم‌ها می‌توانند در بهینه‌سازی مسیرها، کاهش هزینه‌های نصب و نگهداری، و بهبود عملکرد سیستم‌ها نقش بسزایی ایفا کنند. در ادامه کاربردهای این درخت را در حوزه‌های مختلف بررسی می‌کنیم:

  • طراحی شبکه‌های کامپیوتری: درخت پوشای کمینه (MST) می‌تواند به طراحی شبکه‌های کامپیوتری با کمترین هزینه کمک کند. با استفاده از این درخت، تمامی نودهای شبکه با کمترین طول کابل به یکدیگر متصل می‌شوند که این موضوع به کاهش هزینه‌های نصب و نگهداری منجر می‌شود.
  • سیستم‌های حمل و نقل: MST در طراحی و بهینه‌سازی مسیرهای حمل و نقل بسیار مؤثر است. با استفاده از این الگوریتم، می‌توان مسیری را پیدا کرد که تمامی نقاط مهم را با کمترین هزینه و زمان به یکدیگر متصل کند، که منجر به کاهش مصرف سوخت و بهینه‌سازی زمان سفر می‌شود.
  • مدارهای الکتریکی: در طراحی مدارهای الکتریکی، MST می‌تواند به کاهش طول کل سیم‌ها و در نتیجه کاهش افت ولتاژ کمک کند. این موضوع باعث بهینه‌سازی عملکرد مدار و کاهش هزینه‌های تولید می‌شود.
  • شبکه‌های توزیع آب و برق: MST در طراحی شبکه‌های توزیع آب و برق نیز کاربرد دارد. با استفاده از این الگوریتم، می‌توان سیستم‌هایی را طراحی کرد که با کمترین هزینه، تمامی نقاط مصرف را به منابع تأمین متصل کنند.
  • شبکه‌های ارتباطی: MST در طراحی شبکه‌های ارتباطی مانند خطوط تلفن و شبکه‌های فیبر نوری کاربرد دارد. با استفاده از این الگوریتم، می‌توان مسیرهایی را تعیین کرد که هزینه‌های نصب و نگهداری شبکه را به حداقل برساند.
  • طراحی زیرساخت‌های شهری: در طراحی زیرساخت‌های شهری مانند جاده‌ها و پل‌ها، MST می‌تواند به یافتن مسیرهای بهینه کمک کند. این موضوع به کاهش هزینه‌های ساخت و نگهداری و همچنین بهبود کارایی زیرساخت‌ها منجر می‌شود.

کلام آخر درباره درخت پوشای کمینه

در این مقاله با مفهوم درخت پوشای کمینه (MST) و اهمیت آن در بهینه‌سازی شبکه‌ها و کاهش هزینه‌ها آشنا شدیم. درخت پوشای کمینه به ما این امکان را می‌دهد تا با کمترین هزینه ممکن تمامی رئوس یک گراف را به هم متصل کنیم. همچنین دو الگوریتم معروف پریم و کراسکال برای پیدا کردن MST را بررسی کردیم. هر یک از این الگوریتم‌ها ویژگی‌ها و کاربردهای خاص خود را دارند و بسته به نوع گراف و نیازهای مسأله می‌توان از آن‌ها بهره برد.

الگوریتم پریم با انتخاب یال‌های کم‌وزن به صورت تکراری و افزودن آن‌ها به یک زیرمجموعه از گراف، تا زمانی که تمامی رئوس درخت پوشا شوند، عمل می‌کند. این الگوریتم به خصوص در گراف‌های چگال کارایی خوبی دارد. از سوی دیگر، الگوریتم کراسکال با مرتب‌سازی یال‌ها بر اساس وزن و انتخاب یال‌ها به ترتیب وزن، بدون ایجاد حلقه، درخت پوشای کمینه را تشکیل می‌دهد. این روش در گراف‌های پراکنده که تعداد یال‌ها نسبت به تعداد راس‌ها کمتر است، عملکرد بهتری دارد.

در نهایت، MST به عنوان یکی از ابزارهای قدرتمند در نظریه گراف، نقش مهمی در بهبود عملکرد و کارایی سیستم‌های مختلف از جمله شبکه‌های کامپیوتری، حمل و نقل و سیستم‌های الکتریکی دارد. با استفاده از این الگوریتم‌ها می‌توانیم به طور کارآمد و دقیق شبکه‌ها را بهینه‌سازی کنیم و بهترین مسیرها را برای ارتباطات و حمل و نقل پیدا کنیم. این مفاهیم و الگوریتم‌ها نه تنها در علوم کامپیوتر و ریاضیات بلکه در بسیاری از زمینه‌های مهندسی و علمی دیگر نیز کاربردهای گسترده‌ای دارند.

سوالات متداول

چرا درخت پوشای کمینه در طراحی شبکه‌های کامپیوتری مهم است؟

درخت پوشای کمینه به ما این امکان را می‌دهد تا تمامی گره‌های یک شبکه را با کمترین هزینه ممکن به هم متصل کنیم. این ویژگی به بهینه‌سازی پهنای باند (Bandwidth) و کاهش هزینه‌های انتقال داده کمک می‌کند.

 تفاوت‌های اصلی بین الگوریتم پریم و الگوریتم کراسکال چیست؟

الگوریتم پریم از یک نود شروع کرده و یال‌ها را به ترتیب کمترین وزن اضافه می‌کند تا تمامی نودها پوشش داده شوند. الگوریتم کراسکال ابتدا یال‌ها را بر اساس وزن مرتب کرده و سپس آن‌ها را به ترتیب اضافه می‌کند بدون اینکه حلقه‌ای ایجاد شود. الگوریتم پریم برای گراف‌های چگال (Dense Graphs) مناسب‌تر است، در حالی که الگوریتم کراسکال در گراف‌های پراکنده (Sparse Graphs) عملکرد بهتری دارد.

چگونه می‌توان از درخت پوشای کمینه در بهینه‌سازی مسیرهای حمل و نقل استفاده کرد؟

درخت پوشای کمینه می‌تواند در طراحی مسیرهای بهینه برای حمل و نقل استفاده شود تا هزینه‌های سوخت و زمان سفر کاهش یابد. با استفاده از MST، تمامی نقاط مهم (مانند شهرها یا ایستگاه‌ها) با کمترین هزینه ممکن به هم متصل می‌شوند.

چه عواملی بر یکتایی (Uniqueness) درخت پوشای کمینه تاثیر می‌گذارند؟

اگر تمامی یال‌های یک گراف دارای وزن‌های منحصر به فرد باشند، درخت پوشای کمینه یکتا خواهد بود. در گراف‌هایی که وزن یال‌ها تکراری هستند، ممکن است چندین درخت پوشای کمینه با مجموع وزن‌های یکسان وجود داشته باشد.

چگونه الگوریتم‌های MST در بهینه‌سازی سیستم‌های الکتریکی مورد استفاده قرار می‌گیرند؟

در سیستم‌های الکتریکی، درخت پوشای کمینه می‌تواند به طراحی مدارهایی با کمترین طول سیم (Wire Length) و کمترین افت ولتاژ (Voltage Drop) کمک کند. این کار باعث کاهش هزینه‌های تولید و بهبود کارایی مدارهای الکتریکی می‌شود.

یادگیری تحلیل داده را از امروز شروع کنید!

دنیای داده‌ها جذاب است و دانستن علم داده، توانایی تحلیل داده‌، یا بازاریابی مبتنی بر داده، شما را برای فرصت‌های شغلی بسیاری مناسب می‌کند. فارغ از رشته‌ و پیش‌زمینه‌، می‌توانید حالا شروع کنید و از سطح مقدماتی تا پیشرفته بیاموزید. اگر دوست دارید به این حوزه وارد شوید، پیشنهاد می‌کنیم با کلیک روی این لینک قدم اول را همین حالا بردارید.

مشاوران کافه‌تدریس به شما کمک می‌کنند مسیر یادگیری برای ورود به این حوزه را شروع کنید:

دوره جامع دیتا ساینس و ماشین لرنینگ