[ STUDY ]/CodingTest

[ 그래프 ] 위상 정렬(백준 2252, 1516)

김강니 2024. 11. 10. 02:15

 위상 정렬

위상 정렬은 사이클이 없는 방향 그래프에서 노드 순서를 찾는 알고리즘이다.

  • 사이클이 없어야한다.
  • 시간복잡도는 O(V+E)
  • 항상 유일한 값으로 정렬되지 않는다.

💡  위상 정렬의 원리 이해하기

1. 진입 차수 배열을 만들어준다.

    진입 차수는 자기 자신을 가리키는 에지의 개수이다.

    예를 들어 첫번째 리스트를 보면 D[2]++, D[3]++ 계산을 할 수 있다.

 

2. 진입 차수 배열에서 진입 차수가 0인 노드를 선택하고 선택한 보드를 정렬 배열에 저장한다. 그 후 인접 리스트에서 선택된 노드가 가리키는 노드들의 진입차수를 1씩 뺀다.

0인 노드를 선택할 때 3을 먼저 선택할 수도 2를 먼저 선택할 수도 있다. 이러한 이유로 위상정렬은 유일한 값으로 정렬되지 않는 것이다.

 

 

 

 2252 : 줄 세우기

문제

N명의 학생들을 키 순서대로 줄을 세우려고 한다. 각 학생의 키를 직접 재서 정렬하면 간단하겠지만, 마땅한 방법이 없어서 두 학생의 키를 비교하는 방법을 사용하기로 하였다. 그나마도 모든 학생들을 다 비교해 본 것이 아니고, 일부 학생들의 키만을 비교해 보았다.

일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램을 작성하시오.

 

입력

첫째 줄에 N(1 ≤ N ≤ 32,000), M(1 ≤ M ≤ 100,000)이 주어진다. M은 키를 비교한 횟수이다. 다음 M개의 줄에는 키를 비교한 두 학생의 번호 A, B가 주어진다. 이는 학생 A가 학생 B의 앞에 서야 한다는 의미이다.

학생들의 번호는 1번부터 N번이다.

 

출력

첫째 줄에 학생들을 앞에서부터 줄을 세운 결과를 출력한다. 답이 여러 가지인 경우에는 아무거나 출력한다.

입력 출력
3 2
1 3
2 3
1 2 3
4 2
4 2
3 1
4 2 3 1

 

문제 풀이

  • 내 앞에 있는 친구의 번호를 리스트에 추가하고 진입배열도 갱신한다.

 

실행 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Stack;
import java.util.StringTokenizer;

public class P2252_Topology {
    static Stack<Integer> sort = new Stack();
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());
        int N = Integer.parseInt(st.nextToken()); //학생 수
        int M = Integer.parseInt(st.nextToken()); //비교할 수

        // 진입차수배열 & 인접리스트
        int[] D = new int[N + 1];
        ArrayList<Integer>[] A = new ArrayList[N + 1];
        for (int i = 1; i <= N; i++) {
            A[i] = new ArrayList<>();
        }

        // 비교할 횟수만큼 반복
        for(int i = 0; i < M; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());
            A[b].add(a); //내 앞에 오는 사람으로 연결
            D[a]++; //진입차수 더해줌
        }

        Topology(A, D);

        for(int i = 0; i < N; i++) {
            System.out.print(sort.pop()+" ");
        }
    }

    private static void Topology(ArrayList<Integer>[] a, int[] d) {
        //진입차수가 0이면 배열에 추가
        for(int i = 1; i < d.length; i++) {
            if(d[i] == 0) {
                sort.add(i);
            }
        }

        int idx = 0;
        while(idx < sort.size()) {
            int curr = sort.get(idx); //정렬된 리스트에서 현재 노드 가져오기
            idx++;

            //현재 노드와 연결된 노드들의 진입 차수를 줄이고 0이 되면 추가
            for(int next : a[curr]) {
                d[next]--;
                if(d[next] == 0) {
                    sort.add(next);
                }
            }
        }
    }
}

 

 

 1516 : 게임 개발

문제

숌 회사에서 이번에 새로운 전략 시뮬레이션 게임 세준 크래프트를 개발하기로 하였다. 핵심적인 부분은 개발이 끝난 상태고, 종족별 균형과 전체 게임 시간 등을 조절하는 부분만 남아 있었다.

