728x90
반응형
최소 신장 트리(MST) 란?
그래프에서 모든 노드를 연결할 때 사용된 에지들의 가중치의 합을 최소로 하는 트리이다.
특징
- 사이클이 포함되면 가중치의 합이 최소가 될 수 없으므로 사이클을 포함하지 않는다.
- N개의 노드가 있으면 최소 신장 트리를 구성하는 에지의 개수는 항상 N - 1 개이다.
※ 절대적이지 않지만,
간선이 적으면 KRUSKAL, 간선이 많으면 PRIM 알고리즘이 유리
(1) Kruskal MST 알고리즘
(간선을 고르는 간선 중심)
핵심 이론
1. 에지 리스트로 그래프를 구현하고 유니온 파인드 배열 초기화하기
- 데이터를 노드가 아닌 에지 중심으로 저장하므로 에지 리스트의 형태로 저장한다.
- 사이클 처리를 위한 유니온 파인드 배열도 함께 초기화한다. 배열의 인덱스를 해당 자리의 값으로 초기화하면 된다.
2. 그래프 데이터를 가중치 기준으로 정렬하기
- 에지 리스트에 담긴 그래프 데이터를 가중치 기준으로 오름차순 정렬한다.
3. 가중치가 낮은 에지부터 연결 시도하기
- 가중치가 낮은 에지부터 순서대로 선택해 연결을 시도한다.
- 이때, 바로 연결하지 않고 이 에지를 연결했을 때 그래프에 사이클 형성 여부를 find 연산을 이용해 확인한 후 사이클이 형성되지 않을 때만 union 연산을 이용해 두 노드를 연결한다.
4. 전체 노드의 개수가 N개일 때, 연결한 에지의 개수가 N-1이 될 때까지 과정을 반복한다.
Use edgeList
public class MST_Kruskal_Test {
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(this.weight, o.weight);
}
}
static int V, E;
static Edge[] edgeList;
static int[] parents;
public static void main(String[] args) throws IOException {
BufferedReader in =
new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(in.readLine(), " ");
V = Integer.parseInt(st.nextToken());
E = Integer.parseInt(st.nextToken());
edgeList = new Edge[E];
parents = new int[V];
// 각 간선 및 가중치에 대한 정보(간선의 가중치)
int from, to, weight;
for (int i = 0; i < E; i++) {
st = new StringTokenizer(in.readLine(), " ");
from = Integer.parseInt(st.nextToken());
to = Integer.parseInt(st.nextToken());
weight = Integer.parseInt(st.nextToken());
edgeList[i] = new Edge(from, to, weight);
}
// 최초, 모든 간선을 가중치 기준 오름차순으로 정렬
Arrays.sort(edgeList);
// 정점 초기화
make();
// 가중치가 가장 낮은 간선부터 선택하면서 트리를 증가
int cnt = 0, res = 0;
for (Edge edge : edgeList) {
// 싸이클이 형성되지 않았다면
if (union(edge.from, edge.to)) {
res += edge.weight;
// N-1개의 간선이 선택될 때까지 반복
if (++cnt == V - 1) break;
}
}
System.out.println(res);
}
// 정점 초기화(자신을 대표자로)
private static void make() {
for (int i = 0; i < V; i++) {
parents[i] = i;
}
}
// Find : Path Compression
private static int find(int a) {
if (a == parents[a]) return a;
return parents[a] = find(parents[a]);
}
// Union
private static boolean union(int a, int b) {
int aRoot = find(a);
int bRoot = find(b);
// 사이클 존재 시 false
if (aRoot == bRoot) return false;
// 사이클이 생성되지 않는다면(다른 부모를 갖는다면) Union 수행
parents[bRoot] = aRoot;
return true;
}
}
(2) Prim 알고리즘
(정점을 고르는 점 중심)
시작 정점에서부터 출발하여 신장트리 집합을 단계적으로 확장 해나가는 방법, 즉, 이전 단계에서 만들어진 신장 트리를 확장하는 방법
[과정]
- 시작 단계에서는 시작 정점만이 MST(최소 비용 신장 트리) 집합에 포함된다.
- 앞 단계에서 만들어진 MST 집합에 인접한 정점들 중에서 최소 간선으로 연결된 정점을 선택하여 트리를 확장한다.
- 즉, 가장 낮은 가중치를 먼저 선택한다.
- 위의 과정을 트리가 (N-1)개의 간선을 가질 때까지 반복한다.
[인접행렬]
public class MST_Prim_Test {
static class Node implements Comparable<Node> {
// 목적지
int no;
// 비용
int edge;
public Node(int no, int edge) {
super();
this.no = no;
this.edge = edge;
}
@Override
public int compareTo(Node o) {
return this.edge - o.edge;
}
}
public static void main(String[] args) throws Exception {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int cnt = Integer.parseInt(br.readLine().trim());
int[][] input = new int[cnt][cnt];
boolean[] visited = new boolean[cnt];
int[] minEdge = new int[cnt];
PriorityQueue<Node> queue = new PriorityQueue<Node>();
StringTokenizer st;
for (int i = 0; i < cnt; i++) {
st = new StringTokenizer(br.readLine());
for (int j = 0; j < cnt; j++) {
input[i][j] = Integer.parseInt(st.nextToken());
}
minEdge[i] = Integer.MAX_VALUE;
} // i노드에서 j노드까지의 비용을 모두 배열에 저장
int result = 0, nodeCount = 0;
minEdge[0] = 0;
queue.offer(new Node(0, 0));
while (!queue.isEmpty()) {
// 일단 꺼내자. 그럼 젤 작은애가 나오겠찌.
Node minVertex = queue.poll();
// 근데 그 나온애가 이미 목적지가 확보된 녀석이라면 패스.
if (visited[minVertex.no]) continue;
// 확보되지 않은애가 나왔으면 고르자.
result += minVertex.edge;
visited[minVertex.no] = true;
if (++nodeCount == cnt) break;
for (int j = 0; j < cnt; j++) {
// 새로 확보된 녀석으로부터 출발하는 간선을 다 PQ에 넣는다.
if (!visited[j] && input[minVertex.no][j] != 0 && input[minVertex.no][j] < minEdge[j]) {
minEdge[j] = input[minVertex.no][j];
queue.offer(new Node(j, input[minVertex.no][j]));
}
}
}
System.out.println(result);
}
}
[인접 리스트]
public class MST_Prim_Test {
static int V, E;
static ArrayList<Node>[] adj;
static class Node implements Comparable<Node> {
int to, weight;
public Node(int to, int weight) {
super();
this.to = to;
this.weight = weight;
}
@Override
public int compareTo(Node o) {
return Integer.compare(this.weight, o.weight);
}
}
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
V = Integer.parseInt(st.nextToken()); // 정점 개수
E = Integer.parseInt(st.nextToken()); // 간선 개수
adj = new ArrayList[V + 1];
for (int i = 1; i <= V; i++) {
adj[i] = new ArrayList<>();
}
// Edge 정보 입력
for (int i = 0; i < E; i++) {
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
// 인접 리스트
adj[a].add(new Node(b, c));
adj[b].add(new Node(a, c));
}
System.out.println(prim());
}
private static long prim() {
boolean[] visited = new boolean[V + 1];
PriorityQueue<Node> pq = new PriorityQueue<>();
// 1번 노드부터 출발
pq.add(new Node(1, 0));
long res = 0;
int cnt = 0;
while(!pq.isEmpty()) {
Node edge = pq.poll();
// 이미 확인한 정점이면 pass
if(visited[edge.to]) continue;
res += edge.weight;
visited[edge.to] = true;
// 모든 노드를 방문했다면 return
if(++cnt == V) return res;
for (int i = 0; i < adj[edge.to].size(); i++) {
// 연결된 노드들 중 방문하지 않은 노드 찾기
Node next = adj[edge.to].get(i);
if(visited[next.to]) continue;
pq.add(next);
}
}
return res;
}
}
반응형
'Knowledge > 알고리즘' 카테고리의 다른 글
동적 계획법 (DP : Dynamic Programming) (0) | 2024.07.19 |
---|---|
세그먼트 트리 (0) | 2024.07.18 |
위상 정렬 (topology sort) (0) | 2024.07.10 |
유니온 파인드 알고리즘 (Union-find) (1) | 2024.07.06 |
최단 경로 알고리즘 (0) | 2024.07.04 |