[Algorithm] Graph Theory

Updated:

πŸ“£
λ³Έ ν¬μŠ€νŠΈλŠ” β€˜μ΄κ²ƒμ΄ 취업을 μœ„ν•œ μ½”λ”© ν…ŒμŠ€νŠΈλ‹€ with νŒŒμ΄μ¬β€™μ„ κ³΅λΆ€ν•˜κ³  μž‘μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€ :)
click > (μ΄μ½”ν…Œ 2021) 이것이 취업을 μœ„ν•œ μ½”λ”© ν…ŒμŠ€νŠΈλ‹€ with 파이썬

Graph Theory

이둠

μ•žμ„œ 배운 DFS/BFS와 μ΅œλ‹¨ κ²½λ‘œμ—μ„œ 닀룬 λ‚΄μš©λ“€ λͺ¨λ‘ κ·Έλž˜ν”„ μ•Œκ³ λ¦¬μ¦˜μ˜ ν•œ μœ ν˜•μ΄λ‹€. κ·Έ 외에도 λ‹€μ–‘ν•œ κ·Έλž˜ν”„ μ•Œκ³ λ¦¬μ¦˜μ΄ μžˆλŠ”λ°, 이 μž₯μ—μ„œλŠ” κ·Έ μ€‘μ—μ„œ λͺ‡ κ°€μ§€λ₯Ό μΆ”κ°€μ μœΌλ‘œ λ°°μš΄λ‹€.

μ„œλ‘œμ†Œ μ§‘ν•© (Disjoint Sets)

곡톡 μ›μ†Œκ°€ μ—†λŠ” 두 집합을 μ˜λ―Έν•œλ‹€.

μ„œλ‘œμ†Œ μ§‘ν•© 자료ꡬ쑰

μ„œλ‘œμ†Œ λΆ€λΆ„ μ§‘ν•©λ“€λ‘œ λ‚˜λˆ„μ–΄μ§„ μ›μ†Œλ“€μ˜ 데이터λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ 자료ꡬ쑰둜 unionκ³Ό find 2개의 μ—°μ‚°μœΌλ‘œ μ‘°μž‘ν•  수 μžˆλ‹€. 집합은 트리 자료ꡬ쑰λ₯Ό μ΄μš©ν•˜μ—¬ ν‘œν˜„ν•œλ‹€.

  • union μ—°μ‚° : 2개의 μ›μ†Œκ°€ ν¬ν•¨λœ 집합을 ν•˜λ‚˜μ˜ μ§‘ν•©μœΌλ‘œ ν•©μΉ˜λŠ” 연산이닀.
  • find μ—°μ‚° : νŠΉμ •ν•œ μ›μ†Œκ°€ μ†ν•œ 집합이 μ–΄λ–€ 집합인지 μ•Œλ €μ£ΌλŠ” 연산이닀.

μ„œλ‘œμ†Œ μ§‘ν•© 계산 μ•Œκ³ λ¦¬μ¦˜

  1. union 연산을 ν™•μΈν•˜λ©°, μ„œλ‘œ μ—°κ²°λœ 두 λ…Έλ“œ A, Bλ₯Ό ν™•μΈν•œλ‹€.
    1. A와 B의 루트 λ…Έλ“œλ₯Ό 각각 μ°ΎλŠ”λ‹€.
    2. A의 루트 λ…Έλ“œλ₯Ό B의 루트 λ…Έλ“œλ‘œ μ„€μ •ν•œλ‹€. (더 μž‘μ€ 루트 λ…Έλ“œλ₯Ό 가리킀도둝 ν•œλ‹€. )
  2. λͺ¨λ“  union 연산을 μ²˜λ¦¬ν•  λ•ŒκΉŒμ§€ 1의 과정을 λ°˜λ³΅ν•œλ‹€.

μ„œλ‘œμ†Œ μ§‘ν•© μ•Œκ³ λ¦¬μ¦˜ μ†ŒμŠ€μ½”λ“œ

# νŠΉμ • μ›μ†Œκ°€ μ†ν•œ 집합을 μ°ΎκΈ°
def find_parent(parent, x):
	# 루트 λ…Έλ“œκ°€ μ•„λ‹ˆλΌλ©΄, 루트 λ…Έλ“œλ₯Ό 찾을 λ•ŒκΉŒμ§€ μž¬κ·€μ μœΌλ‘œ 호좜
	if parent[x] != x:
		parent[x] = find_parent(parent, parent[x])
	return parent[x]

# 두 μ›μ†Œκ°€ μ†ν•œ 집합을 ν•©μΉ˜κΈ°
def union_parent(parent, a, b):
	a = find_parent(parent, a)
	b = find_parent(parent, a)
	if a < b:
		parent[b] = a
	else : 
		parent[a] = b

