IT 성장기 (교육이수)/알고리즘 문제풀이
백준 : 1197 최소 스패닝 트리
eezy
2025. 3. 29. 02:49
Kruskal 은 “ 간선 중심 “ 알고리즘이다
- 가장 작은 가중치의 간선부터 하나씩 선택하며 사이클이 생기지 않도록 주의해서 신장 트리를 만들어간다
- 이때 가장 중요한 건 간선 정렬과 Union-Find 자료구조를 통한 사이클 판별이다
Prim 은 “ 정점 중싱 “ 알고리즘이다
- 하나의 정점에서 시작해서, 아직 방문하지 않은 정점 중에서 가장 가까운 정점으로 확장해나간다
- 이때는 Min-Heap (우선순위 큐)을 활용해서 매번 가장 짧은 간선을 빠르게 선택한다
두 알고리즘 모두 MST를 구하지만, 그 접근 방식이 다르기 떄문에 구현을 해보면서 내가 어떤 자료구조를 다루고 있는지, 문제가 어떤 그래프 구조를 가졌는지에 따라 알고리즘 선택이 달라질 수 있음을 알 수 있었다.
결론 도출
- 결과는 같더라도, 과정은 전혀 다르다
- → 같은 MST 문제라고 해도, 사용하는 알고리즘과 자료구조는 다를 수 있다.
- 그래프 구조에 따라 선택이 달라진다
- → 간선이 많은 Dense 그래프라면 Prim, 간선이 적은 Sparse 그래프라면 Kruskal이 유리하다.
- 자료구조와 연결 방식이 진짜 중요하다
- → Kruskal의 Union-Find, Prim의 힙 구조를 직접 다뤄보면서 내부 흐름을 이해하게 되었다.
최종 코드
Kruskal 방식으로 풀이
import sys
sys.setrecursionlimit(10**6)
# a가 루트가 아니라면 재귀적으로 루트를 찾아서 parent를 루트로 갱신하고, 루트를 반환
def find(a):
if a!= parent[a]:
parent[a] = find(parent[a])
return parent[a]
def union(a, b):
rootA = find(a) #각각 루트를 찾음
rootB = find(b)
if rootA != rootB : #루트가 다른 경우 -> 다른 집합
parent[rootB] = rootA #한쪽 루트를 다른 쪽에 붙인다 (하나의 집합으로 합친다)
input = sys.stdin.readline
V, E = map(int, input().split())
tree = []
for _ in range(E):
tree.append(list(map(int, input().split())))
tree.sort(key=lambda x: x[2]) #간선들을 가중치 기준으로 오름차순 정렬
parent = list(range(V+1)) #Union-Find용 부모 배열 초기화 (자기 자신이 부모)
ans = 0
for a, b, c in tree:
if find(a) != find(b): #a와 b가 같은 집합에 속하지 않는다면 (사이클 아님)
union(a, b) #두 정점을 연결 (집합 병합)
ans += c #가중치를 누적 증가
print(ans)
Prim을 이용한 풀이
import sys
import heapq
from collections import defaultdict
sys.setrecursionlimit(10**6)
def prim(graph, start, V):
visited = [False]*(V+1) #정점 번호가 1부터 시작으로 V+1 크기로 초기화
min_heap = [(0, start)] #(가중치, 정점)
total_weight = 0 #MST의 총 가중치 누적
while min_heap:
weight, u = heapq.heappop(min_heap) #최소 가중치 간선 꺼내기
if visited[u]: #MST 포함된 정점은 무시
continue
visited[u] = True #MST에 현재 정점 포함
total_weight += weight #해당 간선 가중치 누적
#현재 정점과 연결된 인접 정점 확인
for (v, cost) in graph[u]:
if not visited[v]: #아직 MST에 포함되지 않은 정점만
heapq.heappush(min_heap, (cost, v)) #다음 후보 간선 추가
return total_weight #MST 완성 -> 총 가중치 변환
input = sys.stdin.readline
V, E = map(int, input().split())
graph = defaultdict(list)
for _ in range(E):
a, b, c = map(int, input().split())
graph[a].append((b, c)) #a -> b 간선 추가
graph[b].append((a, c)) #b -> a 간선 추가 (무방향 그래프라서 양방향)
#프림 알고리즘 실행 (정점 1에서 시작)
print(prim(graph, start=1, V=V))
크루스칼은 간선 리스트 (edge list)로 데이터를 받아서 풀이 (리스트 이용)
프림은 인접 리스트로 데이터를 받아서 풀이 (dictionary 이용)