본문 바로가기
Algorithm/Concepts

위상 정렬(Topological Sort)

by 컴돈AI 2024. 3. 21.

 

 

목차

    위상 정렬(Topological Sort)

    • 위상 정렬(Topological Sort)은 방향 그래프의 모든 노드를 "방향성을 거스르지 않도록 순서대로 나열하는 것"을 말합니다.
    • 즉, 그래프 상에서 어떤 노드로 가기 위한 선행 노드들을 모두 거친 후에 해당 노드를 방문할 수 있는 순서로 노드들을 나열하는 과정입니다.
    • 위상 정렬은 일반적으로 사이클이 없는 방향 그래프(Directed Acyclic Graph, DAG)에서만 수행할 수 있습니다. 
    • 예를 들면, 수강신청을 할 때 선수과목이 있는 경우 선수 과목을 먼저 듣고 나서 해당 강의를 수강할 수 있습니다.
      • A->B / B->C / B->D / C->D 가 있다고 할때, A, B, C, D 순서대로 들어야지 모든 과목을 수강할 수 있습니다.
      • 만약 A B D C 라고 한다면, D의 경우 C를 듣지 않으면 수강할 수 없기 때문에 올바르지 않은 경우입니다.
      • 즉 A B C D처럼 정렬된 경우를 위상정렬이 된 상태라고 할 수 있습니다.
    • 위상 정렬이 필요한 경우는 그래프에서 반드시 자신보다 선행되어야 할 일을 다 끝내야만 작업에 들어갈 수 있는 조건이 주어질 때입니다. 
      • ex. 선수과목을 들어야지 다음 과목을 수강 가능
    • 위상정렬
      • (위상정렬은 사이클이 없을 경우에만 가능합니다. 만약 진행 과정 중에 방문했던 노드를 재방문하면 그것은 사이클이 존재하는 경우이기 때문에 위상정렬이 불가능합니다.)
        • 사이클 여부는 최종 result에 들어가 있는 노드의 길이가 n보다 짧을 경우, 사이클이 있는 것입니다.
        • 사이클이 있으면 indegree가 0인 노드가 없을 것입니다. 그렇기 때문에 중간에 사이클이 있는 부분에 들어가게 되면 q에 더 이상 노드가 들어가지 않습니다. 따라서 n개의 노드를 방문하기 전에 조기 종료할 것입니다.
      • 1. 들어오는 간선이 없는 정점(진입차수가 0)을 큐에 모두 넣기
      • 2. 해당 원소와 연결된 노드들을 진입차수에서 1 빼기
        • 2-1. 1을 뺐을때 진입차수가 0이라면 큐에 넣어주기
      • 3. 반복
    • 예시코드
      • from collections import deque
        
        # 그래프 설정. 노드와 간선의 수를 설정해주세요.
        n, m = map(int, input().split())
        graph = [[] for i in range(n + 1)]
        indegree = [0] * (n + 1)
        
        # 간선 정보 입력받기
        for _ in range(m):
            a, b = map(int, input().split())
            graph[a].append(b)
            indegree[b] += 1
        
        # 위상 정렬 함수
        def topology_sort():
            result = []
            q = deque()
        
            # 처음 시작할 때는 진입차수가 0인 노드를 큐에 삽입
            for i in range(1, n + 1):
                if indegree[i] == 0:
                    q.append(i)
        
            # 큐가 빌 때까지 반복
            while q:
                now = q.popleft()
                result.append(now)
                # 해당 원소와 연결된 노드들의 진입차수에서 1 빼기
                for i in graph[now]:
                    indegree[i] -= 1
                    # 새롭게 진입차수가 0이 되는 노드를 큐에 삽입
                    if indegree[i] == 0:
                        q.append(i)
        
            # 위상 정렬 결과 출력
            for i in result:
                print(i, end=' ')
        
        topology_sort()

    예시

    • [백준 2623] 음악프로그램
      • 보조 PD들이 만든 순서를 보고, 전체 가수의 순서를 정해야 합니다.
        • 즉, 방향그래프에서 간선들을 보고, 순서를 만족하는 전체 가수의 순서를 정하는 문제입니다.
        • 위상정렬 문제
      • 만약, 중간에 사이클이 존재한다면, 전체 가수의 순서를 결정할 수 없습니다.
      • 코드
        • import sys
          from collections import deque
          
          input = sys.stdin.readline
          n, m = map(int, input().split())
          
          graph = [[] for _ in range(n + 1)]
          indegree = [0 for _ in range(n + 1)]
          for _ in range(m):
              n_arr, *arr = list(map(int, input().split()))
              for i in range(len(arr) - 1):
                  graph[arr[i]].append(arr[i + 1])
                  indegree[arr[i + 1]] += 1
          
          q = deque()
          
          for i in range(1, n + 1):
              if indegree[i] == 0:
                  q.append(i)
          
          answer = []
          while q:
              now_node = q.popleft()
              answer.append(now_node)
              for next_node in graph[now_node]:
                  indegree[next_node] -= 1
                  if indegree[next_node] == 0:
                      q.append(next_node)
          
          if len(answer) < n:
              answer = [0]
          
          for i in range(len(answer)):
              print(answer[i])