<청춘> 격정적으로 사는 것

밤을 새고 공부한 다음 날 새벽에 느꼈던 생생한 환희와 야생적인 즐거움을 잊을 수 없다

코딩테스트

[그래프 이론] 실전 문제 <4> 커리큘럼 / 이것이 취업을 위한 코딩테스트다 with 파이썬

수학도 2021. 8. 9. 20:32

커리큘럼

난이도 ★★★  풀이 시간 50분  시간 제한 2초

 

  동빈이는 온라인으로 컴퓨터공학 강의를 듣고 있다. 이때 각 온라인 강의는 선수 강의가 있을 수 있는데, 선수 강의가 있는 강의는 선수 강의를 먼저 들어야만 해당 강의를 들을 수 있다. 예를 들어 '알고리즘'강의의 선수 강의로 '자료구조'와 '컴퓨터 기초'가 존재한다면, '자료구조'와 '컴퓨터 기초'를 모두 들은 이후에 '알고리즘'을 들을 수 있다.

  동빈이는 총 N개의 강의를 듣고자 한다. 모든 강의는 1번부터 N번까지의 번호를 가진다. 또한 동시에 여러 개의 강의를 들을 수 있다고 가정한다. 예를 들어 N = 3일 때, 3번 강의의 선수 강의로 1번과 2번 강의가 있고, 1번과 2번 강의는 선수 강의가 없다고 가정하자. 그리고 각 강의에 대하여 강의 시간이 다음과 같다고 가정하자.

  • 1번 강의 : 30시간
  • 2번 강의 : 20시간
  • 3번 강의 : 40시간

  이 경우 1번 강의를 수강하기까지의 최소 시간은 30시간, 2번 강의를 수강하기까지의 최소 시간은 20시간, 3번 강의를 수강하기까지의 최소 시간은 70시간이다.

  동빈이가 듣고자 하는 N개의 강의 정보가 주어졌을 때, N개의 강의에 대하여 수강하기까지 걸리는 최소 시간을 각각 출력하는 프로그램을 작성하시오.

 

입력 조건

  • 첫째 줄에 동빈이가 듣고자 하는 강의의 수 N(1 ≤ N ≤ 500)이 주어진다.
  • 다음 N개의 줄에는 각 강의의 강의 시간과 그 강의를 듣기 위해 먼저 들어야 하는 강의들의 번호가 자연수로 주어지며, 각 자연수는 공백으로 구분한다. 이때 강의 시간은 100,000 이하의 자연수이다.
  • 각 강의 번호는 1부터 N까지로 구성되며, 각 줄은 -1로 끝난다.

출력 조건

  • N개의 강의에 대하여 수강하기까지 걸리는 최소 시간을 한 줄에 하나씩 출력한다.

 

입력 예시

5
10 -1
10 1 -1
4 1 -1
4 3 1 -1
3 3 -1

출력 예시

10
20
14
18
17

코드

from collections import deque
import copy

# 강의 수
N = int(input())
# 진입차수 0으로 초기화
indegree = [0] * (N + 1)
# 간선 정보 리스트
graph = [[] for i in range(N + 1)]
# 각 강의 시간 0으로 초기화
time = [0] * (N + 1)

# 모든 간선 정보
for i in range(1, N + 1):
    data = list(map(int, input().split()))  # 일단 리스트에 전부 담은 후
    time[i] = data[0] # 리스트의 첫 번째 수는 시간
    for x in data[1:-1]:  # 리스트의 두 번째 수부터 뒤에서 두번째까지는 선수과목 번호
        indegree[i] += 1  # 진입차수 증가시키고
        graph[x].append(i)  # 간선 정보에 담기


# 위상 정렬 함수
def topology_sort():
    result = copy.deepcopy(time)  # 리스트의 값을 복사하는 함수
    q = deque()
    # 처음 시작할 때는 진입차수가 0인 노드를 큐에 삽입
    for i in range(1, N + 1):
        if indegree[i] == 0:
            q.append(i)
    # 큐가 빌때까지 반복
    while q:
        # 큐에서 원소 꺼내기
        now = q.popleft()
        # 해당 원소와 연결된 노드들 확인
        for i in graph[now]:
            # 인접한 노드에 대하여 현재보다 강의 시간이 더 긴 경우를 찾는다면, 더 오랜 시간이 걸리는 경우의 시간 값을 저장
            result[i] = max(result[i], result[now] + time[i])
            # 연결된 노드들의 진입차수에서 1빼기
            indegree[i] -= 1
            # 새롭게 진입차수가 0이 되는 노드를 큐에 삽입
            if indegree[i] == 0:
                q.append(i)
    #위상 정렬 수행 결과 출력
    for i in range(1, N+1):
        print(result[i])

