[CS기초] 위상 정렬(Topological Sort)

2025. 3. 30. 21:49·크래프톤 정글/CS기초(키워드, 개념정리)

위상 정렬 개요

위상 정렬(Topological Sort)은 방향 그래프에서 모든 노드를 방향성에 거스르지 않도록 순서대로 나열하는 알고리즘입니다. 다만 모든 방향 그래프에서 다 사용할 수는 없는데요. DAG(Directed Acyclic Graph), 즉 사이클이 존재하지 않는 방향 그래프일 때만 위상 정렬이 가능해집니다.

 

그리고 위상 정렬 동작 과정을 설명하기에 앞서서 알아야 할 개념은 진입 차수(Indegree)라는 개념인데요. 특정한 노드로 들어오는 간선의 개수를 뜻합니다. 위상 정렬에는 각 노드의 차수에 따라 나열할 순서를 결정하게 되는데 이 때 사용하는 차수가 바로 진입 차수입니다. 이때 진입 차수가 높은 노드라면 더 나중에 나열되는 것이죠.

 

 

위상 정렬의 동작 과정

먼저, 특정 노드에 들어오는 간선이 없을 때, 즉 차수가 0일 때 이 노드는 가장 먼저 나열됩니다. 따라서 처음에 모든 노드 중 차수가 0인 노드를 찾아 큐(Queue)에 넣습니다.

 

이후 다음의 과정을 큐가 빌 때까지 반복합니다.

 

  1. 큐에서 노드를 하나씩 꺼내 나열합니다.
  2. 꺼낸 노드와 연결된 노드들의 차수를 감소시킵니다.
  3. 차수가 감소된 노드 중에서 차수가 0이 된 노드를 다시 큐에 넣습니다.

 

이렇게 반복하면, 큐에서 꺼낸 순서대로 나열했을 때 자연스럽게 그래프의 방향성을 위배하지 않으면서 모든 노드가 정렬됩니다.

 

이 과정을 간단히 정리하면 다음과 같습니다.

  1. 주어진 간선 정보를 바탕으로 각 노드의 차수(indegree)를 구하고 인접 리스트(graph)를 만듭니다.
    • 우선순위가 높은 노드(먼저 해야 하는 작업)의 인접 리스트에, 우선순위가 낮은 노드를 추가합니다.
    • 우선순위가 낮은 노드(나중에 할 작업)의 차수를 증가시킵니다.
  2. 차수가 0인 모든 노드를 큐에 넣습니다.
  3. 큐가 빌 때까지 다음을 반복합니다.
    • 큐에서 노드를 꺼내 결과 리스트에 나열합니다.
    • 해당 노드에서 이동 가능한 모든 노드의 차수를 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
'크래프톤 정글/CS기초(키워드, 개념정리)' 카테고리의 다른 글
  • [CS기초] 플로이드-워셜 알고리즘(Floyd-Warshall Algorithm)
  • [CS기초] 다익스트라 알고리즘(Dijkstra's Algorithm)
  • [CS기초] DFS(Depth-First Search), BFS(Breadth-First Search)
  • [CS기초] 그래프(Graph), 그래프 종류와 표현방식
그냥사람_
그냥사람_
IT 관련 포스팅을 합니다. 크래프톤 정글 8기 정경호
  • 그냥사람_
    그냥코딩
    그냥사람_
  • 전체
    오늘
    어제
    • 글 전체보기 N
      • 크래프톤 정글 N
        • 로드 투 정글(입학시험)
        • CS기초(키워드, 개념정리) N
        • 컴퓨터구조(CSAPP)
        • Code 정글(C언어) N
        • 마이 정글(WIL, 에세이) N
      • 자료구조&알고리즘
        • 자료구조
        • 알고리즘
      • 일상
  • 블로그 메뉴

    • 홈
  • 링크

    • Github
  • hELLO· Designed By정상우.v4.10.3
그냥사람_
[CS기초] 위상 정렬(Topological Sort)
상단으로

티스토리툴바