# λ…Έλ“œμ˜ κ°œμˆ˜μ™€ κ°„μ„ (union μ—°μ‚°)의 개수 μž…λ ₯λ°›κΈ°
v, e = map(int, input(),split())
parent = [0] * (v+1) # λΆ€λͺ¨ ν…Œμ΄λΈ” μ΄ˆκΈ°ν™”

# λΆ€λͺ¨ ν…Œμ΄λΈ”μƒμ—μ„œ, λΆ€λͺ¨λ₯Ό 자기 μžμ‹ μœΌλ‘œ μ΄ˆκΈ°ν™”
for i in range(1, v+1):
	parent[i] = i

# union 연산을 각각 μˆ˜ν–‰
for i in range(e):
	a,b = map(int, input().split())
	union_parent(parent, a, b)

# 각 μ›μ†Œκ°€ μ†ν•œ μ§‘ν•© 좜λ ₯
print("각 μ›μ†Œκ°€ μ†ν•œ μ§‘ν•© : ", end="")
for i in range(1, v+1):
	print(find_parent(parent, i), end=" ")

print()

# λΆ€λͺ¨ ν…Œμ΄λΈ” λ‚΄μš© 좜λ ₯
print("λΆ€λͺ¨ ν…Œμ΄λΈ”: " end="")
for i in range(1, v+1):
	print(parent[i], end=" ")

μ„œλ‘œμ†Œ μ§‘ν•© μ•Œκ³ λ¦¬μ¦˜μ˜ μ‹œκ°„ λ³΅μž‘λ„

