Search
moon
sun
🏫

[알고리즘] 7. 그래프 (2) - 최소신장트리/크루스칼/프림

Tags
CS
Algorithm
Last edited time
2025/01/04 14:01
2 more properties
Search
🏫
[알고리즘] 14. 탐색 알고리즘 요약
CS
Algorithm
2025/01/12 5:37
🏫
[알고리즘] 14. 탐색 알고리즘 요약
CS
Algorithm
2025/01/12 5:37

1. 최소 신장 트리 (MST)

1.1. 신장 트리

•
가중 무방향 그래프에서 모든 정점을 포함하는 트리

1.2. 최소 신장 트리

•
정의
◦
간선 (u, v)마다 가중치 w(u, v)를 가진 연결된 무방향 그래프 G=(V, E)에 대해서 다음을 만족하는 트리 G’=(V, E’)
◦
신장 트리 중에서 가중치의 합이 가장 작은 것
•
최소 신장 트리 알고리즘
◦
모든 간선 중에서 정점을 모두 연결하면서 가중치의 합을 가장 작게 만드는 (n-1)개의 간선을 고르는 과정
Greedy_MST ( G ) { T ← Ø; // 최소 신장 트리의 간선 집합 while ( T가 신장 트리를 만들지 않았음 ) { // 최선의 간선: 사이클을 형성하지 않으며 최소한의 가중치를 갖는 간선 최선의 간선 (u, v) 선택; T ← T ∪ { (u, v) }; } return (T); }
C
복사
◦
욕심쟁이 방법 적용
▪
크루스칼, 프림

2. 크루스칼 알고리즘

2.1. 개념

•
정의
◦
최소 신장 트리를 생성하는 알고리즘으로, 가중 무방향 그래프의 간선 중에서 가중치가 가장 작은 간선부터 하나씩 추가.
◦
사이클을 형성하지 않는 선에서 간선을 추가하여, 최소 비용의 신장 트리를 생성
•
사이클 형성 여부의 판단
◦
간선 (u, v)의 두 정점 u, v가 서로 다른 연결 성분에 속하면 사이클이 발생하지 않음
•
동작 원리
1.
그래프의 모든 간선을 가중치 오름차순으로 정렬
2.
가중치가 가장 작은 간선을 선택
3.
선택한 간선이 사이클을 형성하지 않는다면 트리에 추가
4.
트리에 모든 간선이 추가될 때까지 반복
•
예시 1)
•
예시 2)

2.2. 예시 코드

