그래프내 모든 정점을 포함하는 트리
그래프의 최소 연결 부분 그래프
Spanning Tree를 이루는 간선들의 가중치 합이 최소인 트리
가중치의 합이 동일하지 않을 때 적은 수의 간선을 사용한다고 최소 비용이 적어지는 것은 아니다. 가중치를 고려한 Spanning Tree를 고르는 방식.
그리디(Greedy)한 방법으로 모든 노드를 최소비용으로 연결하는 방법을 찾는 알고리즘.
💡 **Greedy?**동작 과정
주의할 점
최소 비용의 간선을 선택할 때마다 사이클이 만들어지는지 확인하는 과정이 필요하다. 추가할 간선의 양 끝 노드가 현재 만들고 있는 Spanning Tree에 포함되어 있는지 확인하는 방식을 통해서 체크가 가능하다.
이때 union-find 알고리즘
을 활용하여 두 노드가 같은 집합에 속하는지 확인할 수 있다.
시간 복잡도
앞서 union-find 알고리즘을 이용하면 Kruscal 알고리즘의 시간 복잡도는 가중치가 있는 간선을 정리하는 시간에 영향을 많이 받는다. 퀵정렬을 통해 간선 정렬을 하는 Kruscal알고리즘인 경우 시간 복잡도는 O(e * loge)
로 정의할 수 있다.
백준 1197번 - 최소 스패닝 트리 (with. Kruscal)
import sys
import heapq
input = sys.stdin.readline
V, E = map(int, input().split())
edges = [ list(map(int, input().split())) for _ in range(E) ]
parent = [ x for x in range( V + 1 ) ]
flag = [ False for _ in range( V + 1 ) ]
wtf = []
# adjedctive list
adj_list = [ [] for _ in range( V + 1 )]
for e in edges:
a, b, c = e
adj_list[a].append([b, c])
adj_list[b].append([a, c])
# Kruscal Algorithm
sorted_edges = sorted(edges, key = lambda x : x[-1])
cost = 0
def find_parent(parent, x):
if parent[x] != x:
parent[x] = find_parent(parent, parent[x])
return parent[x]
for e in sorted_edges:
A, B, C = e
p_a = find_parent(parent, A)
p_b = find_parent(parent, B)
if p_a == p_b:
continue
else:
if p_a < p_b:
parent[p_a] = p_b
else:
parent[p_b] = p_a
cost += C
print(cost)
참고: https://techblog-history-younghunjo1.tistory.com/262#google_vignette
시작 정점에서 출발해서 Spanning Tree의 집합을 확장해나가는 방법
동작 과정
시간 복잡도
정점의 수 만큼 반복하고, 정점마다 간선에 대해서 반복하기 때문에 시간 복잡도는 O(n^2)
라고 볼 수 있다.
백준 1197번 - 최소 스패닝 트리 (with. Prim)
import sys
import heapq
input = sys.stdin.readline
V, E = map(int, input().split())
edges = [ list(map(int, input().split())) for _ in range(E) ]
parent = [ x for x in range( 1, V + 1 ) ]
flag = [ False for _ in range( V + 1 ) ]
wtf = []
# adjedctive list
adj_list = [ [] for _ in range( V + 1 )]
for e in edges:
a, b, c = e
adj_list[a].append([b, c])
adj_list[b].append([a, c])
# prim algorithm
q = [(0, 1, 1)]
total_cost = 0
while q:
cost, node, prev = heapq.heappop(q)
if flag[node] is True:
continue
wtf.append([cost, node, prev])
flag[node] = True
total_cost += cost
for dst, weight in adj_list[node]:
if flag[dst] is False:
heapq.heappush(q, (weight, dst, node))
print(total_cost)
참고: https://gmlwjd9405.github.io/2018/08/30/algorithm-prim-mst.html
서로소 집합이란 교집합이 없는 다른 집합을 의미한다. 따라서 서로소 집합 자료구조는 서로소 부분 집합으로 나누어진 원소들의 데이터를 처리하기 위한 자료구조이다. 서로소 집합 자료구조는 합집합 연산인 union
과 찾기 연산인 find
연산을 사용할 수 있다.
동작과정
def init_parent(array = [], v):
parent = [ 0 ] * (v + 1)
for i in array:
parent[i] = i
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):
p1 = union_parent(parent, a)
p2 = union_parent(parent, b)
if p1 > p2:
parent[p1] = p2
else:
parent[p2] = p1
이 서로소 집합 알고리즘을 통해서 크루스칼 알고리즘의 조건 중 하나인 ‘Spanning Tree’ 내부에 Cycle이 없어야한다는 점을 검증할 수 있다.
cycle = False
for _ range(e):
a, b = map(int, input().split()) # 간선 정보를 받는다.
if find_parent(parent, a) == find_parent(parent,b): # 두 노드의 부모노드를 비교
cycle = True # 같을 경우 순환하는 상태이다.
break
else:
union_parent(parent, a, b)
참고: https://gmlwjd9405.github.io/2018/08/31/algorithm-union-find.html