줄 세우기

백준 2252

GOLD III


  1. 문제 내용
  2. 해결 방안
  3. 풀이 코드

문제 내용

problem.png

문제 링크

해결 방안

문제에 따르면, N명의 학생들 중 일부 학생들에 대하여 학생 두 명씩 줄을 서는 순서만이 주어진 상태이다. 간단히 말하자면, 첫째 줄을 제외한 임의의 줄에서 A B라는 정보가 주어지면, 줄을 설 때 A가 B의 앞에만 있으면 된다. 결과적으로 임의의 줄에서 나타난 순서에 위배만 되지 않는다면 순서를 어떻게 세우든 상관없다. 각각 두 학생들 사이에 방향성이 있고 줄에 순환이 없으므로, 이 문제는 위상 정렬로 풀 수 있다.

먼저, 위상 정렬을 위해 각 학생들의 앞에 나와야 하는 학생들의 수를 확인한다. 그리고, 앞에 나오는 다른 학생들이 없는 학생들을 먼저 앞에 세운다. 각 학생들이 앞에 나오면, 해당 학생들 뒤에 나와야 하는 학생들을 이어서 세운다. 이 과정을 반복하면, 주어진 순서 정보를 따르는 방향으로 학생들이 정렬된다.

위상 정렬(topological sort)이란?

유향 비순환 그래프(Directed Acyclic Graph; DAG)의 방향성을 따라 순서대로 배열하는 정렬 방식이다. 다양한 정렬 알고리즘이 존재하지만, 대표적으로 Kahn’s algorithm(for topological sort)이 있다.

Kahn’s algorithm의 순서는 다음과 같다.

  1. 각 노드로 향하는 간선의 개수를 조사하여, 노드로 향하는 간선의 개수가 0인 노드들을 찾는다.
  2. 찾은 노드들을 하나씩 꺼내, 해당 노드의 간선들이 가리키는 노드의 간선의 개수를 하나씩 줄인다.
  3. 만약 노드로 향하는 간선의 개수가 0인 노드가 발생하면, 해당 노드도 찾은 노드 목록에 추가한다.
  4. 모든 노드들을 찾아 간선들을 확인할 때까지 이 과정을 반복한다.

유향 비순환 그래프(Directed Acyclic Graph)이란?

유향 비순환 그래프(Directed Acyclic Graph; DAG)는 풀어서 설명하자면, 방향이 있고 순환하지 않는 그래프이다. 노드와 노드 사이의 간선에 방향성이 있어, 해당 방향성을 위배하지 않는 선에서 두 노드가 이어져있다. 그리고 비순환이라는 말에서 알 수 있듯이, 이러한 방향을 따라갔을 때 어느 출발 지점으로 돌아오는 간선이 존재해서는 안 된다. 예로 들어 A에서 B를 향하는 간선(A→B)가 있다고 한다면, B에서 A로 이동하는 것은 방향성에 위배되기 때문에 불가능하다. 또한, (A→B)와 (B→C)라는 간선이 있을 때, (C→A)라는 간선은 순환(cycle)이 만들어지기 때문에 존재할 수 없다.

풀이 코드

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

int main() {
    int n, m;  // 학생들의 수와 순서쌍의 수
    int a, b;  // 학생들의 순서쌍
    vector<vector<int>> table(n + 1);  // 인덱스별 자신의 뒤에 나오는 학생 리스트
    vector<int> priority(n + 1);  // 인덱스별 자신의 앞에 나오는 학생들의 수
    queue<int> q;  // 앞에 서는 순서
    int top;  // 현재 줄을 서는 학생

    cin >> n >> m;
    for(int i = 0; i < m; i++) {
        cin >> a >> b;
        // 앞에 나오는 학생들이 뒤에 나오는 학생들의 명단을 가진다.
        table[a].push_back(b);
        // 뒤에 나오는 학생들은 자신의 앞에 나오는 학생들의 수를 기록한다.
        priority[b]++;
    }

    // 자신의 앞에 서는 학생이 없는 학생들을 먼저 큐(queue)에 넣는다.
    for(int i = 1; i <= n; i++) {
        if(priority[i] == 0)
            q.push(i);
    }

    while(!q.empty()) {
        // 큐에서 나오는 순서대로 학생들을 출력한다.
        top = q.front();
        q.pop();
        cout << top << ' ';

        // 먼저 나온 학생들은 뒤에 나오는 학생들의 '앞에 나오는 학생 수'를 감소시킨다.
        // '앞에 나오는 학생 수'가 0이 되면, 그 학생도 이어서 줄을 선다.
        for(auto node: table[top]) {
            priority[node]--;
            if(priority[node] == 0)
                q.push(node);
        }
    }
    cout << endl;
    return 0;
}