topology_sort()

이해를 돕기 위한 print 문을 추가한 코드

from collections import deque
import copy

# 강의 수
N = int(input())
# 진입차수 0으로 초기화
indegree = [0] * (N + 1)
# 간선 정보 리스트
graph = [[] for i in range(N + 1)]
# 각 강의 시간 0으로 초기화
time = [0] * (N + 1)

# 모든 간선 정보
for i in range(1, N + 1):
    data = list(map(int, input().split()))  # 일단 리스트에 전부 담은 후
    time[i] = data[0] # 리스트의 첫 번째 수는 시간
    for x in data[1:-1]:  # 리스트의 두 번째 수부터 뒤에서 두번째까지는 선수과목 번호
        indegree[i] += 1  # 진입차수 증가시키고
        graph[x].append(i)  # 간선 정보에 담기

print('indegree', indegree)
print('graph', graph)

# 위상 정렬 함수
def topology_sort():
    result = copy.deepcopy(time)  # 리스트의 값을 복사하는 함수
    q = deque()
    print('result', result)
    # 처음 시작할 때는 진입차수가 0인 노드를 큐에 삽입
    for i in range(1, N + 1):
        if indegree[i] == 0:
            q.append(i)
    # 큐가 빌때까지 반복
    while q:
        print('\n\n')
        print('q', q)
        # 큐에서 원소 꺼내기
        now = q.popleft()
        print('큐에서 꺼낸 원소 :', now)
        # 해당 원소와 연결된 노드들 확인
        for i in graph[now]:
            print(now, '와 인접한 원소', i)
            print('result[', i, ']', result[i])
            print('result[', now, '] + time[', i, ']', result[now] + time[i])
            # 인접한 노드에 대하여 현재보다 강의 시간이 더 긴 경우를 찾는다면, 더 오랜 시간이 걸리는 경우의 시간 값을 저장
            result[i] = max(result[i], result[now] + time[i])
            print('더 오랜 시간으로 result 값 갱신 : result[', i, ']', result[i])
            # 연결된 노드들의 진입차수에서 1빼기
            indegree[i] -= 1
            print('indegree', indegree)
            # 새롭게 진입차수가 0이 되는 노드를 큐에 삽입
            if indegree[i] == 0:
                q.append(i)
    print('위상정렬 수행 결과')
    #위상 정렬 수행 결과 출력
    for i in range(1, N+1):
        print(result[i])

topology_sort()

 

해설

이 문제는 위상 정렬 알고리즘의 응용 문제이다.

각 노드(강의)에 대하여 인접한 노드를 확인할 때, 인접한 노드에 대하여 현재보다 강의 시간이 더 긴 경우를 찾는다면, 더 오랜 시간이 걸리는 경우의 시간 값을 저장하는 방식으로 결과 테이블을 갱신하여 답을 구할 수 있다.

 

deepcopy( ) 함수 : 리스트 변수의 값을 복사한다.

단순히 대입 연산을 하면 값이 변경되기 때문에 이 함수를 사용한다.


 

입력 예시로 살펴보자.

step 0

graph = [  [ ], [2, 3, 4], [ ], [4, 5] , [ ], [ ]  ]

time = [0, 10, 10, 4, 4, 3]

 

result = [0, 10, 10, 4, 4, 3]

indegree = [0, 0, 1, 1, 2, 1]

q

노드 1

 

step 1

큐에서 꺼낸 원소 : 노드 1

1과 인접한 원소 : 노드 2, 노드 3, 노드 4

 

① 노드 2

현재 노드 2에 담겨 있는 강의 시간 : result[2] = 10

노드 1을 듣고 난 후 노드 2를 듣는 강의 시간 : result[1] + time[2] = 20

