선택된 간선의 개수가 N-1에 도달한다면 종료합니다. (혹은 전부 다 돌려도 상관없습니다.)
프림 알고리즘 (Prim)
임의의 노드에서 시작하여 연결된 노드들의 간선 중 가장 낮은 가중치를 갖는 간선을 선택해 나가는 방법을 프림 알고리즘이라고 합니다.
다익스트라 알고리즘과 거의 동일한 알고리즘이지만, 다익스트라와는 달리 음수 가중치가 있더라도 동작 가능합니다.
시간 복잡도는 우선순위 큐를 이용하면 O(ElogV)가 걸리며, 간선의 개수가 많을 때 크루스칼보다 유리합니다.
구현 방법
임의의 노드 하나를 선택합니다.
연결된 노드 중, 가중치가 작은 간선을 지닌 노드를 선택합니다.
이미 방문한 노드라면, 그 다음으로 가중치가 작은 노드를 선택합니다.
모든 정점이 선택될 까지 2번 과정을 반복합니다.
프로그래밍 언어로 구현
Java
크루스칼
import static algo.UnionFind.*;
import java.util.Arrays;
import java.util.Comparator;
import java.util.stream.IntStream;
/**
* edges = 간선 정보로 {1,2,5} 는 1 -> 2로 5의 가중치를 의미
* v = 정점의 개수
* e = 간선의 개수
*
* 여기서 UnionFind는 제가 직접 만들어둔 UnionFind를 사용했습니다. 관련 게시글이 블로그에 있으니 참고하세요.
*/
public class Kruskal {
// 최소 비용 구하기
public static int kruskal(int[][] edges, int v, int e) {
int weightSum = 0;
// 1. 간선들의 가중치를 오름차순으로 정렬
Arrays.sort(edges, Comparator.comparing(edge -> edge[2]));
// union-find를 사용하기 위한 배열 생성
int[] parents = IntStream.range(0, v + 1).toArray();
// 2. 간선을 순서대로 선택
for (int i = 0; i < e; i++) {
// 사이클이 없을 때만
if (find(parents, edges[i][0]) != find(parents, edges[i][1])) {
union(parents, edges[i][0], edges[i][1]);
// 가중치 합산
weightSum += edges[i][2];
}
}
return weightSum;
}
}
프림
프림은 우선순위큐로 구현하므로, 자바에서 제공하는 PriortiyQueue를 이용했습니다.
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;
public class Prim {
public static int prim(int[][] edges, int v, int e) {
int weightSum = 0;
// 간선 정보를 그래프로 만들어 주는 밑작업
ArrayList<ArrayList<Node>> graph = new ArrayList<>();
for (int i = 0; i < v + 1; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < e; i++) {
ArrayList<Node> nodeA = graph.get(edges[i][0]);
ArrayList<Node> nodeB = graph.get(edges[i][1]);
nodeA.add(new Node(edges[i][1], edges[i][2]));
nodeB.add(new Node(edges[i][0], edges[i][2]));
}
// 노드에 방문했는지 체크할 배열 생성
boolean[] visited = new boolean[v + 1];
// 노드들의 간선 중 가중치가 가장 작은 간선을 선택하기 위한 우선순위 큐
PriorityQueue<Node> pq = new PriorityQueue<>(Comparator.comparing(edge -> edge.weight));
// 1번 노드부터 시작
pq.add(new Node(1, 0));
// MST 간선 수 체크
int cnt = 0;
while (!pq.isEmpty()) {
// 현재 노드
Node cur = pq.poll();
cnt += 1;
// 현재 노드가 방문한 노드라면 건너뛰기
if (visited[cur.to]) {
continue;
}
// 현재 노드 방문 처리
visited[cur.to] = true;
// 현재 노드로 올 때 발생한 가중치 더하기
weightSum += cur.weight;
// 현재 노드가 가지고 있는 인접 노드 수만큼 반복
for (int i = 0; i < graph.get(cur.to).size(); i++) {
// 인접 노드 가져오기
Node adjNode = graph.get(cur.to).get(i);
// 방문한 노드라면 건너뛰기
if (visited[adjNode.to]) {
continue;
}
// 인접 노드를 우선순위 큐에 추가
pq.offer(adjNode);
}
}
return weightSum;
}
// 이동하려는 노드를 위한 자료구조
static class Node {
int to;
int weight;
public Node(int to, int weight) {
this.to = to;
this.weight = weight;
}
}
}