λ…Έλ“œμ˜ κ°œμˆ˜κ°€ V개고 μ΅œλŒ€ V-1 개의 union μ—°μ‚°κ³Ό M개의 find연산이 κ°€λŠ₯ν•  λ•Œ 경둜 μ••μΆ• 방법을 μ μš©ν•œ μ‹œκ°„ λ³΅μž‘λ„λŠ” $O(V + M(1 + M(1 + log_{1-M/V}V))$ 이닀.

μ„œλ‘œμ†Œ 집합을 ν™œμš©ν•œ 사이클 νŒλ³„

μ„œλ‘œμ†Œ 집합은 λ‹€μ–‘ν•œ μ•Œκ³ λ¦¬μ¦˜μ— μ‚¬μš©ν•  수 μžˆλŠ”λ°, 특히 μ„œλ‘œμ†Œ 집합은 무방ν–₯ κ·Έλž˜ν”„ λ‚΄μ—μ„œμ˜ 사이클을 νŒλ³„ν•  λ•Œ μ‚¬μš©ν•  수 μžˆλ‹€. (λ°©ν–₯ κ·Έλž˜ν”„μ—μ„œμ˜ 사이클 μ—¬λΆ€λŠ” DFS둜 νŒλ³„)

  1. 각 간선을 ν™•μΈν•˜λ©° μ—°κ²°λœ 두 λ…Έλ“œμ˜ 루트 λ…Έλ“œλ₯Ό ν™•μΈν•œλ‹€.
    1. 루트 λ…Έλ“œκ°€ μ„œλ‘œ λ‹€λ₯΄λ‹€λ©΄ 두 λ…Έλ“œμ— λŒ€ν•˜μ—¬ union 연산을 μˆ˜ν–‰ν•œλ‹€.
    2. 루트 λ…Έλ“œκ°€ μ„œλ‘œ κ°™λ‹€λ©΄ 사이클이 λ°œμƒν•œ 것이닀.
  2. κ·Έλž˜ν”„μ— ν¬ν•¨λ˜μ–΄ μžˆλŠ” λͺ¨λ“  간선에 λŒ€ν•˜μ—¬ 1번 과정을 λ°˜λ³΅ν•œλ‹€.

μ‹ μž₯ 트리

μ‹ μž₯ 트리 (Spanning Tree)λž€ ν•˜λ‚˜μ˜ κ·Έλž˜ν”„κ°€ μžˆμ„ λ•Œ, λͺ¨λ“  λ…Έλ“œλ₯Ό ν¬ν•¨ν•˜λ©΄μ„œ 사이클이 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” λΆ€λΆ„ κ·Έλž˜ν”„λ₯Ό μ˜λ―Έν•œλ‹€. 이 λ•Œ, λͺ¨λ“  λ…Έλ“œκ°€ ν¬ν•¨λ˜μ–΄ μ„œλ‘œ μ—°κ²°λ˜λ©΄μ„œ 사이클이 μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 쑰건은 트리의 성립 쑰건이기도 ν•˜λ‹€.

image

크루슀칼 μ•Œκ³ λ¦¬μ¦˜

μ‹ μž₯ 트리 μ€‘μ—μ„œ μ΅œμ†Œ λΉ„μš©μœΌλ‘œ λ§Œλ“€ 수 μžˆλŠ” μ‹ μž₯ 트리λ₯Ό μ°ΎλŠ” μ•Œκ³ λ¦¬μ¦˜μ„ β€˜μ΅œμ†Œ μ‹ μž₯ 트리 μ•Œκ³ λ¦¬μ¦˜β€™μ΄λΌκ³  ν•œλ‹€. λŒ€ν‘œμ μΈ μ‹ μž₯ 트리 μ•Œκ³ λ¦¬μ¦˜μœΌλ‘œ 크루슀칼 μ•Œκ³ λ¦¬μ¦˜μ΄ μžˆλ‹€.

크루슀칼 μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•˜λ©΄ κ°€μž₯ 적인 λΉ„μš©μœΌλ‘œ λͺ¨λ“  λ…Έλ“œλ₯Ό μ—°κ²°ν•  수 μžˆλ‹€.

  1. κ°„μ„  데이터λ₯Ό λΉ„μš©μ— 따라 μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬ν•œλ‹€.
  2. 간선을 ν•˜λ‚˜μ”© ν™•μΈν•˜λ©° ν˜„μž¬μ˜ 간선이 사이클을 λ°œμƒμ‹œν‚€λŠ”μ§€ ν™•μΈν•œλ‹€.
    1. 사이클이 λ°œμƒν•˜μ§€ μ•ŠλŠ” 경우, μ΅œμ†Œ μ‹ μž₯ νŠΈλ¦¬μ— ν¬ν•¨μ‹œν‚¨λ‹€.
    2. 사이클을 λ°œμƒν•˜λŠ” 경우, μ΅œμ†Œ μ‹ μž₯ νŠΈλ¦¬μ— ν¬ν•¨μ‹œν‚€μ§€ μ•ŠλŠ”λ‹€.
  3. λͺ¨λ“  간선에 λŒ€ν•˜μ—¬ 2번 과정을 λ°˜λ³΅ν•œλ‹€.

크루슀칼 μ•Œκ³ λ¦¬μ¦˜ μ†ŒμŠ€μ½”λ“œ

def find_parent(parent, x):
    if parent[x] != x:
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

def union_parent(parent, a, b):
    a = find_parent(parent,a)
    b = find_parent(parent,b)
    if a<b:
        parent[b] = a
    else:
        parent[a] = b

v, e = map(int,input().split())
edges = [list(map(int, input().split())) for _ in range(e)]
parent = [i for i in range(v+1)]

edges.sort(key=lambda x:x[2])

result = 0
for edge in edges:
    a,b,cost = edge
    if find_parent(parent,a) != find_parent(parent,b):
        union_parent(parent,a,b)
        result+=cost

print(result)

크루슀칼 μ•Œκ³ λ¦¬μ¦˜μ˜ μ‹œκ°„λ³΅μž‘λ„

크루슀칼 μ•Œκ³ λ¦¬μ¦˜μ€ κ°„μ„ μ˜ κ°œμˆ˜κ°€ E일 λ•Œ, O(ElogE)의 μ‹œκ°„ λ³΅μž‘λ„λ₯Ό κ°€μ§„λ‹€.

μœ„μƒμ •λ ¬ μ•Œκ³ λ¦¬μ¦˜

μœ„μƒ μ •λ ¬ (Topology Sort)λŠ” μ •λ ¬ μ•Œκ³ λ¦¬μ¦˜μ˜ μΌμ’…μœΌλ‘œ, μˆœμ„œκ°€ μ •ν•΄μ Έ μžˆλŠ” 이련의 μž‘μ—…μ„ μˆœμ„œλŒ€λ‘œ μˆ˜ν–‰ν•΄μ•Ό ν•  λ–„ μ‚¬μš©ν•  수 μžˆλŠ” μ•Œκ³ λ¦¬μ¦˜μ΄λ‹€. 즉, λ°©ν–₯ κ·Έλž˜ν”„μ˜ λͺ¨λ“  λ…Έλ“œλ₯Ό λ°©ν–₯성에 거슀λ₯΄μ§€ μ•Šλ„λ‘ μˆœμ„œλŒ€λ‘œ λ‚˜μ—΄ν•˜λŠ” 것이닀.

μ§„μž…μ°¨μˆ˜(indegree) : νŠΉμ •ν•œ λ…Έλ“œλ‘œ λ“€μ–΄μ˜€λŠ” κ°„μ„ μ˜ κ°œμˆ˜μ΄λ‹€.

  1. μ§„μž…μ°¨μˆ˜κ°€ 0인 λ…Έλ“œλ₯Ό 큐에 λ„£λŠ”λ‹€.
  2. 큐가 빌 λ•ŒκΉŒμ§€ λ‹€μŒμ˜ 과정을 λ°˜λ³΅ν•œλ‹€.
    1. νμ—μ„œ μ›μ†Œλ₯Ό κΊΌλ‚΄ ν•΄λ‹Ή λ…Έλ“œμ—μ„œ μΆœλ°œν•˜λŠ” 간선을 κ·Έλž˜ν”„μ—μ„œ μ œκ±°ν•œλ‹€.
    2. μƒˆλ‘­κ²Œ μ§„μž…μ°¨μˆ˜κ°€ 0이된 λ…Έλ“œλ₯Ό 큐에 λ„£λŠ”λ‹€.
v, e = map(int, input().split())
indegree = [0] * (v + 1)
graph = [[] for i in range(v + 1)]
answer = [1] * (v + 1)

for _ in range(e):
    a, b = map(int, input().split())
    graph[a].append(b)
    indegree[b] += 1

def topology_sort():
		result = []
    q = deque()

    for i in range(1, v + 1):
        if indegree[i] == 0:
            q.append(i)

    while q:
        now = q.popleft()
        result.append(now)
        for i in graph[now]:
            indegree[i] -= 1
            if indegree[i] == 0:
                q.append(i)

topology_sort()
for i in result():
		print(i, end='')

μœ„μƒμ •λ ¬μ˜ μ‹œκ°„ λ³΅μž‘λ„

μ°¨λ‘€λŒ€λ‘œ λͺ¨λ“  λ…Έλ“œλ₯Ό ν™•μΈν•˜λ©΄μ„œ, ν•΄λ‹Ή λ…Έλ“œμ—μ„œ μΆœλ°œν•˜λŠ” 간선을 μ°¨λ‘€λŒ€λ‘œ μ œκ±°ν•΄μ•Ό ν•œλ‹€. λ”°λΌμ„œ μœ„μƒ μ •λ ¬μ˜ μ‹œκ°„ λ³΅μž‘λ„λŠ” O(V + E)이닀.

기좜 문제

[Baekjoon 1647] λ„μ‹œλΆ„ν• κ³„νš

import sys
input = sys.stdin.readline

# νŠΉμ • μ›μ†Œκ°€ μ†ν•œ 집합을 μ°ΎκΈ°
def find_parent(parent, x):
    if parent[x] != x: # 루트 λ…Έλ“œλ₯Ό 찾을 λ•ŒκΉŒμ§€
        parent[x] = find_parent(parent, parent[x])
    return parent[x]

# 두 μ›μ†Œκ°€ μ†ν•œ 집합을 ν•©μΉ˜κΈ°
def union_parent(parent, a, b):
    a = find_parent(parent,a)
    b = find_parent(parent,b)
    if a<b:
        parent[b] = a
    else:
        parent[a] = b

v, e = map(int, input().split())
parent = [0] * (v+1)

# λͺ¨λ“  간선을 담을 λ¦¬μŠ€νŠΈμ™€ μ΅œμ’… λΉ„μš©μ„ 담을 λ³€μˆ˜
edges = []
result = 0

for i in range(1, v+1):
    parent[i] = i
for _ in range(e):
    a,b, cost = map(int,input().split())
    edges.append((cost,a,b))

edges.sort() # λΉ„μš© 순으둜 μ •λ ¬
max_edge = 0 # μ΅œμ†Œ μ‹ μž₯ νŠΈλ¦¬μ— ν¬ν•¨λ˜λŠ” κ°„μ„  μ€‘μ—μ„œ κ°€μž₯ λΉ„μš©μ΄ 큰 κ°„μ„ 

for edge in edges:
    cost,a,b = edge
    # 사이클이 λ°œμƒν•˜μ§€ μ•ŠλŠ” 경우
    if find_parent(parent,a) != find_parent(parent,b):
        union_parent(parent,a,b)
        result += cost
        max_edge = cost

print(result - max_edge)

[Baekjoon 14567] μ„ μˆ˜κ³Όλͺ©

from collections import deque
import sys

input = sys.stdin.readline

v, e = map(int, input().split())
indegree = [0] * (v + 1)
graph = [[] for i in range(v + 1)]
answer = [1] * (v + 1)

for _ in range(e):
    a, b = map(int, input().split())
    graph[a].append(b)
    indegree[b] += 1

def topology_sort():
    q = deque()

    for i in range(1, v + 1):
        if indegree[i] == 0:
            q.append(i)

    while q:
        now = q.popleft()
        flag = False
        for i in graph[now]:
            indegree[i] -= 1
            if indegree[i] == 0:
                q.append(i)
                answer[i] = answer[now] + 1

topology_sort()
for x in answer[1:]:
    print(x, end=" ")

λŒ“κΈ€λ‚¨κΈ°κΈ°