둘 중 더 오랜 시간으로 result 값을 갱신 : result[2] = 20

1과 2를 연결하는 간선을 제거 : indegree[2] = 1 - 1 = 0

새롭게 진입차수가 0이 되었기 때문에 노드 2를 큐에 삽입

result = [0, 10, 20, 4, 4, 3]

indegree = [0, 0, 0, 1, 2, 1]

q

노드 2

 

② 노드 3

현재 노드 3에 담겨 있는 강의 시간 : result[3] = 4

노드 1을 듣고 난 후 노드 3을 듣는 강의 시간 : result[1] + time[3] = 14

둘 중 더 오랜 시간으로 result 값을 갱신 : result[3] = 14

1과 3을 연결하는 간선을 제거 : indegree[3] = 1 - 1 = 0

새롭게 진입차수가 0이 되었기 때문에 노드 3을 큐에 삽입

result = [0, 10, 20, 14, 4, 3]

indegree = [0, 0, 0, 0, 2, 1]

q

노드 2 노드 3

 

③ 노드 4

현재 노드 4에 담겨 있는 강의 시간 : result[4] = 4

노드 1을 듣고 난 후 노드 4를 듣는 강의 시간 : result[1] + time[4] = 14

둘 중 더 오랜 시간으로 result 값을 갱신 : result[4] = 14

1과 4를 연결하는 간선을 제거 : indegree[4] = 2 - 1 =1

result = [0, 10, 20, 14, 14, 3]

indegree = [0, 0, 0, 0, 1, 1]

q

노드 2 노드 3

 

 

step 2

큐에서 꺼낸 원소 : 노드 2

2와 인접한 원소 : 없음

q

노드 3

 

 

step 3

큐에서 꺼낸 원소 : 노드 3

3과 인접한 원소 : 노드 4, 노드 5

 

① 노드 4

현재 노드 4에 담겨 있는 강의 시간 : result[4] = 14

노드 3을 듣고 난 후 노드 4를 듣는 강의 시간 : result[3] + time[4] = 18

둘 중 더 오랜 시간으로 result 값을 갱신 : result[4] = 18

3과 4를 연결하는 간선을 제거 : indegree[4] = 1 - 1 = 0

새롭게 진입차수가 0이 되었기 때문에 노드 4를 큐에 삽입

result = [0, 10, 20, 1418, 3]

indegree = [0, 0, 0, 0, 0, 1]

q

노드 4

 

② 노드 5

현재 노드 5에 담겨 있는 강의 시간 : result[5] = 3

노드 3을 듣고 난 후 노드 5를 듣는 강의 시간 :  result[3] + time[5] = 14 + 3 = 17

둘 중 더 오랜 시간으로 result 값을 갱신 : result[5] = 17

3과 5를 연결하는 간선을 제거 : indegree[5] = 1 -1 = 0

새롭게 진입차수가 0이 되었기 때문에 노드 5를 큐에 삽입

result = [0, 10, 20, 14, 18, 17]

indegree = [0, 0, 0, 0, 0, 0]

q

노드 4 노드 5

 

step 4

큐에서 꺼낸 원소 : 노드 4

4와 인접한 원소 : 없음

q

노드 5

 

 

step 5

큐에서 꺼낸 원소 : 노드 5

5와 인접한 원소 :

q

 

 

큐가 비었으므로 위상 정렬 종료

 

 

위상 정렬 결과

result = [0, 10, 20, 14, 18, 17]

 


참고

2021.08.04 - [자료구조 알고리즘] - [알고리즘] 위상 정렬 (Topology Sort) : 순서가 정해져 있는 정렬 알고리즘

 

[알고리즘] 위상 정렬 (Topology Sort) : 순서가 정해져 있는 정렬 알고리즘

위상 정렬 (Topology Sort) 순서가 정해져 있는 일련의 작업을 차례대로 수행해야 할 때 사용할 수 있는 알고리즘이다. 혹은 방향 그래프의 모든 노드를 '방향성에 거스르지 않도록 순서대로 나열하

devmath.tistory.com

 

출처

나동빈, 『이것이 취업을 위한 코딩테스트다 with 파이썬』, 한빛미디어(주), 2020년