Iriton's log

[Algorithm/Java] 위상정렬(Topological sorting) - 2252번: 줄 세우기 본문

Algorithm

[Algorithm/Java] 위상정렬(Topological sorting) - 2252번: 줄 세우기

Iriton 2023. 5. 23. 23:02

참고 자료

 

위상정렬 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 위상 정렬(topological sorting)은 유향 그래프의 꼭짓점들(vertex)을 변의 방향을 거스르지 않도록 나열하는 것을 의미한다. 위상정렬을 가장 잘 설명해 줄 수 있는 예

ko.wikipedia.org

 

위상정렬(Topological sorting)


유향 그래프의 꼭짓점(vertex)들이 변(side)의 방향을 거스르지 않도록 나열하는 것이다.

 

알고리즘

1. 자기 자신을 가리키는 변이 없는 꼭짓점을 찾는다.

2. 찾은 꼭짓점을 출력하고 출력한 꼭짓점과 그 꼭짓점에서 출발하는 변을 삭제한다.

3. 아직 그래프에 꼭짓점이 남아 있으면 단계 1로 돌아가고, 아니면 알고리즘을 종료한다.

 

이해를 위해 사진과 함께 예를 들어 보자.

* 이 예시에서는 꼭짓점에 배정된 숫자가 작은 순서대로 진행한다.

 

1. 자기 자신을 가리키는 변이 없는 꼭짓점은 3, 5, 7 이다. 이 예시에서는 숫자가 작은 순서대로 진행한다.

2. 3을 출력하고 3이 가리키는 변을 모두 삭제한다. -> 8과 10을 가리키는 변이 삭제된다.

3. 이후로 5, 7, 8, 11, 2, 9, 10 순서대로 반복하다 알고리즘은 종료된다.

 

 

 

예시를 보면 위상정렬은 일부 노드에 대해서 작업 순서를 정할 수 있다는 걸 확인할 수 있다.

따라서, 위상정렬은 선택에 의해 결과값이 다양해지는 알고리즘이다.

 

또한, 위상정렬은 방향 사이클이 존재하지 않는 그래프인 DAG(Directed Acyclic Graph)에서만 적용이 가능하다.

방향 사이클이 존재한다면 시작 지점을 정할 수 없기 때문이다.

 

 

 

이 위상정렬은 큐(Queue)스택(Stack)을 이용하여 구현할 수 있다.

앞서 말했듯이 필요에 따라 선택하기 나름이다.

 

 

이제부터는 꼭짓점을 노드(Node)로, 변을 간선으로 표현할 것이다.

덧붙여, 진입차수(in-Degree)는 노드를 가리키는 간선의 수이다.

지정된 정점이 수행되기 전에 먼저 수행되어야 할 정점의 개수를 의미한다.

이를 이용하는 것은 너비 우선 탐색, BFS이다.

 

진출차수(out-Degree)는 노드가 가리키는 간선의 수이다.

지정된 정점이 먼저 수행되어야 수행할 수 있는 정점의 개수를 의미한다.

이를 이용하는 것은 깊이 우선 탐색, DFS이다.

 

BFS by Queue

1. 진입차수가 0인 정점을 큐에 저장한다.

2. 큐에서 원소를 pop 하여 출력한다.

3. 해당 정점에 연결된 모든 간선을 제거한다. 그 후에 진입차수가 0이 된 정점을 큐에 삽입한다.(단계 1로 회귀)

4. 큐가 빌 때까지 반복한다.

-> 모든 원소를 방문하기 전에 큐가 비는 것은 사이클이 존재한다는 뜻이다.

 

 

 

DFS by Stack

1. 진입차수가 0인 정점부터 탐색을 시작한다.

2. 진출차수가 0인 정점까지 탐색을 한 후, 해당 정점을 스택에 저장한다. 이때 해당 정점은 탐색이 종료된다.

3. 해당 정점에 연결된 모든 간선을 제거한다. 그 후에 진출차수가 0이 된 정점을 스택에 삽입한다.

4. 탐색 종료된 정점 순서의 역순으로 반환된다.

-> 종료 순서:  D - C - B - A // 반환 순서: A - B - C - D

 

 

참고 자료

 

[Algorithm] 깊이우선탐색(DFS)과 너비우선탐색(BFS)

깊이우선탐색(DFS)과 너비우선탐색(BFS)에 대한 개념을 공부하고, 구현을 정리한 내용입니다.

velog.io


2252번: 줄 세우기

문제


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

 

입력


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

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

 

출력


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

 

풀이


요약 및 분석


N 명의 학생들을 M 번에 걸쳐서 비교한다. 이때, 먼저 비교된 수들이 앞 쪽(큰 쪽)으로 저장된다.   
예를 들어, 첫 번째로 1, 3을 비교하고 두 번째에 2, 4를 비교하는 식으로 4(N)명을 2(M)번에 걸쳐 비교한다면,    
첫 번째 비교한 결과 3, 1 과 두 번째 비교한 결과 2, 4는 서로 비교하지 않았기 때문에 독립적이다.    
따라서, 오직 비교 순서에 맞추어 배열된 최종 출력값은 3, 1, 2, 4가 된다.    
이는 위상정렬에 해당한다.  

 

위상정렬 참고 자료:

 

25. 위상 정렬(Topology Sort)

  위상 정렬(Topology Sort)은 '순서가 정해져있는 작업'을 차례로 수행해야 할 때 그 순서를 ...

blog.naver.com

 

코드

import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

// 일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램
public class Main {
    static int N; // 그래프 정점의 수
    static int M; // 간선의 수

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        N = scanner.nextInt();
        M = scanner.nextInt();

        int[] cntLink = new int[N + 1]; // 간선의 수에 대한 배열

        // 가중치가 없는 그래프(인접 리스트 이용)
        ArrayList<ArrayList<Integer>> graph = new ArrayList<>();
        for (int i = 0; i < N + 1; i++) {
            graph.add(new ArrayList<Integer>());
        }
        // 단방향 연결 설정
        for (int i = 0; i < M; i++) {
            int v1 = scanner.nextInt();
            int v2 = scanner.nextInt();
            graph.get(v1).add(v2);
            cntLink[v2]++; // 후행 정점에 대한 간선의 수 증가
        }

        // 위상 정렬 (A B: A가 B앞에 선다. A가 선행)
        topologicalSort(graph, cntLink);
    }


    // 위상 정렬
    static void topologicalSort(ArrayList<ArrayList<Integer>> graph, int[] cntLink) {
        Queue<Integer> queue = new LinkedList();

        // 초기: 선행 정점을 가지지 않는 정점을 큐에 삽입
        for (int i = 1; i < N + 1; i++) {
            if (cntLink[i] == 0) { // 해당 정점의 간선의 수가 0이면
                queue.add(i);
            }
        }

        // 정점의 수 만큼 반복
        for (int i = 0; i < N; i++) {
            int v = queue.remove(); // 1. 큐에서 정점 추출
            System.out.print(v + " "); // 정점 출력

            // 2. 해당 정점과 연결된 모든 정점에 대해
            for (int nextV : graph.get(v)) {
                cntLink[nextV]--; // 2-1. 간선의 수 감소

                // 2-2. 선행 정점을 가지지 않는 정점을 큐에 삽입
                if (cntLink[nextV] == 0) { // 해당 정점의 간선의 수가 0이면
                    queue.add(nextV);
                }
            }
        }
    }
}
Comments