본문 바로가기
백준_JAVA

[백준] 1197번 - 최소 스패닝 트리

by stonage 2023. 5. 23.

 

 

1197번: 최소 스패닝 트리

첫째 줄에 정점의 개수 V(1 ≤ V ≤ 10,000)와 간선의 개수 E(1 ≤ E ≤ 100,000)가 주어진다. 다음 E개의 줄에는 각 간선에 대한 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 정점과 B번 정점이

www.acmicpc.net

 

 

프로그래머스 섬 연결하기와 동일한 문제이다. 

 

[프로그래머스] 섬 연결하기

처음 시도 했을 때에는 쉽게 풀릴 것 같았다. 첫 번째 시도에는 costs 에서 가중치 기준 오름차순 정렬하여 단순하게 가중치가 작은 간선만을 연결하며 방문처리 하는 방식으로 구현하였지만 처

stonage.tistory.com

 

 

무방향 가중치 그래프에서 모든 정점을 연결하는 동시에 가중치 합의 최소를 구하는 '최소 신장 트리'를 구하는 문제이다. 

문제풀이에 사용할 수 있는 알고리즘은 크루스칼 알고리즘, 프림 알고리즘이 있고, 두 방법을 모두 사용하여 문제풀이를 해보겠다.

 

우선 최소 신장 트리를 구하는 두 알고리즘에 대한 설명은 아래와 같다.

 

최소 신장 트리(Minimum Spanning Tree)

Spanning Tree 무방향 연결 그래프 내의 모든 정점을 연결하고 사이클이 없는 그래프를 의미하며 신장 트리라고도 부른다. N개의 정점을 가지는 그래프의 최소 간선의 수는 N-1 개이고, 신장 트리의

stonage.tistory.com

 

 

 

 

 

크루스칼 알고리즘을 사용하는 경우 원리는 부모 노드가 같은 두 정점은 같은 트리에 포함된다는 성질을 이용한다.  그러기 위해서는 부모 노드를 저장하는 배열 parent[]가 필요하고 간선의 가중치 기준 오름차순 정렬을 해야한다. 그 다음 가중치가 가장 작은 간선부터 오름차순으로 진행한다. 

출발하는 from 정점과 도착하는 to 정점의 부모 노드가 서로 같다면 해당 간선은 고려하지 않는다. 반면 두 정점의 부모 노드가 서로 다르다면 해당 간선의 가중치를 최종 결과에 더해주고 두 정점의 부모 노드를 from 정점의 부모 노드로 같게 만들어 준다. 

 

여담으로 이전까지 객체 정렬에 람다 표현식을 주로 사용했었는데 Comparable 을 구현하는 방식을 연습겸 사용하였다. 

 

 

크루스칼 알고리즘을 사용한 전체 코드

import java.io.*;
import java.util.*;

public class Main {
    
    private static class Node implements Comparable<Node>{
        int from;
        int to;
        int weight;
        public Node (int from, int to, int weight) {
            this.from = from;
            this.to = to;
            this.weight = weight;
        }
        
        @Override
        public int compareTo (Node node) {
            return this.weight - node.weight;
        }
    }
    
    
    static int V, E, parent[];
    
    
    public static void main (String[] args) throws IOException {
        
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        
        String[] input = br.readLine().split(" ");
        V = Integer.parseInt(input[0]+1);
        E = Integer.parseInt(input[1]);
        
        parent = new int[V+1];
        Node[] costs = new Node[E];
        
        // 처음 시작 시 정점 i의 부모 노드는 자기 자신이다.
        for (int i=1; i<=V; i++) parent[i] = i;
        
        for (int i=0; i<E; i++) {
            input = br.readLine().split(" ");
            int from = Integer.parseInt(input[0]);
            int to = Integer.parseInt(input[1]);
            int weight = Integer.parseInt(input[2]);
            costs[i] = new Node(from, to, weight);
        }
        
        // 크루스칼 알고리즘을 사용하기 위해서는 가중치 기준 오름차순 정렬이 되어 있어야 한다.
        Arrays.sort(costs);
        
        int result = 0;
        
        // 가중치가 낮은 간선부터 탐색하므로 최소 가중치를 유지하면서 정점들으 연결할 수 있다.
        for (Node node : costs) {
            
            int from = node.from;
            int to = node.to;
            int weight = node.weight;
            
            int fromParent = findParent(from);
            int toParent = findParent(to);
            
            // from 과 to의 부모 노드가 같다면 두 정점은 이미 최소 가중치 상태로 연결되어 있으므로 pass
            if (fromParent == toParent) continue;
            
            result += weight;
            
            
            // to 정점의 부모를 from 정점의 부모와 동일하게 만들어서 이 다음에 to에 연결되는 다른 정점들도 
            // 현재의 from 정점과 연결되도록 만든다.
            parent[toParent] = fromParent;
        }
        
        
        System.out.println(result);
        
        br.close();
    }
    
