https://www.acmicpc.net/problem/1197
최소 스패닝 트리(MST)란?
모든 정점을 연결하는 가중치의 합이 최소가 되는 트리로, MST라고 부른다.
즉, 양방향(무향) 가중 글리프로 각 정점 사이의 최소 비용의 경로를 구하여 모든 정점을 연결할 수 있다.
MST를 해결하기 위해 KRUSKAL 알고리즘과 PRIM 알고리즘이 있습니다.
KRUSAKL 알고리즘
간선을 하나씩 탐색하고 MST를 찾는 알고리즘이다.
대응하는 알고리즘에서는, 서로의 세트에 사용되는 유니온 find 가 사용됩니다.
모든 간선을 가중 기준으로 오름차순으로 정렬하고, 가중이 낮은 간선에서 하나씩 연결한다.
이 때, 대응하는 간선에 접속하려고 하는 정점이 현재의 정점과 사이클 구조를 이루지 않아야 한다.
static long kruskal() {
long result = 0;
int cnt = 0;
for (Edge edge : edges) {
// 두 간선이 싸이클이 형성되지 않았으면
if (union(edge.from, edge.to)) {
result += edge.weight; // 가중치 누적
if (++cnt == V-1) break; // 모든 노드를 연결했으면 break;
}
}
return result;
}
PRIM 알고리즘
각 정점으로 이어지는 간선 중에서 하나씩 선택해서 MST를 만드는 방식이다.
이 때, 우선 순위 큐를 이용하여, 선택된 정점에 인접하는 정점 중 가장 가까운 정점을 선택한다.
간선을 중심으로 탐색할지 혹은 정점을 중심으로 탐색할지에 따라 크루스컬과 프림을 적절히 선택하면 좋은 것 같다.
간선이 적으면 크루스컬, 그렇지 않으면 프림 알고리즘이 탐색에 유리하다.
static int prim() {
pq.offer(new Edge(1, 0)); // 임의의 정점부터 시작
int total = 0, cnt = 0;
while (!
pq.isEmpty()) {
Edge cur = pq.poll(); // 가중치가 낮은 간선을 가진 정점부터 선택
int node = cur.to;
int cost = cur.weight;
if(v(node)) continue; // 이미 방문했던 노드라면 continue;
v(node) = true; // 노드 방문 처리
total += cost;
for (Edge edge : edges(node)) // 현재 정점과 인접한 정점들의 간선 추가
if (!
v(edge.to)) pq.offer(edge);
if (++cnt == V) break;
}
return total;
}
풀코드(KRUSKAL)
import java.io.*;
import java.util.*;
public class Main {
static class Edge implements Comparable<Edge> {
int from, to, weight;
public Edge(int from, int to, int weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
@Override
public int compareTo(Edge o) {
return Integer.compare(weight, o.weight);
}
}
static int V, E;
static int () p;
static Edge () edges;
static void make() {
p = new int(V+1);
for (int i = 1; i <= V; i++) p(i) = i;
}
static int find(int a) {
if (p(a) == a) return a;
return p(a) = find(p(a));
}
static boolean union(int a, int b) {
int aRoot = find(a);
int bRoot = find(b);
if (aRoot == bRoot) return false;
p(bRoot) = aRoot;
return true;
}
static long kruskal() {
long result = 0;
int cnt = 0;
for (Edge edge : edges) {
// 두 간선이 싸이클이 형성되지 않았으면
if (union(edge.from, edge.to)) {
result += edge.weight;
if (++cnt == V-1) break;
}
}
return result;
}
public static void main(String() args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
V = Integer.parseInt(st.nextToken());
E = Integer.parseInt(st.nextToken());
edges = new Edge(E);
for (int i = 0; i < E; i++) {
st = new StringTokenizer(br.readLine(), " ");
int from = Integer.parseInt(st.nextToken());
int to = Integer.parseInt(st.nextToken());
int weight = Integer.parseInt(st.nextToken());
edges(i) = new Edge(from, to, weight); // 간선별로 연결된 정점과 가중치 등록
}
Arrays.sort(edges); // 가중치별로 정렬
make(); // 각 노드 서로소 집합 생성
// 크루스칼 알고리즘 최소 가중치 합 출력
System.out.println(kruskal());
br.close();
}
}
풀 코드 (PRIM)
import java.io.*;
import java.util.*;
public class Main {
static class Edge implements Comparable<Edge> {
int to, weight;
public Edge(int to, int weight) {
this.to = to;
this.weight = weight;
}
@Override
public int compareTo(Edge o) {
return Integer.compare(weight, o.weight);
}
}
static int V, E;
static boolean () v;
static List<Edge>() edges;
static PriorityQueue<Edge> pq = new PriorityQueue<>();
static int prim() {
pq.offer(new Edge(1, 0));
int total = 0, cnt = 0;
while (!
pq.isEmpty()) {
Edge cur = pq.poll();
int node = cur.to;
int cost = cur.weight;
if(v(node)) continue; // 이미 방문했던 노드라면 continue;
v(node) = true; // 노드 방문 처리
total += cost;
for (Edge edge : edges(node))
if (!
v(edge.to)) pq.offer(edge);
if (++cnt == V) break;
}
return total;
}
public static void main(String() args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine(), " ");
V = Integer.parseInt(st.nextToken());
E = Integer.parseInt(st.nextToken());
edges = new ArrayList(V+1);
for (int i = 1; i <= V; i++)edges(i) = new ArrayList<>();
v = new boolean(V+1);
for (int i = 0; i < E; i++) {
st = new StringTokenizer(br.readLine(), " ");
int from = Integer.parseInt(st.nextToken());
int to = Integer.parseInt(st.nextToken());
int weight = Integer.parseInt(st.nextToken());
edges(from).add(new Edge(to, weight));// 무향 그래프이므로, 양방향에 추가!
!
!
edges(to).add(new Edge(from, weight));
}
System.out.println(prim());
br.close();
}
}
회고
먼저 크루스컬로 제출하고 프림 알고리즘을 공부하기 위해 프림으로도 한 번 풀어 보았습니다.
간선 정보를 양방향으로 등록하지 않았고 여러 번 잘못됐다.
양방향 가중치 그래프에 사용되는 것을 잊지 마십시오.