[BOJ] 1197번 최소 스패닝 트리 java

밈무·2022년 5월 5일
0

문제

https://www.acmicpc.net/problem/1197
문제
그래프가 주어졌을 때, 그 그래프의 최소 스패닝 트리를 구하는 프로그램을 작성하시오.

최소 스패닝 트리는, 주어진 그래프의 모든 정점들을 연결하는 부분 그래프 중에서 그 가중치의 합이 최소인 트리를 말한다.

입력
첫째 줄에 정점의 개수 V(1 ≤ V ≤ 10,000)와 간선의 개수 E(1 ≤ E ≤ 100,000)가 주어진다. 다음 E개의 줄에는 각 간선에 대한 정보를 나타내는 세 정수 A, B, C가 주어진다. 이는 A번 정점과 B번 정점이 가중치 C인 간선으로 연결되어 있다는 의미이다. C는 음수일 수도 있으며, 절댓값이 1,000,000을 넘지 않는다.

그래프의 정점은 1번부터 V번까지 번호가 매겨져 있고, 임의의 두 정점 사이에 경로가 있다. 최소 스패닝 트리의 가중치가 -2,147,483,648보다 크거나 같고, 2,147,483,647보다 작거나 같은 데이터만 입력으로 주어진다.

출력
첫째 줄에 최소 스패닝 트리의 가중치를 출력한다.

풀이

크루스칼 알고리즘을 이용하여 풀이했다.

또한 프림 알고리즘을 이용해도 정답을 얻을 수 있다.

MST

Minimum Spanning Tree

신장 트리(Spanning Tree)

  • 무방향 그래프 G(V,E)에서 E에 속한 간선들
  • 사이클을 포함하지 않으며 모든 정점 V를 연결한 부분 그래프
  • n개의 정점 -> n-1개의 간선

MST

  • 최소 비용 신장 트리(최소 신장 트리)
  • 여러 신장 트리 중에 간선들의 가중치 합이 최소인 신장 트리

크루스칼(Kruskal)

  • 최소 신장 트리를 구하는 알고리즘
  • Greedy하게 모든 정점을 최소 비용으로 연결
  1. 모든 간선을 가중치 기준으로 오름차순으로 정렬
  2. 간선들을 순서대로 모든 정점이 연결될 때까지 연결
  3. Union-Find 알고리즘 이용하여 정점 연결(union)
  • 시간복잡도 : O(ElogE) (퀵정렬 사용시)
    -> E가 간선의 개수이기 때문에 간선이 적은 경우에 적합

프림(Prim)

  • 최소 신장 트리를 구하는 알고리즘
  • 임의의 시작점에서 현재 연결된 정점들에 연결되지 않은 정점들에 대해서 가장 가중치가 작은 정점을 연결하는 정점 선택 기반으로 작동
  1. 트리 집합을 단계적으로 확장
  2. 새로운 정점을 연결할 때마다 새로운 정점에서 갈 수 있는 아직 연결되지 않은 정점들에 대한 간선을 추가
  3. Priority Queue를 이용한 최소힙으로 구현 가능
  • 시간복잡도 : O(ElogV)
    -> 간선이 많은 경우에 적합

코드

크루스칼을 이용한 풀이

package BOJ.graph;

import java.util.*;
import java.io.*;
public class No1197_최소스패닝트리_크루스칼 {
    static int[] parent;
    public static void main(String[] args) throws IOException{
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st=new StringTokenizer(br.readLine());

        int V=Integer.parseInt(st.nextToken());
        int E=Integer.parseInt(st.nextToken());

        ArrayList<Edge> edges=new ArrayList<>();
        int answer=0;

        for(int i=0;i<E;i++){
            st=new StringTokenizer(br.readLine());
            int start=Integer.parseInt(st.nextToken());
            int end=Integer.parseInt(st.nextToken());
            int cost=Integer.parseInt(st.nextToken());

            edges.add(new Edge(start, end, cost));
        }

        //cost 기준으로 정렬(or 우선순위큐 써줘도 됨)
        edges.sort(new Comparator<Edge>(){
            @Override
            public int compare(Edge o1, Edge o2) {
                return Integer.compare(o1.c, o2.c);
            }
        });

        //parent 초기화
        //처음에는 자기자신을 root로 가지도록 설정
        parent=new int[V+1];
        for(int i=1;i<V+1;i++){
            parent[i]=i;
        }

        //트리 계산
        //isSameParent==true -> cycle 발생
        //false인 경우 cost를 answer에 합산 한 후, 한 트리로 union
        for(int i=0;i<E;i++){
            Edge edge=edges.get(i);
            if(!isSameParent(edge.s, edge.e)){
                answer+=edge.c;
                union(edge.s, edge.e);
            }
        }

        System.out.println(answer);

    }

    //parent 찾기
    static int findParent(int x){
        //루트가 아니면 재귀로 parent 찾기
        if(parent[x]!=x){
            parent[x]=findParent(parent[x]);
        }
        return parent[x];
    }

    //같은 parent인지 판별
    static boolean isSameParent(int x, int y){
        x=findParent(x);
        y=findParent(y);

        if(x==y)return true;
        else return false;
    }

    //Union
    static void union(int x, int y){
        x=findParent(x);
        y=findParent(y);

        if(x<y){
            parent[y]=x;
        }
        else{
            parent[x]=y;
        }
    }
}

class Edge{
    int s, e, c;
    Edge(int s, int e, int c){
        this.s=s;
        this.e=e;
        this.c=c;
    }
}

0개의 댓글