    // 매개변수로 들어온 정점의 부모노드가 자기자신이 될 때까지 재귀호출한다. 
    static int findParent (int node) {
        if (parent[node] == node) return node;
        return findParent(parent[node]);
    }
}

 

 

 

프림 알고리즘은 우선순위 큐를 사용하여 구현한다. 낮은 가중치를 갖는 Node 객체가 높은 먼저 빠져나오는 우선순위 큐를 생성한다. 추가로 양방향 그래프의 두 정점간 관계를 저장하기 위한 List나 이중 배열이 필요한데 필자는 보통 list를 이용한다. 그 다음 출발할 정점을 큐에 넣어주는데 결과적으로 모든 정점이 연결되어야 하므로 출발 정점은 아무 거나 골라도 된다. 

그 다음 큐가 비어있지 않다면 아래의 과정을 반복한다.

 

i) 현재 큐에 들어있는 데이터 중 우선순위가 가장 높은(가중치가 가장 작은) 간선 정보를 꺼낸다.

ii)  해당 정점을 방문하지 않았다면 방문처리하고 출력할 결과에 해당 간선의 가중치를 더한다.

iii) 그 다음 현재 위치한 정점에서 이동할 수 있는 정점 중 아직 방문하지 않은 정점과의 간선 정보를 큐에 넣는다.

 

while 반복문의 매 시작에 큐에서 꺼내는 데이터는 언제나 가중치가 가장 작은 간선에 대한 정보이다. 따라서 최종적으로 최소 신장 트리를 형성하게 된다.

 

프림 알고리즘을 사용한 전체 코드

import java.io.*;
import java.util.*;

public class Main {
    
    private static class Node implements Comparable<Node> {
        
        int edge;
        int weight;
        public Node (int edge, int weight) {
            this.edge = edge;
            this.weight = weight;
        }
        
        @Override
        public int compareTo (Node o) {
            return this.weight - o.weight;
        }
    }
    
    
    public static void main (String[] args) throws IOException {
        
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        PriorityQueue<Node> que = new PriorityQueue<>();
            
        String[] input = br.readLine().split(" ");
        int V = Integer.parseInt(input[0]+1);
        int E = Integer.parseInt(input[1]);
        
        List<Node>[] link = new ArrayList[V+1];
        Node[] costs = new Node[E];
        boolean[] visited = new boolean[V+1];
        
        for (int i=1; i<=V; i++) link[i] = new ArrayList<>();
        
        for (int i=0; i<E; i++) {
            input = br.readLine().split(" ");
            int from = Integer.parseInt(input[0]);
            int to = Integer.parseInt(input[1]);
            int weight = Integer.parseInt(input[2]);
            link[from].add(new Node(to, weight));
            link[to].add(new Node(from, weight));
        }
        
        
        int result = 0;
        
        que.offer(new Node(1, 0));
        
        while (!que.isEmpty()) {
            
            Node n = que.poll();
            
            // 이미 방문한 정점이라면 고려하지 않는다.
            if (visited[n.edge]) continue;
            
            int now = n.edge;
            int weight = n.weight;
            
            // 우선순위 큐에 의해 현 상태에서 연결할 수 있는 간선 중 가장 가중치가 작은 간선다. 
            visited[now] = true;
            result += weight;
            
            for (Node next : link[now]) {
            	// 다음으로 이동할 수 있는 정점이면서 아직 방문하지 않은 정점이면 큐에 넣는다.
                if (!visited[next.edge]) {
                    que.offer(next);
                }
            }
            
        }
        
        
        System.out.println(result);
        
        br.close();
    }
}

 

'백준_JAVA' 카테고리의 다른 글

[백준] 6497번 - 전력난  (0) 2023.05.24
[백준] 1717번 - 집합의 표현  (0) 2023.05.24
[백준] 9372번 - 상근이의 여행  (0) 2023.05.23
[백준] 12865번 - 평범한 배낭  (0) 2023.05.22
[백준] 9251번 - LCS  (1) 2023.05.22