"양말보다 신발을 먼저 신을 수는 없잖아. 위상 정렬은 'X가 Y보다 먼저'라는 규칙 뭉치를 주면 전부를 해낼 유효한 순서를 돌려주는 알고리즘이야. 아니면 그 규칙들이 서로 모순이라고 알려주거나."
문제
간선 X→Y가 "X가 Y보다 먼저"를 뜻하는 방향 비순환 그래프(DAG)가 주어지면, 위상 정렬은 모든 선행 조건을 만족하는 하나의 선형 순서를 만들어 줘. 빌드 의존성, 강의 선수과목, 작업 스케줄링, 스프레드시트 재계산에 바로 쓰이지. 참고로 Python의 실제 import 실행은 캐시와 런타임 코드, 순환 import가 얽혀 있어서 단순한 전체 위상 정렬이라고 보면 안 돼.
Kahn 알고리즘: 준비된 것부터 벗겨내기
가장 직관적인 방법은 노드마다 아직 남은 선행 조건 수, 그러니까 진입 차수(in-degree)를 놓고 도는 거야. 남은 선행 조건이 0인 노드, 그러니까 지금 바로 할 수 있는 노드를 아무거나 하나 꺼내서 출력하고, 거기서 나가는 간선을 제거해. 그러면 이웃들의 진입 차수가 줄고 그중 일부도 준비 상태가 되지. 준비된 노드는 큐에 담아 두고 전부 출력될 때까지 반복하면 돼. BFS 냄새가 나지. 지금 할 수 있는 작업의 경계를 처리하면 다음 경계가 열리는 구조니까. 전체가 O(V + E)야.
다른 방법은 DFS 후위 순회야. 노드가 끝날 때마다 답에 넣고 마지막에 통째로 뒤집는 거지. 단, 방문 전과 현재 경로 위와 완료라는 세 가지 상태를 구분해야 해. 그래야 현재 경로에 있는 노드를 다시 만나는 걸로 사이클을 잡아낼 수 있거든. 사이클 검출 없이 후위 순회만 뒤집으면 DAG가 아닌 입력에도 그럴듯하지만 틀린 순서를 내놓을 수 있어.
보너스: 순환 검출
위상 정렬은 DAG, 그러니까 순환이 없는 그래프에서만 돌아가. 그리고 그 조건이 깨지면 알려줘. Kahn 알고리즘에서 전부 출력하기도 전에 진입 차수 0인 노드가 동나면, 남은 노드들이 순환을 이루고 있다는 뜻이야. A가 B를 필요로 하고 B가 A를 필요로 하니 어느 쪽도 영영 '준비'되지 않는 상호 의존이지. 이건 정말 쓸모 있는 진단이야. "순환 의존성 감지됨"이라고 알려주는 빌드 시스템이 바로 실패한 위상 정렬이거든. 알고리즘이 할 수 있는 것들의 순서를 매기는 데 그치지 않고, 유효한 순서가 애초에 존재하는지까지 증명해 주는 거야.