13_topological_sort.py

Download
python 433 lines 12.6 KB
  1"""
  2์œ„์ƒ ์ •๋ ฌ (Topological Sort)
  3Topological Sorting
  4
  5๋ฐฉํ–ฅ ๋น„์ˆœํ™˜ ๊ทธ๋ž˜ํ”„(DAG)์—์„œ ์ •์ ์„ ์„ ํ˜• ์ˆœ์„œ๋กœ ๋‚˜์—ดํ•ฉ๋‹ˆ๋‹ค.
  6"""
  7
  8from typing import List, Dict, Set, Optional, Tuple
  9from collections import deque, defaultdict
 10
 11
 12# =============================================================================
 13# 1. Kahn's ์•Œ๊ณ ๋ฆฌ์ฆ˜ (BFS ๊ธฐ๋ฐ˜)
 14# =============================================================================
 15
 16def topological_sort_kahn(n: int, edges: List[Tuple[int, int]]) -> List[int]:
 17    """
 18    Kahn's ์•Œ๊ณ ๋ฆฌ์ฆ˜ (์ง„์ž… ์ฐจ์ˆ˜ ๊ธฐ๋ฐ˜)
 19    ์‹œ๊ฐ„๋ณต์žก๋„: O(V + E)
 20    ๊ณต๊ฐ„๋ณต์žก๋„: O(V + E)
 21
 22    edges: [(from, to), ...] - from โ†’ to ์˜์กด ๊ด€๊ณ„
 23    ๋ฐ˜ํ™˜: ์œ„์ƒ ์ •๋ ฌ๋œ ์ˆœ์„œ, ์‚ฌ์ดํด ์žˆ์œผ๋ฉด ๋นˆ ๋ฆฌ์ŠคํŠธ
 24    """
 25    # ๊ทธ๋ž˜ํ”„ ๋ฐ ์ง„์ž… ์ฐจ์ˆ˜ ๊ตฌ์„ฑ
 26    graph = defaultdict(list)
 27    in_degree = [0] * n
 28
 29    for u, v in edges:
 30        graph[u].append(v)
 31        in_degree[v] += 1
 32
 33    # ์ง„์ž… ์ฐจ์ˆ˜ 0์ธ ๋…ธ๋“œ๋กœ ์‹œ์ž‘
 34    queue = deque([i for i in range(n) if in_degree[i] == 0])
 35    result = []
 36
 37    while queue:
 38        node = queue.popleft()
 39        result.append(node)
 40
 41        for neighbor in graph[node]:
 42            in_degree[neighbor] -= 1
 43            if in_degree[neighbor] == 0:
 44                queue.append(neighbor)
 45
 46    # ๋ชจ๋“  ๋…ธ๋“œ ๋ฐฉ๋ฌธ ์—ฌ๋ถ€๋กœ ์‚ฌ์ดํด ํ™•์ธ
 47    return result if len(result) == n else []
 48
 49
 50# =============================================================================
 51# 2. DFS ๊ธฐ๋ฐ˜ ์œ„์ƒ ์ •๋ ฌ
 52# =============================================================================
 53
 54def topological_sort_dfs(n: int, edges: List[Tuple[int, int]]) -> List[int]:
 55    """
 56    DFS ๊ธฐ๋ฐ˜ ์œ„์ƒ ์ •๋ ฌ
 57    ์‹œ๊ฐ„๋ณต์žก๋„: O(V + E)
 58    ๊ณต๊ฐ„๋ณต์žก๋„: O(V + E)
 59    """
 60    graph = defaultdict(list)
 61    for u, v in edges:
 62        graph[u].append(v)
 63
 64    WHITE, GRAY, BLACK = 0, 1, 2
 65    color = [WHITE] * n
 66    result = []
 67    has_cycle = False
 68
 69    def dfs(node: int) -> None:
 70        nonlocal has_cycle
 71
 72        if has_cycle:
 73            return
 74
 75        color[node] = GRAY  # ๋ฐฉ๋ฌธ ์ค‘
 76
 77        for neighbor in graph[node]:
 78            if color[neighbor] == GRAY:  # ์‚ฌ์ดํด ๋ฐœ๊ฒฌ
 79                has_cycle = True
 80                return
 81            if color[neighbor] == WHITE:
 82                dfs(neighbor)
 83
 84        color[node] = BLACK  # ๋ฐฉ๋ฌธ ์™„๋ฃŒ
 85        result.append(node)
 86
 87    for i in range(n):
 88        if color[i] == WHITE:
 89            dfs(i)
 90
 91    return result[::-1] if not has_cycle else []
 92
 93
 94# =============================================================================
 95# 3. ์‚ฌ์ดํด ํƒ์ง€
 96# =============================================================================
 97
 98def has_cycle(n: int, edges: List[Tuple[int, int]]) -> bool:
 99    """
100    ๋ฐฉํ–ฅ ๊ทธ๋ž˜ํ”„์—์„œ ์‚ฌ์ดํด ์กด์žฌ ์—ฌ๋ถ€
101    ์‹œ๊ฐ„๋ณต์žก๋„: O(V + E)
102    """
103    graph = defaultdict(list)
104    for u, v in edges:
105        graph[u].append(v)
106
107    WHITE, GRAY, BLACK = 0, 1, 2
108    color = [WHITE] * n
109
110    def dfs(node: int) -> bool:
111        color[node] = GRAY
112
113        for neighbor in graph[node]:
114            if color[neighbor] == GRAY:
115                return True  # ์‚ฌ์ดํด
116            if color[neighbor] == WHITE and dfs(neighbor):
117                return True
118
119        color[node] = BLACK
120        return False
121
122    for i in range(n):
123        if color[i] == WHITE and dfs(i):
124            return True
125
126    return False
127
128
129# =============================================================================
130# 4. ๋ชจ๋“  ์œ„์ƒ ์ •๋ ฌ ์ˆœ์„œ ์ฐพ๊ธฐ
131# =============================================================================
132
133def all_topological_sorts(n: int, edges: List[Tuple[int, int]]) -> List[List[int]]:
134    """
135    ๋ชจ๋“  ๊ฐ€๋Šฅํ•œ ์œ„์ƒ ์ •๋ ฌ ์ˆœ์„œ ์ฐพ๊ธฐ
136    ์‹œ๊ฐ„๋ณต์žก๋„: O(V! * (V + E)) - ์ตœ์•…์˜ ๊ฒฝ์šฐ
137    """
138    graph = defaultdict(list)
139    in_degree = [0] * n
140
141    for u, v in edges:
142        graph[u].append(v)
143        in_degree[v] += 1
144
145    result = []
146    path = []
147    visited = [False] * n
148
149    def backtrack():
150        # ๋ชจ๋“  ๋…ธ๋“œ ๋ฐฉ๋ฌธ ์™„๋ฃŒ
151        if len(path) == n:
152            result.append(path.copy())
153            return
154
155        for i in range(n):
156            if not visited[i] and in_degree[i] == 0:
157                # ์„ ํƒ
158                visited[i] = True
159                path.append(i)
160                for neighbor in graph[i]:
161                    in_degree[neighbor] -= 1
162
163                backtrack()
164
165                # ๋ณต์›
166                visited[i] = False
167                path.pop()
168                for neighbor in graph[i]:
169                    in_degree[neighbor] += 1
170
171    backtrack()
172    return result
173
174
175# =============================================================================
176# 5. ์‹ค์ „ ๋ฌธ์ œ: ์ˆ˜๊ฐ• ๊ณผ๋ชฉ ์ˆœ์„œ (Course Schedule)
177# =============================================================================
178
179def can_finish(num_courses: int, prerequisites: List[List[int]]) -> bool:
180    """
181    ๋ชจ๋“  ๊ณผ๋ชฉ ์ˆ˜๊ฐ• ๊ฐ€๋Šฅ ์—ฌ๋ถ€ (์‚ฌ์ดํด ์—†์Œ = ๊ฐ€๋Šฅ)
182    prerequisites: [course, prereq] - prereq โ†’ course
183    """
184    edges = [(prereq, course) for course, prereq in prerequisites]
185    return not has_cycle(num_courses, edges)
186
187
188def find_order(num_courses: int, prerequisites: List[List[int]]) -> List[int]:
189    """
190    ๊ณผ๋ชฉ ์ˆ˜๊ฐ• ์ˆœ์„œ ๋ฐ˜ํ™˜
191    """
192    edges = [(prereq, course) for course, prereq in prerequisites]
193    return topological_sort_kahn(num_courses, edges)
194
195
196# =============================================================================
197# 6. ์‹ค์ „ ๋ฌธ์ œ: ์™ธ๊ณ„์ธ ์‚ฌ์ „ (Alien Dictionary)
198# =============================================================================
199
200def alien_order(words: List[str]) -> str:
201    """
202    ์™ธ๊ณ„์ธ ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ ๊ฒฐ์ •
203    ๋‹จ์–ด ๋ชฉ๋ก์ด ์‚ฌ์ „์ˆœ์œผ๋กœ ์ •๋ ฌ๋˜์–ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •
204    """
205    # ๋ชจ๋“  ๋ฌธ์ž ์ˆ˜์ง‘
206    chars = set()
207    for word in words:
208        chars.update(word)
209
210    # ๊ทธ๋ž˜ํ”„ ๊ตฌ์„ฑ
211    graph = defaultdict(set)
212    in_degree = defaultdict(int)
213    for char in chars:
214        in_degree[char] = 0
215
216    for i in range(len(words) - 1):
217        word1, word2 = words[i], words[i + 1]
218
219        # ์œ ํšจํ•˜์ง€ ์•Š์€ ์ˆœ์„œ ์ฒดํฌ
220        if len(word1) > len(word2) and word1.startswith(word2):
221            return ""
222
223        # ์ฒซ ๋ฒˆ์งธ ๋‹ค๋ฅธ ๋ฌธ์ž๋กœ ์ˆœ์„œ ๊ฒฐ์ •
224        for c1, c2 in zip(word1, word2):
225            if c1 != c2:
226                if c2 not in graph[c1]:
227                    graph[c1].add(c2)
228                    in_degree[c2] += 1
229                break
230
231    # ์œ„์ƒ ์ •๋ ฌ
232    queue = deque([c for c in chars if in_degree[c] == 0])
233    result = []
234
235    while queue:
236        char = queue.popleft()
237        result.append(char)
238
239        for neighbor in graph[char]:
240            in_degree[neighbor] -= 1
241            if in_degree[neighbor] == 0:
242                queue.append(neighbor)
243
244    return ''.join(result) if len(result) == len(chars) else ""
245
246
247# =============================================================================
248# 7. ์‹ค์ „ ๋ฌธ์ œ: ์ž‘์—… ๋ณ‘๋ ฌ ์‹คํ–‰ (Parallel Courses)
249# =============================================================================
250
251def minimum_semesters(n: int, relations: List[List[int]]) -> int:
252    """
253    ๋ชจ๋“  ๊ณผ๋ชฉ์„ ์ˆ˜๊ฐ•ํ•˜๋Š”๋ฐ ํ•„์š”ํ•œ ์ตœ์†Œ ํ•™๊ธฐ ์ˆ˜
254    ๋ณ‘๋ ฌ ์ˆ˜๊ฐ• ๊ฐ€๋Šฅ, relations: [prev, next]
255    """
256    graph = defaultdict(list)
257    in_degree = [0] * (n + 1)
258
259    for prev_course, next_course in relations:
260        graph[prev_course].append(next_course)
261        in_degree[next_course] += 1
262
263    # ์ง„์ž… ์ฐจ์ˆ˜ 0์ธ ๋…ธ๋“œ๋กœ ์‹œ์ž‘ (1-indexed)
264    queue = deque([i for i in range(1, n + 1) if in_degree[i] == 0])
265    semesters = 0
266    completed = 0
267
268    while queue:
269        semesters += 1
270        next_queue = deque()
271
272        while queue:
273            course = queue.popleft()
274            completed += 1
275
276            for next_course in graph[course]:
277                in_degree[next_course] -= 1
278                if in_degree[next_course] == 0:
279                    next_queue.append(next_course)
280
281        queue = next_queue
282
283    return semesters if completed == n else -1
284
285
286# =============================================================================
287# 8. ์‹ค์ „ ๋ฌธ์ œ: ์ตœ์žฅ ๊ฒฝ๋กœ (DAG)
288# =============================================================================
289
290def longest_path_dag(n: int, edges: List[Tuple[int, int, int]]) -> List[int]:
291    """
292    DAG์—์„œ ๊ฐ ๋…ธ๋“œ๊นŒ์ง€์˜ ์ตœ์žฅ ๊ฒฝ๋กœ (๊ฐ€์ค‘์น˜ ํฌํ•จ)
293    edges: [(from, to, weight), ...]
294    """
295    graph = defaultdict(list)
296    in_degree = [0] * n
297
298    for u, v, w in edges:
299        graph[u].append((v, w))
300        in_degree[v] += 1
301
302    # ์œ„์ƒ ์ •๋ ฌ
303    topo_order = []
304    queue = deque([i for i in range(n) if in_degree[i] == 0])
305
306    while queue:
307        node = queue.popleft()
308        topo_order.append(node)
309        for neighbor, _ in graph[node]:
310            in_degree[neighbor] -= 1
311            if in_degree[neighbor] == 0:
312                queue.append(neighbor)
313
314    # ์ตœ์žฅ ๊ฒฝ๋กœ ๊ณ„์‚ฐ
315    dist = [0] * n
316
317    for node in topo_order:
318        for neighbor, weight in graph[node]:
319            dist[neighbor] = max(dist[neighbor], dist[node] + weight)
320
321    return dist
322
323
324# =============================================================================
325# 9. ์‹ค์ „ ๋ฌธ์ œ: ๋นŒ๋“œ ์ˆœ์„œ ๊ฒฐ์ •
326# =============================================================================
327
328def build_order(projects: List[str], dependencies: List[Tuple[str, str]]) -> List[str]:
329    """
330    ๋นŒ๋“œ ์ˆœ์„œ ๊ฒฐ์ •
331    dependencies: [(proj, depends_on), ...] - proj๊ฐ€ depends_on์— ์˜์กด
332    """
333    # ํ”„๋กœ์ ํŠธ ์ธ๋ฑ์Šค ๋งคํ•‘
334    proj_to_idx = {p: i for i, p in enumerate(projects)}
335    n = len(projects)
336
337    # ๊ฐ„์„  ๋ณ€ํ™˜ (depends_on โ†’ proj)
338    edges = [(proj_to_idx[dep], proj_to_idx[proj]) for proj, dep in dependencies]
339
340    # ์œ„์ƒ ์ •๋ ฌ
341    order = topological_sort_kahn(n, edges)
342
343    if not order:
344        return []  # ์‚ฌ์ดํด ์กด์žฌ
345
346    return [projects[i] for i in order]
347
348
349# =============================================================================
350# ํ…Œ์ŠคํŠธ
351# =============================================================================
352
353def main():
354    print("=" * 60)
355    print("์œ„์ƒ ์ •๋ ฌ (Topological Sort) ์˜ˆ์ œ")
356    print("=" * 60)
357
358    # 1. Kahn's ์•Œ๊ณ ๋ฆฌ์ฆ˜
359    print("\n[1] Kahn's ์•Œ๊ณ ๋ฆฌ์ฆ˜ (BFS)")
360    n = 6
361    edges = [(5, 2), (5, 0), (4, 0), (4, 1), (2, 3), (3, 1)]
362    result = topological_sort_kahn(n, edges)
363    print(f"    ๋…ธ๋“œ: 0-5, ๊ฐ„์„ : {edges}")
364    print(f"    ์œ„์ƒ ์ •๋ ฌ: {result}")
365
366    # 2. DFS ๊ธฐ๋ฐ˜
367    print("\n[2] DFS ๊ธฐ๋ฐ˜ ์œ„์ƒ ์ •๋ ฌ")
368    result = topological_sort_dfs(n, edges)
369    print(f"    ์œ„์ƒ ์ •๋ ฌ: {result}")
370
371    # 3. ์‚ฌ์ดํด ํƒ์ง€
372    print("\n[3] ์‚ฌ์ดํด ํƒ์ง€")
373    cyclic_edges = [(0, 1), (1, 2), (2, 0)]
374    acyclic_edges = [(0, 1), (1, 2), (0, 2)]
375    print(f"    {cyclic_edges}: ์‚ฌ์ดํด {has_cycle(3, cyclic_edges)}")
376    print(f"    {acyclic_edges}: ์‚ฌ์ดํด {has_cycle(3, acyclic_edges)}")
377
378    # 4. ๋ชจ๋“  ์œ„์ƒ ์ •๋ ฌ
379    print("\n[4] ๋ชจ๋“  ์œ„์ƒ ์ •๋ ฌ ์ˆœ์„œ")
380    n = 4
381    edges = [(0, 1), (0, 2), (1, 3), (2, 3)]
382    all_orders = all_topological_sorts(n, edges)
383    print(f"    ๋…ธ๋“œ: 0-3, ๊ฐ„์„ : {edges}")
384    print(f"    ๋ชจ๋“  ์ˆœ์„œ: {all_orders}")
385
386    # 5. ๊ณผ๋ชฉ ์ˆ˜๊ฐ• ์ˆœ์„œ
387    print("\n[5] ๊ณผ๋ชฉ ์ˆ˜๊ฐ• ์ˆœ์„œ")
388    num_courses = 4
389    prerequisites = [[1, 0], [2, 0], [3, 1], [3, 2]]
390    can = can_finish(num_courses, prerequisites)
391    order = find_order(num_courses, prerequisites)
392    print(f"    ๊ณผ๋ชฉ ์ˆ˜: {num_courses}, ์„ ์ˆ˜: {prerequisites}")
393    print(f"    ์ˆ˜๊ฐ• ๊ฐ€๋Šฅ: {can}")
394    print(f"    ์ˆœ์„œ: {order}")
395
396    # 6. ์™ธ๊ณ„์ธ ์‚ฌ์ „
397    print("\n[6] ์™ธ๊ณ„์ธ ์‚ฌ์ „")
398    words = ["wrt", "wrf", "er", "ett", "rftt"]
399    order = alien_order(words)
400    print(f"    ๋‹จ์–ด: {words}")
401    print(f"    ์•ŒํŒŒ๋ฒณ ์ˆœ์„œ: {order}")
402
403    # 7. ์ตœ์†Œ ํ•™๊ธฐ ์ˆ˜
404    print("\n[7] ์ตœ์†Œ ํ•™๊ธฐ ์ˆ˜")
405    n = 3
406    relations = [[1, 3], [2, 3]]
407    semesters = minimum_semesters(n, relations)
408    print(f"    ๊ณผ๋ชฉ: {n}, ๊ด€๊ณ„: {relations}")
409    print(f"    ์ตœ์†Œ ํ•™๊ธฐ: {semesters}")
410
411    # 8. DAG ์ตœ์žฅ ๊ฒฝ๋กœ
412    print("\n[8] DAG ์ตœ์žฅ ๊ฒฝ๋กœ")
413    n = 4
414    edges = [(0, 1, 3), (0, 2, 2), (1, 3, 4), (2, 3, 1)]
415    dist = longest_path_dag(n, edges)
416    print(f"    ๊ฐ„์„ : {edges}")
417    print(f"    ๊ฐ ๋…ธ๋“œ๊นŒ์ง€ ์ตœ์žฅ ๊ฑฐ๋ฆฌ: {dist}")
418
419    # 9. ๋นŒ๋“œ ์ˆœ์„œ
420    print("\n[9] ๋นŒ๋“œ ์ˆœ์„œ")
421    projects = ['a', 'b', 'c', 'd', 'e', 'f']
422    dependencies = [('d', 'a'), ('b', 'f'), ('d', 'b'), ('a', 'f'), ('c', 'd')]
423    order = build_order(projects, dependencies)
424    print(f"    ํ”„๋กœ์ ํŠธ: {projects}")
425    print(f"    ์˜์กด์„ฑ: {dependencies}")
426    print(f"    ๋นŒ๋“œ ์ˆœ์„œ: {order}")
427
428    print("\n" + "=" * 60)
429
430
431if __name__ == "__main__":
432    main()