μμ μ λ ¬ (Topological Sort)
μμ μ λ ¬ (Topological Sort)¶
κ°μ¶
μμ μ λ ¬μ λ°©ν₯ λΉμν κ·Έλν(DAG)μ μ μ λ€μ μ νμΌλ‘ μ λ ¬νλ μκ³ λ¦¬μ¦μ λλ€. λͺ¨λ κ°μ (u, v)μ λν΄ uκ° vλ³΄λ€ λ¨Όμ λνλλλ‘ μ λ ¬ν©λλ€.
λͺ©μ°¨¶
- μμ μ λ ¬ κ°λ
- Kahn μκ³ λ¦¬μ¦ (BFS)
- DFS κΈ°λ° μμ μ λ ¬
- μ¬μ΄ν΄ νμ§
- νμ© λ¬Έμ
- μ°μ΅ λ¬Έμ
1. μμ μ λ ¬ κ°λ ¶
1.1 DAG (Directed Acyclic Graph)¶
DAG: λ°©ν₯ λΉμν κ·Έλν
- λ°©ν₯ κ°μ μ κ°μ§
- μ¬μ΄ν΄μ΄ μμ
μμ μ λ ¬μ΄ κ°λ₯ν 쑰건: κ·Έλνκ° DAG
μμ: μκ° μ μ²
κ³Όλͺ©A β κ³Όλͺ©B β κ³Όλͺ©D
β β
κ³Όλͺ©C β κ³Όλͺ©E
μ ν¨ν μκ° μμ:
A β B β C β D β E λλ
A β C β B β D β E λλ
A β C β B β E β D
1.2 μ§μ μ°¨μμ μ§μΆ μ°¨μ¶
μ§μ
μ°¨μ (In-degree): μ μ μΌλ‘ λ€μ΄μ€λ κ°μ μ
μ§μΆ μ°¨μ (Out-degree): μ μ μμ λκ°λ κ°μ μ
μμ:
A β B
β β
C β D
μ μ | μ§μ
| μ§μΆ
-----|------|------
A | 0 | 2
B | 1 | 1
C | 1 | 1
D | 2 | 0
μ§μ
μ°¨μκ° 0μΈ μ μ : μμμ (μμ‘΄μ± μμ)
1.3 μμ μμμ νΉμ±¶
- μ¬λ¬ μμ μμκ° μ‘΄μ¬ν μ μμ
- DAGμμλ§ μμ μ λ ¬ κ°λ₯
- μ¬μ΄ν΄ μ‘΄μ¬ μ μμ μ λ ¬ λΆκ°λ₯
μμ μ λ ¬ μκ³ λ¦¬μ¦:
1. Kahn μκ³ λ¦¬μ¦ (BFS) - O(V + E)
2. DFS κΈ°λ° - O(V + E)
2. Kahn μκ³ λ¦¬μ¦ (BFS)¶
2.1 μκ³ λ¦¬μ¦ μ리¶
1. λͺ¨λ μ μ μ μ§μ
μ°¨μ κ³μ°
2. μ§μ
μ°¨μκ° 0μΈ μ μ μ νμ μ½μ
3. νμμ μ μ μ κΊΌλ΄ κ²°κ³Όμ μΆκ°
4. ν΄λΉ μ μ μμ λκ°λ κ°μ μ κ±° (μΈμ μ μ μ§μ
μ°¨μ κ°μ)
5. μλ‘ μ§μ
μ°¨μκ° 0μ΄ λ μ μ μ νμ μ½μ
6. νκ° λΉ λκΉμ§ λ°λ³΅
μκ°ν:
μ΄κΈ°: in_degree = [0, 1, 1, 2]
A(0) β B(1) β D(2)
β β
C(1) ββββ
λ¨κ³ 1: A μ ν (μ§μ
μ°¨μ 0)
ν: [A], κ²°κ³Ό: [A]
B, Cμ μ§μ
μ°¨μ κ°μ β B(0), C(0)
λ¨κ³ 2: B μ ν (λλ C)
ν: [B, C], κ²°κ³Ό: [A, B]
Dμ μ§μ
μ°¨μ κ°μ β D(1)
λ¨κ³ 3: C μ ν
ν: [C], κ²°κ³Ό: [A, B, C]
Dμ μ§μ
μ°¨μ κ°μ β D(0)
λ¨κ³ 4: D μ ν
ν: [D], κ²°κ³Ό: [A, B, C, D]
μ΅μ’
: [A, B, C, D]
2.2 ꡬν¶
from collections import deque
def topological_sort_kahn(n, edges):
"""
Kahn μκ³ λ¦¬μ¦ (BFS κΈ°λ° μμ μ λ ¬)
n: μ μ μ (0λΆν° n-1)
edges: [(u, v), ...] u β v κ°μ
μκ°: O(V + E)
"""
# κ·Έλν κ΅¬μ± λ° μ§μ
μ°¨μ κ³μ°
graph = [[] for _ in range(n)]
in_degree = [0] * n
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
# μ§μ
μ°¨μκ° 0μΈ μ μ λ€λ‘ μμ
queue = deque()
for i in range(n):
if in_degree[i] == 0:
queue.append(i)
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# μ¬μ΄ν΄ νμΈ
if len(result) != n:
return [] # μ¬μ΄ν΄ μ‘΄μ¬
return result
# μμ
n = 6
edges = [(5, 2), (5, 0), (4, 0), (4, 1), (2, 3), (3, 1)]
print(topological_sort_kahn(n, edges)) # [4, 5, 2, 0, 3, 1] λλ λ€λ₯Έ μ ν¨ν μμ
2.3 C++ ꡬν¶
#include <vector>
#include <queue>
using namespace std;
vector<int> topologicalSortKahn(int n, vector<pair<int, int>>& edges) {
vector<vector<int>> graph(n);
vector<int> inDegree(n, 0);
for (auto& [u, v] : edges) {
graph[u].push_back(v);
inDegree[v]++;
}
queue<int> q;
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) {
q.push(i);
}
}
vector<int> result;
while (!q.empty()) {
int node = q.front();
q.pop();
result.push_back(node);
for (int neighbor : graph[node]) {
if (--inDegree[neighbor] == 0) {
q.push(neighbor);
}
}
}
if (result.size() != n) {
return {}; // μ¬μ΄ν΄ μ‘΄μ¬
}
return result;
}
3. DFS κΈ°λ° μμ μ λ ¬¶
3.1 μκ³ λ¦¬μ¦ μ리¶
1. λͺ¨λ μ μ μ λν΄ DFS μν
2. DFSκ° λλλ μμλλ‘ μ€νμ μ½μ
3. μ€νμ μμμΌλ‘ μΆλ ₯
μμ΄λμ΄:
- DFSμμ μ μ vμ νμμ΄ λλλ©΄ vμμ κ° μ μλ λͺ¨λ μ μ μ μ΄λ―Έ μ²λ¦¬λ¨
- λ°λΌμ μ€νμ λ¦κ² λ€μ΄κ° μ μ μ΄ μμ μμμ λ¨Όμ μμΌ ν¨
μκ°ν:
A β B β D
β
C
DFS λ°©λ¬Έ μμ: A β B β D(μλ£) β B(μλ£) β C(μλ£) β A(μλ£)
μ€ν (μλ£ μ): [D, B, C, A]
κ²°κ³Ό (μμ): [A, C, B, D] λλ [A, B, D, C]
3.2 ꡬν¶
def topological_sort_dfs(n, edges):
"""
DFS κΈ°λ° μμ μ λ ¬
μκ°: O(V + E)
"""
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
visited = [0] * n # 0: λ―Έλ°©λ¬Έ, 1: λ°©λ¬Έ μ€, 2: μλ£
result = []
has_cycle = False
def dfs(node):
nonlocal has_cycle
if has_cycle:
return
visited[node] = 1 # λ°©λ¬Έ μ€
for neighbor in graph[node]:
if visited[neighbor] == 1:
# λ°©λ¬Έ μ€μΈ λ
Έλλ₯Ό λ€μ λ§λ¨ β μ¬μ΄ν΄
has_cycle = True
return
if visited[neighbor] == 0:
dfs(neighbor)
visited[node] = 2 # μλ£
result.append(node)
for i in range(n):
if visited[i] == 0:
dfs(i)
if has_cycle:
return []
return result[::-1] # μμ
# μμ
n = 6
edges = [(5, 2), (5, 0), (4, 0), (4, 1), (2, 3), (3, 1)]
print(topological_sort_dfs(n, edges)) # [5, 4, 2, 3, 1, 0] λλ λ€λ₯Έ μ ν¨ν μμ
3.3 μ€ν μ¬μ© (λΉμ¬κ·)¶
def topological_sort_iterative(n, edges):
"""λ°λ³΅μ DFS μμ μ λ ¬"""
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
visited = [False] * n
result = []
for start in range(n):
if visited[start]:
continue
stack = [(start, False)] # (λ
Έλ, μ²λ¦¬ μλ£ μ¬λΆ)
while stack:
node, processed = stack.pop()
if processed:
result.append(node)
continue
if visited[node]:
continue
visited[node] = True
stack.append((node, True)) # μλ£ μ²λ¦¬λ₯Ό μν΄ λ€μ μ½μ
for neighbor in graph[node]:
if not visited[neighbor]:
stack.append((neighbor, False))
return result[::-1]
4. μ¬μ΄ν΄ νμ§¶
4.1 Kahn μκ³ λ¦¬μ¦μμμ μ¬μ΄ν΄ νμ§¶
def has_cycle_kahn(n, edges):
"""
μμ μ λ ¬ κ²°κ³Όμ κΈΈμ΄κ° nλ³΄λ€ μμΌλ©΄ μ¬μ΄ν΄ μ‘΄μ¬
"""
result = topological_sort_kahn(n, edges)
return len(result) != n
# μ¬μ΄ν΄μ΄ μλ κ²½μ°
edges_with_cycle = [(0, 1), (1, 2), (2, 0)] # 0 β 1 β 2 β 0
print(has_cycle_kahn(3, edges_with_cycle)) # True
4.2 DFS μμ κΈ°λ° μ¬μ΄ν΄ νμ§¶
def has_cycle_dfs(n, edges):
"""
DFS μμ κΈ°λ²μΌλ‘ μ¬μ΄ν΄ νμ§
WHITE(0): λ―Έλ°©λ¬Έ
GRAY(1): λ°©λ¬Έ μ€ (μ¬κ· μ€νμ μμ)
BLACK(2): μλ£
"""
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
WHITE, GRAY, BLACK = 0, 1, 2
color = [WHITE] * n
def dfs(node):
color[node] = GRAY
for neighbor in graph[node]:
if color[neighbor] == GRAY:
return True # μ¬μ΄ν΄ λ°κ²¬
if color[neighbor] == WHITE:
if dfs(neighbor):
return True
color[node] = BLACK
return False
for i in range(n):
if color[i] == WHITE:
if dfs(i):
return True
return False
4.3 μ¬μ΄ν΄ κ²½λ‘ μ°ΎκΈ°¶
def find_cycle(n, edges):
"""μ¬μ΄ν΄μ΄ μλ€λ©΄ μ¬μ΄ν΄ κ²½λ‘ λ°ν"""
graph = [[] for _ in range(n)]
for u, v in edges:
graph[u].append(v)
WHITE, GRAY, BLACK = 0, 1, 2
color = [WHITE] * n
parent = [-1] * n
def dfs(node):
color[node] = GRAY
for neighbor in graph[node]:
if color[neighbor] == GRAY:
# μ¬μ΄ν΄ λ°κ²¬! κ²½λ‘ λ³΅μ
cycle = [neighbor]
curr = node
while curr != neighbor:
cycle.append(curr)
curr = parent[curr]
cycle.append(neighbor)
return cycle[::-1]
if color[neighbor] == WHITE:
parent[neighbor] = node
result = dfs(neighbor)
if result:
return result
color[node] = BLACK
return None
for i in range(n):
if color[i] == WHITE:
result = dfs(i)
if result:
return result
return None
# μμ
edges = [(0, 1), (1, 2), (2, 3), (3, 1)] # 1 β 2 β 3 β 1 μ¬μ΄ν΄
print(find_cycle(4, edges)) # [1, 2, 3, 1]
5. νμ© λ¬Έμ ¶
5.1 μκ° μ μ² (κΈ°λ³Έ)¶
def can_finish(num_courses, prerequisites):
"""
λͺ¨λ κ³Όλͺ©μ μκ°ν μ μλμ§ νμΈ
prerequisites[i] = [a, b] : bλ₯Ό λ¨Όμ λ€μ΄μΌ aλ₯Ό λ€μ μ μμ
"""
result = topological_sort_kahn(num_courses,
[(b, a) for a, b in prerequisites])
return len(result) == num_courses
# LeetCode 207. Course Schedule
print(can_finish(2, [[1, 0]])) # True: 0 β 1
print(can_finish(2, [[1, 0], [0, 1]])) # False: μ¬μ΄ν΄
5.2 μκ° μμ μ°ΎκΈ°¶
def find_order(num_courses, prerequisites):
"""
κ°λ₯ν μκ° μμ λ°ν
λΆκ°λ₯νλ©΄ λΉ λ¦¬μ€νΈ λ°ν
"""
return topological_sort_kahn(num_courses,
[(b, a) for a, b in prerequisites])
# LeetCode 210. Course Schedule II
print(find_order(4, [[1, 0], [2, 0], [3, 1], [3, 2]]))
# [0, 1, 2, 3] λλ [0, 2, 1, 3]
5.3 λΉλ μμ¶
def build_order(projects, dependencies):
"""
νλ‘μ νΈ λΉλ μμ κ²°μ
dependencies: [(a, b), ...] - bλ₯Ό λΉλν ν aλ₯Ό λΉλ
"""
# νλ‘μ νΈ μ΄λ¦ β μΈλ±μ€ λ§€ν
proj_to_idx = {p: i for i, p in enumerate(projects)}
n = len(projects)
edges = [(proj_to_idx[b], proj_to_idx[a]) for a, b in dependencies]
order = topological_sort_kahn(n, edges)
if not order:
return None # μν μμ‘΄μ±
return [projects[i] for i in order]
# μμ
projects = ['a', 'b', 'c', 'd', 'e', 'f']
dependencies = [('a', 'd'), ('f', 'b'), ('b', 'd'), ('f', 'a'), ('d', 'c')]
print(build_order(projects, dependencies)) # ['e', 'f', 'c', 'b', 'd', 'a'] λ±
5.4 μμ μ€μΌμ€λ§ (κ°μ₯ λΉ λ₯Έ μλ£ μκ°)¶
def earliest_completion(n, edges, times):
"""
κ° μμ
μ κ°μ₯ λΉ λ₯Έ μλ£ μκ° κ³μ°
edges: [(u, v), ...] u μλ£ ν v μμ κ°λ₯
times[i]: μμ
iμ μμ μκ°
"""
from collections import deque
graph = [[] for _ in range(n)]
in_degree = [0] * n
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
# earliest[i] = μμ
iμ κ°μ₯ λΉ λ₯Έ μμ μκ°
earliest = [0] * n
queue = deque()
for i in range(n):
if in_degree[i] == 0:
queue.append(i)
while queue:
node = queue.popleft()
for neighbor in graph[node]:
# neighborμ μμ μκ° κ°±μ
earliest[neighbor] = max(earliest[neighbor],
earliest[node] + times[node])
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
# μλ£ μκ° κ³μ°
completion = [earliest[i] + times[i] for i in range(n)]
return completion, max(completion)
# μμ
n = 4
edges = [(0, 2), (1, 2), (2, 3)]
times = [3, 2, 5, 4]
completion, total = earliest_completion(n, edges, times)
print(f"μλ£ μκ°: {completion}") # [3, 2, 8, 12]
print(f"μ 체 μμ: {total}") # 12
5.5 μ¬μ μ κ°μ₯ λΉ λ₯Έ μμ μμ¶
import heapq
def lexicographic_topological_sort(n, edges):
"""
μ¬μ μμΌλ‘ κ°μ₯ λΉ λ₯Έ μμ μμ
μ΅μ ν μ¬μ©
"""
graph = [[] for _ in range(n)]
in_degree = [0] * n
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
# μ΅μ ν μ¬μ©
heap = []
for i in range(n):
if in_degree[i] == 0:
heapq.heappush(heap, i)
result = []
while heap:
node = heapq.heappop(heap)
result.append(node)
for neighbor in graph[node]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
heapq.heappush(heap, neighbor)
return result if len(result) == n else []
# μμ
n = 4
edges = [(0, 1), (0, 2), (1, 3), (2, 3)]
print(lexicographic_topological_sort(n, edges)) # [0, 1, 2, 3]
5.6 μμ μ λ ¬ κ²½λ‘μ μ¶
def count_topological_sorts(n, edges):
"""
κ°λ₯ν μμ μ λ ¬μ μ (λ°±νΈλνΉ)
μ£Όμ: μ§μμ μκ° λ³΅μ‘λ
"""
graph = [[] for _ in range(n)]
in_degree = [0] * n
for u, v in edges:
graph[u].append(v)
in_degree[v] += 1
count = 0
def backtrack(path):
nonlocal count
if len(path) == n:
count += 1
return
for i in range(n):
if i not in path and in_degree[i] == 0:
# μ ν
for neighbor in graph[i]:
in_degree[neighbor] -= 1
path.add(i)
backtrack(path)
# 볡μ
path.remove(i)
for neighbor in graph[i]:
in_degree[neighbor] += 1
backtrack(set())
return count
# μμ (μμ nμμλ§ μ¬μ©)
n = 4
edges = [(0, 1), (0, 2), (1, 3), (2, 3)]
print(count_topological_sorts(n, edges)) # 2: [0,1,2,3], [0,2,1,3]
5.7 Alien Dictionary¶
def alien_order(words):
"""
μΈκ³μ΄ μνλ²³ μμ μΆλ‘
words: μ¬μ μμΌλ‘ μ λ ¬λ μΈκ³μ΄ λ¨μ΄ λͺ©λ‘
"""
from collections import defaultdict
# λͺ¨λ λ¬Έμ μμ§
chars = set()
for word in words:
chars.update(word)
graph = defaultdict(set)
in_degree = {c: 0 for c in chars}
# μΈμ ν λ¨μ΄λ€μ λΉκ΅νμ¬ μμ μΆλ‘
for i in range(len(words) - 1):
w1, w2 = words[i], words[i + 1]
# μμΈ: w1μ΄ w2μ μ λμ¬μΈλ° λ κΈΈλ©΄ λΆκ°λ₯
if len(w1) > len(w2) and w1.startswith(w2):
return ""
for c1, c2 in zip(w1, w2):
if c1 != c2:
if c2 not in graph[c1]:
graph[c1].add(c2)
in_degree[c2] += 1
break
# μμ μ λ ¬
from collections import deque
queue = deque([c for c in chars if in_degree[c] == 0])
result = []
while queue:
c = queue.popleft()
result.append(c)
for neighbor in graph[c]:
in_degree[neighbor] -= 1
if in_degree[neighbor] == 0:
queue.append(neighbor)
if len(result) != len(chars):
return "" # μ¬μ΄ν΄ (λΆκ°λ₯ν μ
λ ₯)
return "".join(result)
# LeetCode 269. Alien Dictionary
words = ["wrt", "wrf", "er", "ett", "rftt"]
print(alien_order(words)) # "wertf"
6. μ°μ΅ λ¬Έμ ¶
μΆμ² λ¬Έμ ¶
| λμ΄λ | λ¬Έμ | νλ«νΌ | μ ν |
|---|---|---|---|
| ββ | μ€ μΈμ°κΈ° | λ°±μ€ | κΈ°λ³Έ μμμ λ ¬ |
| ββ | Course Schedule | LeetCode | μ¬μ΄ν΄ νμ§ |
| ββ | Course Schedule II | LeetCode | μμμ λ ¬ μμ |
| βββ | μμ | λ°±μ€ | μ΅μ μκ° |
| βββ | κ²μ κ°λ° | λ°±μ€ | μμ μ€μΌμ€λ§ |
| βββ | λ¬Έμ μ§ | λ°±μ€ | μ¬μ μ μμμ λ ¬ |
| ββββ | Alien Dictionary | LeetCode | μμ μΆλ‘ |
μκ³ λ¦¬μ¦ λΉκ΅¶
βββββββββββββββββββ¬ββββββββββββββ¬ββββββββββββββ¬βββββββββββββββββββββ
β μκ³ λ¦¬μ¦ β μκ° β κ³΅κ° β νΉμ§ β
βββββββββββββββββββΌββββββββββββββΌββββββββββββββΌβββββββββββββββββββββ€
β Kahn (BFS) β O(V + E) β O(V) β μ§μ
μ°¨μ κΈ°λ° β
β DFS κΈ°λ° β O(V + E) β O(V) β μ’
λ£ μμ β
βββββββββββββββββββΌββββββββββββββΌββββββββββββββΌβββββββββββββββββββββ€
β μ¬μ μ (ν) β O((V+E)logV)β O(V) β μ΅μ ν μ¬μ© β
βββββββββββββββββββ΄ββββββββββββββ΄ββββββββββββββ΄βββββββββββββββββββββ
V = μ μ μ, E = κ°μ μ
λ€μ λ¨κ³¶
- 14_Shortest_Path.md - Dijkstra, Bellman-Ford
μ°Έκ³ μλ£¶
- Topological Sorting
- Introduction to Algorithms (CLRS) - Chapter 22.4