Kruskal ( G ) { T = Ø; for ( G의 각 정점 v에 대해 ) 정점 v로 구성된 연결 성분 초기화; 가중치가 증가하는 순으로 모든 간선을 정렬; for ( 가중치가 가장 작은 간선부터 모든 간선 (u, v)∈E에 대해서 ) if ( u와 v가 서로 다른 연결 성분에 속하면 ) { // 사이클을 형성하지 않으면 T = T ∪ { (u, v) }; u가 속한 연결 성분과 v가 속한 연결 성분을 합침; } else 간선 (u, v)를 버림; return (T); }
C
복사
# Union-Find (Disjoint Set) 자료구조 구현 class UnionFind: def __init__(self, n): # 초기에는 자기 자신이 루트 self.parent = list(range(n)) # (선택사항) 트리 높이를 최소화하기 위한 rank 배열 self.rank = [0] * n def find(self, x): # x의 루트를 찾는 함수 (경로 압축(Path Compression) 기법 사용) if self.parent[x] != x: self.parent[x] = self.find(self.parent[x]) return self.parent[x] def union(self, a, b): # a와 b가 속한 집합을 합치는 함수 rootA = self.find(a) rootB = self.find(b) if rootA != rootB: # rank가 낮은 쪽을 rank가 높은 쪽에 붙인다. if self.rank[rootA] < self.rank[rootB]: rootA, rootB = rootB, rootA self.parent[rootB] = rootA if self.rank[rootA] == self.rank[rootB]: self.rank[rootA] += 1 return True return False def kruskal(nodes, edges): """ nodes: 정점 목록 (예: [0, 1, 2, 3] 또는 range(n)) edges: (u, v, w)의 튜플로 이루어진 리스트 -> u, v는 정점, w는 가중치 return: (mst_edges, total_weight) """ # 1. 간선을 가중치 기준으로 오름차순 정렬 edges.sort(key=lambda x: x[2]) # 2. Union-Find 구조 초기화 uf = UnionFind(len(nodes)) mst_edges = [] total_weight = 0 # 3. 가중치가 작은 간선부터 확인하여, 사이클이 생기지 않으면 MST에 추가 for u, v, w in edges: if uf.union(u, v): # 두 정점이 합쳐지면(= 사이클이 없다면) mst_edges.append((u, v, w)) total_weight += w return mst_edges, total_weight if __name__ == "__main__": # 예시: 정점이 0~5번, 6개 정점이라 하자 nodes = [0, 1, 2, 3, 4, 5] # (u, v, w) 형태의 간선들 edges = [ (0, 1, 4), # 정점 0과 1이 가중치 4로 연결 (0, 2, 4), (1, 2, 2), (2, 3, 3), (2, 5, 2), (2, 4, 4), (3, 4, 3), (4, 5, 3), ] mst, total = kruskal(nodes, edges) print("MST에 포함된 간선:", mst) print("MST의 총 가중치:", total)
Python
복사

2.3. 성능

•
시간 복잡도: O(Elog E)

3. 프림 알고리즘

3.1. 개념

•
정의
◦
임의의 한 정점에서 시작해서 연결된 정점을 하나씩 선택해 나가는 방법
•
동작 원리
1.
임의의 정점을 선택하여 시작
2.
어떤 순간에 이미 선택된 정점의 집합 S와 선택되지 않은 정점 집합 V-S 를 잇는 간선 중에서 가중치가 가장 작은 간선을 선택
3.
간선이 연결하는 정점을 S에 추가
4.
S = V가 될 때까지 반복
•
예시

3.2. 예시 코드

Prim ( G ) { T = ∅; S = { 1 }; // 임의의 정점(예, 여기서는 1)으로 초기화 while ( S != V ) { u ∈ S, v ∈ V-S인 것 중 가중치가 최소인 간선 (u, v) 선택; T = T ∪ {(u, v)}; S = S ∪ { v }; } return ( T ); }
C
복사
import heapq def prim(graph, start=1): """ graph: 딕셔너리 형태의 인접 리스트 예) graph = { 1: [(가중치, 2), (가중치, 3), ...], 2: [(가중치, 1), (가중치, 4), ...], ... } start: 초기 정점(기본값 1) return: (mst_edges, total_weight) mst_edges: MST에 포함된 (u, v, cost) 튜플의 리스트 total_weight: MST 전체 가중치 """ visited = set([start]) # 집합 S를 나타냄 (초기에는 start만 포함) edges = [] # (가중치, u, v) 형태로 우선순위 큐에 저장 mst_edges = [] # MST에 포함되는 간선들 total_weight = 0 # 시작 정점(start)와 인접한 모든 간선을 우선순위 큐에 삽입 for cost, nxt in graph[start]: heapq.heappush(edges, (cost, start, nxt)) # 모든 정점이 방문될 때까지 반복 while len(visited) < len(graph): # 가장 가중치가 작은 간선을 추출 cost, u, v = heapq.heappop(edges) # 이미 방문한 정점이라면 스킵(사이클 방지) if v in visited: continue # 새로 방문할 정점 v를 MST에 포함 visited.add(v) mst_edges.append((u, v, cost)) total_weight += cost # 새로 방문한 정점 v와 연결된 간선을 우선순위 큐에 추가 for nxt_cost, nxt_node in graph[v]: if nxt_node not in visited: heapq.heappush(edges, (nxt_cost, v, nxt_node)) return mst_edges, total_weight if __name__ == "__main__": # 예시 그래프 (정점: 1 ~ 5) # 각 정점에 대해 (간선 가중치, 연결된 정점) 형태의 리스트 graph = { 1: [(4, 2), (3, 3), (2, 4)], 2: [(4, 1), (1, 3), (5, 5)], 3: [(3, 1), (1, 2), (2, 5)], 4: [(2, 1), (7, 5)], 5: [(5, 2), (2, 3), (7, 4)] } mst, total = prim(graph, start=1) print("MST에 포함된 간선:", mst) # (u, v, cost) 형태의 리스트 print("MST 전체 가중치:", total)
Python
복사

3.3. 성능

•
시간 복잡도
◦
인접 행렬의 경우 O(|V|^2)
◦
인접 리스트 + 힙을 사용한 경우 (우선순위 큐 활용) O((|V| + |E|) log |V|)