4. 위상 정렬(Topological Sorting)
2021 AL林 정기 스터디
의존성이 있는 작업들이 주어질 때, 수행해야 하는 순서
유향 그래프의 정점을 방향을 거스르지 않도록 나열하는 것
(유향 그래프의 정점을 정렬하는 것)
“간선 (i, j)가 존재하면 정렬 결과에서 정점 i는 반드시 정점 j보다 앞에 위치해야 한다.”
5. 위상 정렬(Topological Sorting)
2021 AL林 정기 스터디
“간선 (i, j)가 존재하면 정렬 결과에서 정점 i는 반드시 정점 j보다 앞에 위치해야 한다.”
정렬할 그래프는
1. 유향 그래프이며
2. 사이클이 없어야 합니다. (위 성질을 만족할 수 없죠)
6. ※ 참고
2021 AL林 정기 스터디
이러한 그래프를
DAG(Directed Acyclic Graph)라고 합니다.
※ 의존성 그래프(dependency graph)라고 하기도 합니다
정렬할 그래프는
1. 유향 그래프이며
2. 사이클이 없어야 합니다.
7. 위상 정렬의 예
2021 AL林 정기 스터디
1. 대학의 선수과목
2. 요리
3. 스타크래프트
등등...
11. 풀어볼 문제 줄 세우기(BOJ 2252번)
2021 AL林 정기 스터디
https://www.acmicpc.net/problem/2252
N명의 학생들을 키 순서대로 줄을 세우려고 한다.
각 학생의 키를 직접 재서 정렬하면 간단하겠지만, 마땅한 방법이 없어서 두 학생의 키를 비교하는 방법을 사용하
기로 하였다. 그나마도 모든 학생들을 다 비교해 본 것이 아니고, 일부 학생들의 키만을 비교해 보았다.
일부 학생들의 키를 비교한 결과가 주어졌을 때, 줄을 세우는 프로그램을 작성하시오.
12. 풀어볼 문제 줄 세우기(BOJ 2252번)
2021 AL林 정기 스터디
[입력]
N = 3
M = 2
1 < 3
2 < 3
학생들의 번호는 1번부터 N번, M은 키를 비교한 회수
답이 여러 가지인 경우에는 아무거나 출력한다.
[출력]
1 2 3
13. 풀어볼 문제 줄 세우기(BOJ 2252번)
2021 AL林 정기 스터디
[입력]
N = 8, M = 6
1 4
2 3
2 4
4 5
6 7
5 8
[출력]
1 2 3 4 6 5 7 8
24. 위상 정렬 알고리즘 ① : Queue
2021 AL林 정기 스터디
아이디어는 쉽습니다.
하지만 어떻게 구현해야 효율적일까요?
25. 위상 정렬 알고리즘 ① : Queue
2021 AL林 정기 스터디
for i ← 1 to n {
① 진입 간선이 없는 정점 u를 선택한다.
② A[i] = u
③ 정점 u와 u의 진출 간선을 모두 제거한다.
}
return A[]
26. 위상 정렬 알고리즘 ① : Queue
2021 AL林 정기 스터디
Q = Queue()
INDEGREE = [0, 0, ..., 0]
1. 각 정점마다 진입 간선의 수를 INDEGREE에 저장한다.
2. 진입 간선이 없는 정점(INDEGREE[i] == 0)을 모두 Q에 넣는다.
27. 위상 정렬 알고리즘 ① : Queue
2021 AL林 정기 스터디
result = List()
while (Q is not empty) {
① u = Q.pop()
② result.push(u)
③ for (u에 인접한 정점 v에 대해서){
INDEGREE[v] -= 1
if (INDEGREE[v] == 0) Q.push(v)
}
}
return result
28. 위상 정렬 알고리즘 ① : 소스코드(python)
2021 AL林 정기 스터디
from collections import deque
N, M = map(int, input().split())
in_degree = [0] * (N + 1)
adj = [list() for _ in range(N)]
# 1. 각 정점마다 진입 간선의 수를 INDEGREE에 저장한다.
for _ in range(M):
u, v = map(int, input().split())
adj[u - 1].append(v - 1)
in_degree[v - 1] += 1
# 2. 진입 간선이 없는 정점(INDEGREE[i] == 0)을 모두 queue에 넣는다.
queue = deque()
for u in range(N):
if in_degree[u] == 0:
queue.append(u)
29. 위상 정렬 알고리즘 ① : 소스코드(python)
2021 AL林 정기 스터디
# Topological Sorting
result = []
while len(queue) > 0:
u = queue.popleft()
result.append(u)
for v in adj[u]:
in_degree[v] -= 1
if in_degree[v] == 0:
queue.append(v)
# 출력
for u in result:
print(u + 1, end=' ')
30. 위상 정렬 알고리즘 ① : 소스코드(python)
2021 AL林 정기 스터디
Queue 풀이 (소스코드 전체)
http://boj.kr/e6f76e4167714f60852b3cf559aaefad
31. 위상 정렬 알고리즘 ② : DFS
2021 AL林 정기 스터디
사실 방금까지 한 위상 정렬은
DFS 종료 순서를 뒤집기만 하면 됩니다.
32. 위상 정렬 알고리즘 ② : DFS
2021 AL林 정기 스터디
DFS(u) {
visited[u] = True
for (u에 인접한 정점 v에 대해서)
if (visited[v] = False) DFS(v)
result.push(u)
}
33. 위상 정렬 알고리즘 ② : DFS
2021 AL林 정기 스터디
result = List()
for (모든 정점 u에 대해서)
visited[u] = False
for (모든 정점 u에 대해서)
if (visited[u] = False) DFS(u)
reverse(result) // result 를 뒤집는다.
return result
34. 위상 정렬 알고리즘 ② : 소스코드(python)
2021 AL林 정기 스터디
import sys
sys.setrecursionlimit(10**9)
def dfs_ts(u):
visited[u] = True
for v in adj[u]:
if not visited[v]:
dfs_ts(v)
result.append(u)
35. 위상 정렬 알고리즘 ② : 소스코드(python)
2021 AL林 정기 스터디
N, M = map(int, input().split())
adj = [list() for _ in range(N)]
for _ in range(M):
u, v = map(int, input().split())
adj[u - 1].append(v - 1)
visited = [False] * N
result = []
for u in range(N):
if not visited[u]:
dfs_ts(u)
result.reverse()
for u in result:
print(u + 1, end=' ')
36. 위상 정렬 알고리즘 ② : 소스코드(python)
2021 AL林 정기 스터디
DFS 풀이 (소스코드 전체)
http://boj.kr/1db6303b1a6a47c48abc2723cf2b7cd9
37. 위상 정렬 알고리즘 ② : DFS
2021 AL林 정기 스터디
이 방법은 먼저 알아본 방법에 비해서
상당히 비직관적입니다.
이 알고리즘의 정당성을 증명해볼까요?
38. 위상 정렬 알고리즘 ② : DFS 정당성
2021 AL林 정기 스터디
귀류법으로 증명합니다. (알고리즘 문제 해결 전략 831페이지를 참고했습니다.)
위상 정렬 결과에서 역행하는 간선 (u,v)가 있다고 가정해봅시다.
위상 정렬 결과의 예 : {3, 2, ... , v, ... ,u, ..., 9, ...}
이 결과가 나오기 위해서는 dfs(u)가 종료한 후 dfs(v)가 종료했다는 것입니다.
dfs(u)는 종료 전에 인접한 간선을 모두 보기 때문에 (u,v) 또한 검사했을 것입니다.
39. 위상 정렬 알고리즘 ② : DFS 정당성
2021 AL林 정기 스터디
이때 dfs(u)에서
1. visited[v]가 거짓일 경우
dfs(u)를 dfs(v)를 재귀 호출했을 것입니다.
따라서 dfs(v)가 종료된 후에 dfs(u)가 종료되었을 것이고 v는 u의 왼쪽에 있을 수 없습니다.
2. visited[v]가 참일 경우
dfs(v)는 이미 한번 호출되었어야 합니다.
그런데 dfs(v)가 dfs(u)보다 늦게 끝나기 위해서는, dfs(v)가 현재 실행 중이어야 합니다.
이렇게 되기 위해서는 v에서 u로 가는 경로가 필요합니다. (dfs(v) -> dfs(u)로 재귀호출)
하지만 그렇다면 그래프는 사이클을 형성합니다.
40. 위상 정렬 알고리즘 ② : DFS
2021 AL林 정기 스터디
따라서 DFS로 얻어낸 위상 정렬의 결과에서
(u, v)인 간선이 있을 경우
u는 v의 왼쪽에 있을 수 밖에 없습니다!
42. 다시보기
2021 AL林 정기 스터디
result = List()
while (Q is not empty) {
① u = Q.pop()
② result.push(u)
③ for (u에 인접한 정점 v에 대해서){
INDEGREE[v] -= 1
if (INDEGREE[v] == 0) Q.push(v)
}
}
return result
43. 큐의 크기를 통해서 알 수 있는 것
2021 AL林 정기 스터디
① 중간에 큐가 비어버리면 (루프가 N번 진행되지 않으면)
위상 정렬이 불가능한데 이 경우는 사이클이 있는 경우입니다.
(사이클이 있으면 위상정렬을 할 수 없다고 했죠.)
② 중간에 큐의 크기가 2 이상인 경우가 있다면
위상 정렬의 결과가 2개 이상입니다.
(큐에서 pop할 때 빼낼 수 있는 원소가 여러개라서 그렇습니다.)
44. earliest time
2021 AL林 정기 스터디
DP + Topological Sort
연습문제 : 작업 (BOJ 2056), ACM Craft (BOJ 1005)
https://www.acmicpc.net/problem/2056
https://www.acmicpc.net/problem/1005
45. critical path(임계경로)
2021 AL林 정기 스터디
A critical path is a longest path through the DAG, corresponding to the
longest time to perform any sequence of jobs. Thus, the weight of a critical
path provides a lower bound on the total time to perform all the jobs.
연습문제 : 임계경로 (BOJ 1948)
https://www.acmicpc.net/problem/1948