게임 플레이에 들어가는 시간은 상황에 따라 다를 수 있기 때문에, 모든 건물을 짓는데 걸리는 최소의 시간을 이용하여 근사하기로 하였다. 물론, 어떤 건물을 짓기 위해서 다른 건물을 먼저 지어야 할 수도 있기 때문에 문제가 단순하지만은 않을 수도 있다. 예를 들면 스타크래프트에서 벙커를 짓기 위해서는 배럭을 먼저 지어야 하기 때문에, 배럭을 먼저 지은 뒤 벙커를 지어야 한다. 여러 개의 건물을 동시에 지을 수 있다.

편의상 자원은 무한히 많이 가지고 있고, 건물을 짓는 명령을 내리기까지는 시간이 걸리지 않는다고 가정하자.

 

입력

첫째 줄에 건물의 종류 수 N(1 ≤ N ≤ 500)이 주어진다. 다음 N개의 줄에는 각 건물을 짓는데 걸리는 시간과 그 건물을 짓기 위해 먼저 지어져야 하는 건물들의 번호가 주어진다. 건물의 번호는 1부터 N까지로 하고, 각 줄은 -1로 끝난다고 하자. 각 건물을 짓는데 걸리는 시간은 100,000보다 작거나 같은 자연수이다. 모든 건물을 짓는 것이 가능한 입력만 주어진다.

 

출력

N개의 각 건물이 완성되기까지 걸리는 최소 시간을 출력한다.

 

입력 출력
5
10 -1
10 1 -1
4 1 -1
4 3 1 -1
3 3 -1
10
20
14
18
17

 

문제 풀이

  1. 그래프와 데이터 초기화
    각 번호가 선행건물인 건물들을 리스트로 구현 예를들어 2번과 3번의 선행건물이 1번이라면 1 -> 2, 3 이렇게 연결

  2. 진입 차수 0인 건물 큐에 추가하고 초기 완료 시간 설정 result[i] = time[i]

  3. 큐에서 건물을 꺼내서 해당건물이 선행 조건인 건물들을 순회하며 진입 차수를 감소, 건설 시간 갱신(큐가 빌때까지 반복)
    result[next] = Math.max(result[next], result[cur] + time[next])
    result[next] = Math.max(현재 건물을 짓는데 걸리는 시간, 선행건물짓는데 걸리는 시간 + 현재 건물만을 짓는데 걸리는 시간)

  4. 진입 차수가 감소되어 0이 된 건물은 큐에 다시 추가

 

실행 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

public class P1516_Topology {
    static ArrayList<Integer>[] A;
    static int[] time, D, result;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        //인접리스트
        int N = Integer.parseInt(br.readLine());
        A = new ArrayList[N+1];
        for (int i = 1; i <= N; i++)
            A[i] = new ArrayList<>();

        D = new int[N+1]; //진입차수배열
        time = new int[N+1]; // 짓는데 걸리는 시간
        result = new int[N+1]; // 선행건물까지 짓는걸 모두 합한 시간

        //집입차수배열 데이터 넣기
        for(int i = 1; i <= N; i++){
            st = new StringTokenizer(br.readLine());
            time[i] = Integer.parseInt(st.nextToken()); //시간
            while(true){
                int x = Integer.parseInt(st.nextToken());
                if(x==-1) break;
                A[x].add(i);
                D[i]++;
            }
        }

        Topology();

        for(int i = 1; i <= N; i++)
            System.out.println(result[i]);
    }

    private static void Topology() {
        Queue<Integer> q = new LinkedList<>();

        // 초기 설정 : 진입 차수가 0인 노드 큐 삽입
        for(int i = 1; i < D.length; i++){
            if(D[i]==0){
                q.add(i);
                result[i] = time[i];
            }
        }

        //위상 정렬
        while(!q.isEmpty()){
            int cur = q.poll();

            for(int next : A[cur]){ // cur은 next들을 짓기위해 선행건물로 지어야한느 인덱스
                D[next]--; // 진입 차수 감소
                result[next] = Math.max(result[next], result[cur] + time[next]);
                if(D[next]==0){
                    q.add(next);
                }
            }
        }
    }
}