가장 싸게 다 잇자냥! 최소 신장 트리
안녕, 나 코딩냥이다냥! 도시들을 도로로 다 잇고 싶은데 비용을 최소로 하고 싶다냥. 이걸 푸는 게 최소 신장 트리(MST)라냥.
그리고 만드는 방법인 크루스칼은, 16장 유니온 파인드를 거의 그대로 가져다 쓴다냥. 배운 걸 바로 써먹는 장이라냥.
오늘의 학습 목표
- 신장 트리와 최소 신장 트리가 뭔지 안다.
- 크루스칼로 MST 를 만든다.
- 연결이 안 되는 경우를 판별한다.
- MST 의 가장 비싼 간선을 활용한다.
- 이미 놓인 간선이 있을 때 응용한다.
1단계 · 신장 트리가 뭐냥 (8분)
신장 트리는 그래프의 모든 점을 사이클 없이 하나로 잇는 트리라냥.
간선을 딱 n-1개만 쓴다냥. 그중 비용 합이 가장 작은 것이 최소 신장
트리라냥.
비유로 이해하기 — 마을에 수도관 깔기
집 n채를 수도관으로 다 이어야 한다냥. 모든 집에 물이 가되, 관 놓는
비용은 최소로 하고 싶다냥.
고리(사이클)를 만들면 낭비라냥 — 이미 이어진 집을 또 잇는 거니까라냥.
그래서 사이클 없이, 딱 n-1개의 관으로, 가장 싸게 잇는 게 MST 라냥.
2단계 · 크루스칼 (13분)
방법은 놀랄 만큼 단순하다냥. 싼 간선부터 골라서, 사이클을 만들지 않으면 쓰고, 만들면 버린다냥. 그게 전부라냥.
- 모든 간선을 비용 오름차순으로 정렬한다
- 싼 것부터 하나씩 본다
- 두 끝이 다른 무리면 쓴다 (합친다)
- 같은 무리면 버린다 (사이클이 되니까)
n-1개를 쓰면 끝이라냥
edges.sort() # (비용, u, v) 순으로 정렬한다냥
parent = list(range(n + 1))
def find(x):
while parent[x] != x:
parent[x] = parent[parent[x]]
x = parent[x]
return x
total = 0
for w, u, v in edges:
ru, rv = find(u), find(v)
if ru != rv: # 다른 무리면 잇는다냥
parent[rv] = ru
total += w16장의 유니온 파인드가 그대로라냥.
"두 끝이 같은 무리냥?" 이 바로 16장의 사이클 판별이라냥. find(u) != find(v)
면 잇고, 같으면 버린다냥. 크루스칼은 정렬 + 유니온 파인드일 뿐이라냥.
정렬을 빼먹으면 최소가 아니다냥.
싼 간선부터 봐야 최소 비용이 된다냥. 정렬 없이 입력 순서대로 더하면 그냥 아무 신장 트리일 뿐, 최소가 아니라냥. 오늘 1번 문제가 이걸 잡는다냥.
3단계 · 연결이 안 될 때 (10분)
간선이 모자라면 하나로 못 잇는다냥. 섬처럼 따로 노는 무리가 남는 거라냥. 이걸 어떻게 알아채냥?
쓴 간선이 정확히 n-1개인지 세면 된다냥.
신장 트리는 간선이 딱 n-1개라냥. 크루스칼이 끝났는데 쓴 간선이 그보다
적으면, 모두를 잇지 못한 거라냥.
used = 0
# ... 간선을 쓸 때마다 used += 1
print(total if used == n - 1 else -1)오늘 2번 문제가 이거라냥. 연결이 안 되면 -1 을 출력한다냥.
"쓴 간선 수" 대신 "남은 무리 수"를 세도 된다냥. 크루스칼이 끝난 뒤 대표가 자기 자신인 노드가 하나뿐이면 다 이어진 거라냥.
4단계 · 가장 비싼 간선 활용 (12분)
크루스칼은 싼 것부터 더하니, MST 에 마지막으로 들어간 간선이 가장 비싼 간선이라냥. 이 사실이 은근히 쓸모가 많다냥.
MST 는 트리라, 간선 하나를 끊으면 정확히 두 조각으로 갈라진다냥. 그중 가장 비싼 간선을 끊으면, 두 그룹으로 나누면서 유지 비용을 가장 많이 아낀다냥.
그래서 "모두 잇되 두 그룹으로 나누는 최소 유지 비용" 은 —
MST 전체 비용 - MST 의 가장 비싼 간선
오늘 3번(가장 비싼 간선)과 4번(두 그룹 나누기)이 이 아이디어라냥.
mx = 0
for w, u, v in edges:
ru, rv = find(u), find(v)
if ru != rv:
parent[rv] = ru
total += w
mx = w # 마지막에 쓴 게 가장 비싼 간선이라냥간선을 오름차순으로 보니, MST 에 실제로 쓴 간선 중 마지막이 가장
비싸다냥. 그냥 mx = w 로 계속 덮어쓰면 자동으로 최댓값이 남는다냥.
5단계 · 이미 놓인 간선 (10분)
현실에서는 이미 놓인 도로가 있을 수 있다냥. 그건 공짜로 쓰면 된다냥. 이때도 16장의 아이디어를 쓴다냥.
이미 이어진 쌍들을 먼저 union 해둔다냥. 그러면 그 무리들은 이미 하나로 묶여 있어서, 크루스칼이 그 위에서 나머지만 최소로 잇는다냥.
for a, b in built: # 이미 놓인 것부터 합친다냥
parent[find(a)] = find(b)
# 이제 남은 후보 간선으로 크루스칼을 돌린다냥이미 놓인 간선으로 무리가 줄었으니, 필요한 추가 간선도 그만큼 줄어든다냥. "쓴 간선 수 == (남은 무리 수) - 1" 이면 다 이어진 거라냥.
오늘 5번 문제가 이거라냥. 16장의 union 을 크루스칼 앞에 붙이면 끝이라냥.
오늘 배운 내용 정리
먼저 스스로 답해보고, "정답 보기"를 눌러서 맞는지 확인해보자냥!
이제 직접 풀어볼 차례다냥
오늘 문제는 다섯 개라냥. 1번 MST 비용, 2번 연결 판별(-1), 3번 가장 비싼 간선, 4번 두 그룹 나누기, 5번 이미 놓인 간선이라냥.
전부 정렬 + 유니온 파인드 한 뼈대의 변주라냥. 크루스칼을 한 번 제대로 짜두면 나머지는 한두 줄 차이라냥. 가보자냥~
