위상 정렬 개요
위상 정렬(Topological Sort)은 방향 그래프에서 모든 노드를 방향성에 거스르지 않도록 순서대로 나열하는 알고리즘입니다. 다만 모든 방향 그래프에서 다 사용할 수는 없는데요. DAG(Directed Acyclic Graph), 즉 사이클이 존재하지 않는 방향 그래프일 때만 위상 정렬이 가능해집니다.
그리고 위상 정렬 동작 과정을 설명하기에 앞서서 알아야 할 개념은 진입 차수(Indegree)라는 개념인데요. 특정한 노드로 들어오는 간선의 개수를 뜻합니다. 위상 정렬에는 각 노드의 차수에 따라 나열할 순서를 결정하게 되는데 이 때 사용하는 차수가 바로 진입 차수입니다. 이때 진입 차수가 높은 노드라면 더 나중에 나열되는 것이죠.
위상 정렬의 동작 과정
먼저, 특정 노드에 들어오는 간선이 없을 때, 즉 차수가 0일 때 이 노드는 가장 먼저 나열됩니다. 따라서 처음에 모든 노드 중 차수가 0인 노드를 찾아 큐(Queue)에 넣습니다.
이후 다음의 과정을 큐가 빌 때까지 반복합니다.
- 큐에서 노드를 하나씩 꺼내 나열합니다.
- 꺼낸 노드와 연결된 노드들의 차수를 감소시킵니다.
- 차수가 감소된 노드 중에서 차수가 0이 된 노드를 다시 큐에 넣습니다.
이렇게 반복하면, 큐에서 꺼낸 순서대로 나열했을 때 자연스럽게 그래프의 방향성을 위배하지 않으면서 모든 노드가 정렬됩니다.
이 과정을 간단히 정리하면 다음과 같습니다.
- 주어진 간선 정보를 바탕으로 각 노드의 차수(indegree)를 구하고 인접 리스트(graph)를 만듭니다.
- 우선순위가 높은 노드(먼저 해야 하는 작업)의 인접 리스트에, 우선순위가 낮은 노드를 추가합니다.
- 우선순위가 낮은 노드(나중에 할 작업)의 차수를 증가시킵니다.
- 차수가 0인 모든 노드를 큐에 넣습니다.
- 큐가 빌 때까지 다음을 반복합니다.
- 큐에서 노드를 꺼내 결과 리스트에 나열합니다.
- 해당 노드에서 이동 가능한 모든 노드의 차수를 1씩 감소시킵니다.
- 차수가 0이 된 노드는 큐에 추가합니다.
위상 정렬이 헷갈릴 때: 고순위 vs 저순위로 생각해보기
저 같은 경우 위상 정렬을 익히면서 어떤 노드의 차수를 높여야 하고, 간선 방향을 어떻게 설정해야 할지 헷갈릴 때가 많았는데요. 그러다 찾은 가장 쉬운 방법은, 모든 노드를 '고순위' 와 '저순위' 로 구분해 생각하는 것이었습니다.
- 고순위 노드: 반드시 먼저 처리해야 하는 노드 (예: 선수과목)
- 저순위 노드: 나중에 처리해도 되는 노드 (예: 선수과목을 수강한 후 수강하는 과목)
이렇게 구분하고 나면, 간선을 추가할 때 훨씬 명확해집니다.
- 그래프의 방향은 항상 고순위 -> 저순위 방향으로 추가한다.
- 자연스럽게 저순위 노드의 차수(indegree)를 증가시킨다.
예시로 쉽게 이해하기
예를 들어, 'a라는 과목을 들어야만 b라는 과목을 수강할 수 있다'고 가정하겠습니다. 여기서 a는 고순위, b는 저순위 노드입니다.
a b # 고순위(a) → 저순위(b)
이를 토대로 먼저 그래프의 방향을 고순위 -> 저순위로 추가해줍니다.
# 1. graph가 [[] for _ in range(v+1)] (인접 리스트)로 초기화된 경우
graph[a].append(b)
# 2. graph가 [[False]*(v+1) for _ in range(v+1)] (인접 행렬)로 초기화된 경우
graph[a][b] = True
그리고 자연스럽게 저순위가 나중에 나와야 하니까 저순위의 차수를 증가시켜 줍니다.
indegree[b] += 1
이처럼 간선에 대해 노드 간의 관계를 구분하면 보다 쉽게 위상 정렬을 수행할 수 있습니다.
파이썬에서 위상 정렬의 구현
from collections import deque
# 노드, 간선 개수 입력받기
v, e = map(int, input().split())
# 모든 노드에 대한 진입차수를 0으로 초기화
indegree = [0] * (v+1)
# 인접 리스트 초기화
graph = [[] for _ in range(v+1)]
# 방향 그래프의 모든 간선 정보 입력받기
for _ in range(e):
a, b = map(int, input().split())
# a→b 관계에서 진입차수, 노드 간 관계 처리
graph[a].append(b)
indegree[b] += 1
# 큐(deque) 선언 및 진입차수가 0인 노드를 큐에 삽입
q = deque()
for i in range(1, v+1):
if indegree[i] == 0:
q.append(i)
# 알고리즘 수행 결과를 담은 리스트 선언
result = []
# 큐가 빌 때까지 반복
while q:
# 큐에서 원소 꺼내기 및 결과 리스트에 추가
now = q.popleft()
result.append(now)
# 해당 원소와 연결된 노드들의 진입차수를 1씩 빼고, 뺐을 때 0이 되었다면 큐에 추가
for i in graph[now]:
indegree[i] -= 1
if indegree[i] == 0:
q.append(i)
# 위상 정렬을 수행한 결과 출력
for i in result:
print(i, end=' ')
'크래프톤 정글 > CS기초(키워드, 개념정리)' 카테고리의 다른 글
[CS기초] 플로이드-워셜 알고리즘(Floyd-Warshall Algorithm) (0) | 2025.03.31 |
---|---|
[CS기초] 다익스트라 알고리즘(Dijkstra's Algorithm) (0) | 2025.03.31 |
[CS기초] DFS(Depth-First Search), BFS(Breadth-First Search) (0) | 2025.03.30 |
[CS기초] 그래프(Graph), 그래프 종류와 표현방식 (0) | 2025.03.30 |
[CS기초] Set은 왜 이진 탐색보다 빠르게 데이터 유무를 알 수 있을까? (0) | 2025.03.27 |