본문으로 건너뛰기
김신건의 로그

위상 정렬 (Topological Sorting)

· 수정 · 📖 약 3분 · 1,080자/단어 #algorithm #graph #dag #topological-sorting
Topological Sorting, topological sort, 위상 정렬, topological-sorting

정의

위상 정렬 (Topological Sorting) 은 방향 비순환 그래프 (DAG) 의 모든 정점을 간선 방향을 어기지 않도록 일렬로 나열하는 것. 간선 u → v 가 있으면 정렬 결과에서 uv 보다 앞에 온다.

DAG 에만 존재하며, 사이클이 있으면 위상 정렬이 불가능. 여러 해가 있을 수 있다.

문제 상황과 동기

선수 과목 그래프, 작업 의존성 (Makefile), 빌드 순서, 상속 / import 위계 등 의존 관계가 순환하지 않는 상황에서 실행 가능한 순서 를 찾는다.

  • naive: DFS 로 탐색 후 임의 정렬. 간선 방향 보장 없음.
  • topological sort: O(V + E) 에 의존성이 만족되는 순서를 출력.

핵심 통찰: DFS 의 post-order 역순 또는 in-degree 가 0 인 노드부터 BFS 하면 자연스럽게 위상 순서.

시각화

핵심 아이디어

방법 1: Kahn’s Algorithm (BFS, in-degree)

  1. 모든 정점의 진입 차수 (in-degree) 를 센다.
  2. in-degree 가 0 인 정점을 큐에 넣는다.
  3. 큐에서 빼며 정렬 결과에 추가, 그 정점에서 나가는 간선의 도착 정점 in-degree 를 1 감소.
  4. 새로 in-degree 가 0 이 된 정점을 큐에 넣는다.
  5. 큐가 빌 때까지 반복.

결과 개수가 V 미만이면 사이클 존재.

방법 2: DFS post-order 역순

  1. DFS 를 돌며 탐색이 끝난 (post-order) 순서를 스택에 쌓는다.
  2. 스택을 역순으로 출력하면 위상 정렬.

사이클 감지는 DFS 중 back edge 가 있는지 체크.

두 방법 모두 O(V + E) 시간, 공간 O(V).

알고리즘

Kahn’s (BFS)

topological_sort_kahn(G):
    in_degree[v] = 0 for all v
    for each edge u → v:
        in_degree[v] += 1
    Q = queue of all v with in_degree[v] = 0
    result = []
    while Q not empty:
        u = Q.dequeue()
        result.append(u)
        for each edge u → v:
            in_degree[v] -= 1
            if in_degree[v] = 0:
                Q.enqueue(v)
    if len(result) < V:
        return "CYCLE"
    return result

DFS post-order 역순

topological_sort_dfs(G):
    visited = [false] × V
    stack = []
    for v in V:
        if not visited[v]:
            dfs(v)
    return reverse(stack)

dfs(v):
    visited[v] = true
    for u in neighbors[v]:
        if not visited[u]:
            dfs(u)
        # back edge check: if u is in recursion stack → cycle
    stack.push(v)

구현

// Kahn's algorithm, in-degree BFS
#include <bits/stdc++.h>
using namespace std;
int main() {
  int v, e; cin >> v >> e;
  vector<vector<int>> adj(v + 1);
  vector<int> indeg(v + 1, 0);
  for (int i = 0; i < e; i++) {
      int a, b; cin >> a >> b;     // a → b
      adj[a].push_back(b);
      indeg[b]++;
  }
  queue<int> Q;
  for (int i = 1; i <= v; i++)
      if (!indeg[i]) Q.push(i);
  vector<int> res;
  while (!Q.empty()) {
      int u = Q.front(); Q.pop();
      res.push_back(u);
      for (int w : adj[u]) {
          if (--indeg[w] == 0) Q.push(w);
      }
  }
  if ((int)res.size() < v) {
      cout << "CYCLE\n";
  } else {
      for (int x : res) cout << x << " ";
      cout << "\n";
  }
}
stdin
6 7
1 2
1 3
2 4
3 4
4 5
3 5
5 6
결과
1 2 3 4 5 6

복잡도

항목
시간 (Kahn)O(V + E)
시간 (DFS)O(V + E)
공간O(V + E) (인접 리스트)
유일성✗ (해가 여러 개 가능)

변형 / 활용

1. Lexicographically smallest order

Kahn 에서 큐 대신 priority queue 사용. 매번 가능한 정점 중 가장 작은 번호를 뽑음. 시간 O((V + E) log V).

2. 모든 위상 정렬 열거

백트래킹. in-degree 0 인 정점을 선택, 간선 제거, 재귀. 최악 O(V!).

3. Dynamic programming on DAG

위상 순서대로 DP 를 채우면 의존성 보장. 최장 경로, 경로 개수 등.

4. Critical Path Method (CPM)

작업 일정 관리. 최장 경로가 프로젝트 최소 완료 시간.

함정

1. 사이클 감지 누락

Kahn 에서 res.size() < V 체크, DFS 에서 back edge 체크. 둘 다 안 하면 잘못된 결과.

2. 1-indexed / 0-indexed 혼동

정점 번호 체계를 일관되게. in-degree 배열 크기 주의.

3. 다중 간선 / 자기 루프

자기 루프는 사이클. 다중 간선은 in-degree 중복 증가 주의.

4. disconnected graph

여러 컴포넌트가 있으면 모든 컴포넌트를 순회. DFS 에서 모든 정점 시작 체크.

BOJ 연습 문제

번호제목정답률링크
BOJ 2252줄 세우기49.8%kokoa-lab
BOJ 1766문제집38.6%kokoa-lab
BOJ 1005ACM Craft33.2%kokoa-lab
BOJ 2623음악프로그램52.3%kokoa-lab

참고

이 글의 용어 (4개)
강한 연결 요소 (SCC)algorithm
정의 강한 연결 요소 (Strongly Connected Component, SCC) 는 방향 그래프에서 서로 도달 가능한 정점들의 최대 부분 집합. SCC 내 임의 두 정점 u…
깊이 우선 탐색 (DFS)algorithm
정의 깊이 우선 탐색 (Depth-First Search, DFS) 는 그래프 G=(V, E) 에서 갈 수 있는 만큼 깊이 들어가다가 막히면 백트래킹하는 알고리즘. 스택 (LIF…
너비 우선 탐색 (BFS)algorithm
정의 너비 우선 탐색 (Breadth-First Search, BFS) 는 그래프 G=(V, E) 에서 시작 정점 s 로부터 가까운 정점부터 순서대로 방문하는 알고리즘. 큐 (F…
방향 비순환 그래프 (DAG)algorithm
정의 방향 비순환 그래프 (Directed Acyclic Graph, DAG) 는 사이클이 없는 방향 그래프. 어떤 정점에서 출발해도 자기 자신으로 돌아오는 경로가 없다. DAG…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기