프림 알고리즘
마지막 업데이트
프림 알고리즘은 최소 신장 트리(MST) - 모든 노드를 사이클 없이 연결하는 가장 저렴한 간선 집합 - 를 시작 노드에서 하나의 트리를 바깥쪽으로 키우며 구성합니다. 각 단계에서 트리에서 트리 바깥의 노드로 교차하는 모든 간선을 살펴보고 가장 저렴한 것을 추가합니다. 위의 재생을 눌러 항상 새 노드에 도달하는 최소 가중치 간선을 택하며 트리가 확장되는 모습을 확인하세요.
항상 최소 교차 간선을 선택하기 때문에 각 추가는 안전합니다(어떤 MST의 일부임이 보장됩니다). 각 바깥 노드로 가는 가장 저렴한 간선을 키로 하는 이진 힙 우선순위 큐를 사용하면 프림은 O(E log V)에 실행됩니다. 이는 하나의 연결된 트리를 키우는 대신 모든 간선을 전역적으로 정렬하는 크루스칼과 대조됩니다.
시간 및 공간 복잡도
| 구현 | 복잡도 | 비고 |
|---|---|---|
| 이진 힙 | O(E log V) | 교차 간선의 우선순위 큐 |
| 인접 행렬 | O(V²) | 더 단순함; 밀집 그래프에 적합 |
| 공간 | O(V + E) | 트리 소속 + 우선순위 큐 |
| 가장 적합한 경우 | 밀집 그래프 | 단일 시작 노드에서 성장 |
단계별 설명
| 단계 | 무슨 일이 일어나는가 |
|---|---|
| 1 | 아무 단일 노드로 트리를 시작한다. |
| 2 | 트리에서 트리 바깥의 노드로 교차하는 모든 간선을 살펴본다. |
| 3 | 가중치가 가장 작은 교차 간선을 선택한다. |
| 4 | 그 간선과 새 노드를 트리에 추가한다. |
| 5 | 모든 노드가 트리에 들어올 때까지 반복한다. |
풀이 예제
간선 A-B=1, A-C=3, B-C=2, B-D=4, C-D=5 를 가진 4개 노드 그래프의 MST를 A 에서 시작해 구성:
| 단계 | 트리 | 교차 간선 | 선택된 간선 |
|---|---|---|---|
| 1 | {A} | A-B=1, A-C=3 | A-B (가중치 1) |
| 2 | {A, B} | A-C=3, B-C=2, B-D=4 | B-C (가중치 2) |
| 3 | {A, B, C} | B-D=4, C-D=5 | B-D (가중치 4) |
| 4 | {A, B, C, D} | 없음 - 모든 노드가 트리에 있음 | 완료: MST 가중치 1+2+4 = 7 |
프림 알고리즘을 사용할 때
| 사용할 때 | 피할 때 |
|---|---|
| 연결된 무방향 가중 그래프의 최소 신장 트리가 필요할 때. | 그래프가 방향성이 있거나 최단 경로가 필요할 때 - 대신 다익스트라나 벨만-포드를 사용한다. |
그래프가 밀집(E 가 V² 에 가까움)할 때; O(V²) 행렬 형태가 단순하고 빠르다. | 그래프가 희소하고 간선이 이미 정렬되어 있거나 정렬하기 쉬울 때 - 크루스칼이 더 단순한 경우가 많다. |
| 트리를 한 영역에서 바깥쪽으로 키우고 싶을 때(예: 점진적 네트워크 배치). | 그래프가 비연결일 때 - 프림은 하나의 성분만 신장하므로 최소 신장 숲이 필요하다. |
| 이미 인접 구조와 우선순위 큐를 사용할 수 있을 때. | 전역 간선 집합에서 사이클을 감지해야 할 때 - union-find(크루스칼)가 이 형태에 더 잘 맞는다. |
Prim's Algorithm 코드
Python, JavaScript, Java, C++, C로 작성된 깔끔하고 실행 가능한 Prim's Algorithm 구현입니다. 언어를 선택해 코드를 복사하거나 Coddy 플레이그라운드에서 바로 열어보세요.
Python로 구현한 Prim's Algorithm 코드
1import heapq2
3
4def prim(graph, start):5 visited = {start}6 heap = [(w, start, v) for v, w in graph[start]]7 heapq.heapify(heap)8 mst, total = [], 09 while heap and len(visited) < len(graph):10 w, u, v = heapq.heappop(heap)11 if v in visited:12 continue13 visited.add(v)14 mst.append((u, v, w))15 total += w16 # Offer the new node's edges to the frontier17 for neighbor, weight in graph[v]:18 if neighbor not in visited:19 heapq.heappush(heap, (weight, v, neighbor))20 return mst, total21
22
23graph = {24 "A": [("B", 4), ("C", 1)],25 "B": [("A", 4), ("C", 3), ("D", 2)],26 "C": [("A", 1), ("B", 3), ("D", 5)],27 "D": [("B", 2), ("C", 5), ("E", 7)],28 "E": [("D", 7)],29}30
31mst, total = prim(graph, "A")32for u, v, w in mst:33 print(f"{u} - {v} (weight {w})")34print("Total MST weight:", total)JavaScript로 구현한 Prim's Algorithm 코드
1const graph = {2 A: { B: 4, C: 2 },3 B: { A: 4, C: 1, D: 5 },4 C: { A: 2, B: 1, D: 8, E: 10 },5 D: { B: 5, C: 8, E: 2 },6 E: { C: 10, D: 2 },7};8
9function prim(start) {10 const inTree = new Set([start]);11 const mst = [];12 let total = 0;13 while (inTree.size < Object.keys(graph).length) {14 // JS has no built-in heap: scan all crossing edges for the cheapest15 let best = null;16 for (const u of inTree) {17 for (const [v, w] of Object.entries(graph[u])) {18 if (!inTree.has(v) && (best === null || w < best[2])) {19 best = [u, v, w];20 }21 }22 }23 const [u, v, w] = best;24 inTree.add(v);25 mst.push(`${u}-${v} (${w})`);26 total += w;27 }28 return { mst, total };29}30
31const { mst, total } = prim("A");32console.log("MST edges:", mst.join(", "));33console.log("Total weight:", total);Java로 구현한 Prim's Algorithm 코드
1import java.util.ArrayList;2import java.util.List;3import java.util.PriorityQueue;4
5public class Main {6 public static void main(String[] args) {7 int n = 6;8 List<List<int[]>> adj = new ArrayList<>();9 for (int i = 0; i < n; i++) adj.add(new ArrayList<>());10 int[][] edges = {11 {0, 1, 4}, {0, 2, 3}, {1, 2, 1}, {1, 3, 2},12 {2, 3, 4}, {3, 4, 2}, {4, 5, 6}, {2, 5, 7}13 };14 for (int[] e : edges) {15 adj.get(e[0]).add(new int[]{e[1], e[2]});16 adj.get(e[1]).add(new int[]{e[0], e[2]});17 }18
19 boolean[] inMst = new boolean[n];20 // Always grow the tree along the cheapest crossing edge21 PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);22 pq.add(new int[]{0, 0}); // {node, edge weight}23 int total = 0, taken = 0;24 while (!pq.isEmpty() && taken < n) {25 int[] cur = pq.poll();26 if (inMst[cur[0]]) continue;27 inMst[cur[0]] = true;28 total += cur[1];29 taken++;30 for (int[] edge : adj.get(cur[0])) {31 if (!inMst[edge[0]]) pq.add(edge);32 }33 }34 System.out.println("MST nodes reached: " + taken);35 System.out.println("MST total weight: " + total);36 }37}C++로 구현한 Prim's Algorithm 코드
1#include <iostream>2#include <queue>3#include <vector>4
5int main() {6 // Undirected weighted graph: adj[u] = {(neighbor, weight), ...}7 std::vector<std::vector<std::pair<int, int>>> adj = {8 {{1, 2}, {3, 6}}, // 09 {{0, 2}, {2, 3}, {3, 8}, {4, 5}}, // 110 {{1, 3}, {4, 7}}, // 211 {{0, 6}, {1, 8}}, // 312 {{1, 5}, {2, 7}}, // 413 };14 int n = static_cast<int>(adj.size());15 std::vector<bool> inMST(n, false);16 using State = std::pair<int, int>; // (edge weight, node)17 std::priority_queue<State, std::vector<State>, std::greater<State>> pq;18 pq.push({0, 0});19 int total = 0, picked = 0;20 // Always grow the tree along the cheapest crossing edge21 while (!pq.empty() && picked < n) {22 auto [w, u] = pq.top();23 pq.pop();24 if (inMST[u]) continue;25 inMST[u] = true;26 total += w;27 ++picked;28 std::cout << "Add node " << u << " (edge weight " << w << ")\n";29 for (auto [v, weight] : adj[u]) {30 if (!inMST[v]) pq.push({weight, v});31 }32 }33 std::cout << "MST total weight: " << total << "\n";34 return 0;35}C로 구현한 Prim's Algorithm 코드
1#include <stdbool.h>2#include <stdio.h>3
4#define N 55#define INF 10000000006
7int main(void) {8 // Undirected weighted graph: w[u][v] = 0 means no edge9 int w[N][N] = {10 {0, 2, 0, 6, 0},11 {2, 0, 3, 8, 5},12 {0, 3, 0, 0, 7},13 {6, 8, 0, 0, 0},14 {0, 5, 7, 0, 0},15 };16 int key[N], parent[N];17 bool inMST[N] = {false};18 for (int v = 0; v < N; v++) {19 key[v] = INF;20 parent[v] = -1;21 }22 key[0] = 0;23 int total = 0;24 // O(V^2) scan for the cheapest crossing edge (no heap needed here)25 for (int iter = 0; iter < N; iter++) {26 int u = -1;27 for (int v = 0; v < N; v++) {28 if (!inMST[v] && (u == -1 || key[v] < key[u])) u = v;29 }30 inMST[u] = true;31 total += key[u];32 if (parent[u] != -1) {33 printf("Add edge %d - %d (weight %d)\n", parent[u], u, key[u]);34 }35 for (int v = 0; v < N; v++) {36 if (w[u][v] > 0 && !inMST[v] && w[u][v] < key[v]) {37 key[v] = w[u][v];38 parent[v] = u;39 }40 }41 }42 printf("MST total weight: %d\n", total);43 return 0;44}프림 알고리즘 FAQ
프림 알고리즘의 시간 복잡도는 얼마인가요?
O(E log V)에 실행됩니다. 각 단계에서 최소 교차 간선을 훑는 더 단순한 인접 행렬 버전은 O(V²)이며, 밀집 그래프에서는 더 빠를 수 있습니다. 둘 다 O(V + E) 공간을 사용합니다.프림 알고리즘과 크루스칼 알고리즘의 차이는 무엇인가요?
프림 알고리즘은 항상 최적의 MST를 찾나요?
크루스칼 대신 프림 알고리즘을 언제 사용해야 하나요?
O(V²) 행렬 형태는 모든 E 개의 간선을 정렬할 필요가 없고 시작 노드에서 하나의 트리를 키우기 때문입니다. 크루스칼은 간선 리스트 정렬이 저렴하고 union-find가 사이클 검사를 빠르게 유지하는 희소 그래프에서 빛을 발합니다. 둘 다 올바른 MST를 생성하므로 선택은 주로 간선 밀도와 이미 갖고 있는 자료 구조에 달려 있습니다.