위상 정렬은 선행 관계가 정해진 작업들 사이에서, 모든 제약을 만족하는 실행 순서를 찾는 알고리즘이다.
예를 들어,
배럭 -> 팩토리 -> 스타포트 -> 사이언스 퍼실러티팩토리 -> 머신샵 -> 탱크스타포트 -> 레이스가 주어질때, 모든 연구를 다 할수 있는 순서는
배럭 -> 팩토리 -> 머신샵 -> 탱크 -> 스타포트 -> 레이스 -> 사이언스 퍼실러티이렇게 정렬할 수 있다. (물론 다른 경우의 수도 가능하다)
보다시피 사이클이 발생하면 안된다. 위상 정렬 알고리즘의 조건은, 방향 비순환 그래프(directed acyclic graph, DAG)이다.
위상 정렬의 핵심 아이디어는 진입 차수 이다.
진입 차수란, 특정 작업을 시작하기 전에 선행되어야 할 작업의 개수를 의미한다.
위상 정렬 알고리즘은 다음과 같은 흐름으로 동작한다.
이번엔 알고리즘 관점에서 흐름을 그려보자.
우선 모든 정점을 순회하고, 모든 간선의 개수만큼 순회하므로 이다.
2623번의 코드이다.
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int M = sc.nextInt();
List<List<Integer>> graph = new ArrayList<>();
int[] indegree = new int[N + 1]; // 진입 차수 배열
for (int i = 0; i <= N; i++) {
graph.add(new ArrayList<>());
}
for (int i = 0; i < M; i++) {
int count = sc.nextInt();
int[] sequence = new int[count];
for (int j = 0; j < count; j++) {
sequence[j] = sc.nextInt();
}
// 그래프 및 진입 차수 초기화
for (int j = 0; j < count - 1; j++) {
int from = sequence[j];
int to = sequence[j + 1];
graph.get(from).add(to);
indegree[to]++;
}
}
// 위상 정렬 시작 (Kahn's algorithm)
Queue<Integer> queue = new LinkedList<>();
List<Integer> result = new ArrayList<>();
// 진입 차수가 0인 노드 찾기
for (int i = 1; i <= N; i++) {
if (indegree[i] == 0) {
queue.offer(i);
}
}
while (!queue.isEmpty()) {
int curr = queue.poll();
result.add(curr);
for (int next : graph.get(curr)) {
// 꺼낸 노드에서 연결된 노드들의 진입 차수를 1씩 감소
indegree[next]--;
// 진입 차수가 0이 된 노드를 큐에 삽입한다.
if (indegree[next] == 0) {
queue.offer(next);
}
// 0이 되는 순간에만 진입 차수가 0이 되는걸 체크하므로, 간선을 제거하지 않아도 된다.
}
}
if (result.size() == N) {
for (int num : result) {
System.out.println(num);
}
} else { // 결과 리스트의 크기가 노드 수와 다르면 위상 정렬이 불가능한 것이다.
System.out.println(0);
}
}
}
참고: Kahn's Algorithm 말고도 DFS 기반 방식도 있다.