Algorithms/Java

백준 4386: 별자리 만들기 (java)

Jenn28 2023. 5. 19. 16:57

https://www.acmicpc.net/problem/4386

 

4386번: 별자리 만들기

도현이는 우주의 신이다. 이제 도현이는 아무렇게나 널브러져 있는 n개의 별들을 이어서 별자리를 하나 만들 것이다. 별자리의 조건은 다음과 같다. 별자리를 이루는 선은 서로 다른 두 별을 일

www.acmicpc.net

 

도현이는 우주의 신이다. 이제 도현이는 아무렇게나 널브러져 있는 n개의 별들을 이어서 별자리를 하나 만들 것이다. 별자리의 조건은 다음과 같다.

  • 별자리를 이루는 선은 서로 다른 두 별을 일직선으로 이은 형태이다.
  • 모든 별들은 별자리 위의 선을 통해 서로 직/간접적으로 이어져 있어야 한다.

별들이 2차원 평면 위에 놓여 있다. 선을 하나 이을 때마다 두 별 사이의 거리만큼의 비용이 든다고 할 때, 별자리를 만드는 최소 비용을 구하시오.

 

첫째 줄에 별의 개수 n이 주어진다. (1 ≤ n ≤ 100)

둘째 줄부터 n개의 줄에 걸쳐 각 별의 x, y좌표가 실수 형태로 주어지며, 최대 소수점 둘째자리까지 주어진다. 좌표는 1000을 넘지 않는 양의 실수이다.

 

 

사용 알고리즘: ArrayList, Priority Queue, Union-Find, Kruskal's

 

 

먼저, 별의 거리를 저장하는 Edge 클래스를 만들었다. Edge 클래스에서는 시작 정점, 도착 정점, 가중치를 받아준다.

Priority Queue 를 사용하기 위해 compareTo 메서드를 사용해서 가중치를 비교해준다.

import java.util.Scanner;
import java.util.ArrayList;
import java.util.PriorityQueue;

class Edge implements Comparable<Edge> { // 별의 거리를 저장하는 클래스
    int u, v; // 시작 정점, 도착 정점
    double w; // 가중치

    public Edge(int u, int v, double w) {
        this.u = u;
        this.v = v;
        this.w = w;
    }

    // 가중치가 작은 순으로 정렬
    @Override
    public int compareTo(Edge other) {
        return Double.compare(this.w, other.w);
    }
}

 

Cycle 을 만들지 않기 위해 Union-Find 를 활용해준다.

find 함수는 어떤 원소 a가 주어질 때, 이 원소가 속한 집합을 반환한다.

여기서는 원소 u가 속한 집합의 루트를 찾고 그 루트를 parent[u] 에 다시 넣어준다. 그렇게 해서 path compression 해준다.

 

path compression 이란?

더보기
path compression 예시 (우측)

 

public class BOJ4386 {
    static int[] parent;

    public static int find(int u) {
        if (parent[u] == u) { // u가 루트인 경우, 자기 자신을 리턴
            return u;
        }
        // path compression 사용
        return parent[u] = find(parent[u]); // u가 속한 집합의 루트를 찾는 함수
    }

 

main 함수에서 입출력을 담당한다. 

아직 StringTokenizer 가 익숙하지 않아서 시간이 오래걸리는 단점이 있지만 Scanner 로 입력을 받았다.

 

parent 에 배열을 만들고, 1부터 n 까지 설정해준다. 이 단계에서는 각각 자기 자신이 부모로 설정되어있다.

예를 들어, n = 3 이면 노드 1의 부모는 1이고, 노드 2의 부모는 2고, 노드 3의 부모는 3이다.

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();

        parent = new int[n+1];
        for (int i=1; i<=n; i++) {
            parent[i] = i; // 처음에는 자기 자신을 부모로 설정
        }

 

stars 라는 double 타입의 2차 배열을 만들고, 여기에 x 좌표와 y 좌표를 저장해준다.

        double[][] stars = new double[n][2];
        for (int i=0; i<n; i++) {
            stars[i][0] = in.nextDouble(); // x 좌표
            stars[i][1] = in.nextDouble(); // y 좌표
        }

 

이제 모든 간선의 리스트를 edges 라는 동적 배열에 저장할 것이다.

두 별 사이의 거리를 계산한 값을 distance 에 추가해준다. 여기 distance 가 가중치가 된다.

i (시작 정점), j (도착 정점), distance (가중치) 의 간선을 Edge 객체로 생성/표현해주고 edges 에 추가한다.

        ArrayList<Edge> edges = new ArrayList<>(); // 모든 간선의 리스트
        for (int i=0; i<n; i++) {
            for (int j=i+1; j<n; j++) {
                // 두 별 사이의 거리 계산
                double distance = Math.sqrt(Math.pow(stars[i][0] - stars[j][0], 2) + Math.pow(stars[i][1] - stars[j][1], 2));
                edges.add(new Edge(i, j, distance));
            }
        }
        double totalCost = kruskal(edges);
        System.out.printf("%.2f", totalCost);
    }

 

Edge 에서 가중치를 비교해놨는데, for 문에서 가중치가 작은 순서대로 정렬한다.

근데 이 부분 다시 봐야할듯? Edge edge 가 뭐지?

    public static double kruskal(ArrayList<Edge> edges) {
        // 간선들의 가중치를 오름차순으로 저장
        PriorityQueue<Edge> pq = new PriorityQueue<>();
        for (Edge edge : edges) {
            pq.add(edge); // 가중치가 작은 순서대로 정렬
        }

 

pq 에서 가장 큰 것부터 꺼내서 edge 에 저장한다.

시작 정점의 루트를 찾아서 uRoot 에 저장한다.

도착 정점의 루트를 찾아서 vRoot 에 저장한다.

 

이때, 시작 정점의 루트와 도착 정점의 루트가 같지 않은 경우에만 totalCost 에 가중치를 더해준다.

이후, 부모를 같은 부모로 설정해줌으로서 연결 완료가 된다!

        double totalCost = 0; // 최소 비용의 누적 값

        while (!pq.isEmpty()) {
            Edge edge = pq.poll();

            int uRoot = find(edge.u);
            int vRoot = find(edge.v);

            // 싸이클이 생성되지 않는 경우에만 가중치를 더하고 두 정점을 연결
            if (uRoot != vRoot) {
                totalCost += edge.w; // 현재 간선의 가중치 더함
                parent[uRoot] = vRoot; // uRoot와 vRoot 연결 (부모 갱신)
            }
        }
        return totalCost;
    }
}