<!doctype html>
7. 최소 비용 신장 트리(MST) 알고리즘
- 신장 트리 (Spanning Tree)
- 최소 비용 신장 트리 (MST)
- Kruskal MST 알고리즘
- Prim MST 알고리즘
1. 신장 트리 (Spanning Tree)
1.1. Spanning Tree
그래프 내의 모든 정점을 포함하는 트리
- Spanning Tree는 그래프의
최소 연결 부분 그래프
이다- 트리의 특수한 형태로 모든 정점들이 연결되어 있고 사이클은 존재하지 않는다
n
개의 정점을 정확히n-1
개의 간선으로 연결한다BFS, DFS
를 이용하여 그래프에서 Spanning Tree를 찾을 수 있다- 하나의 그래프에는 많은 Spanning Tree가 존재한다
2. 최소 비용 신장 트리 (MST, Minimum Spanning Tree)
2.1. MST
Spanning Tree
중에서 사용된 간선들의 가중치 합이 최소인 트리
- 각 간선의 가중치가 동일하지 않을 때, 단순히 가장 적은 간선을 사용한다고 최소 비용이 아니다
MST
는 간선의 가중치를 고려하여 최소 비용을 선택하는 알고리즘이다- 즉, 그래프에 있는 모든 정점을 선택하고 가장 적은 수의 간선과 비용으로 Spanning Tree 를 구성하는 것이다
3. Kruskal MST 알고리즘
3.1. 이론
탐욕적인 방법(Greedy)
을 이용하여 가중치 그래프의 모든 정점을 최소 비용으로 연결하는 방법
- MST 가
최소 비용의 간선으로 구성됨
,사이클을 포함하지 않음
의 조건에 근거하여 각 단계에서 사이클을 이루지 않는 최소 비용 간선을 선택한다 - 간선 선택을 기반으로 하는 알고리즘이다
- 이전 단계에서 만들어진 신장 트리와는 상관없이 무조건 최소 간선만을 선택하는 방법이다
3.2. 과정
-
그래프의 간선들을 가중치의 오름차순으로 정렬한다
-
정렬된 간선 리스트에서 순서대로 사이클을 형성하지 않는 간선을 선택한다
- 즉, 가장 낮은 가중치를 먼저 선택한다
- 사이클을 형성하는 간선을 제외한다
- 해당 간선을 현재의 MST의 집합에 추가한다
주의할점
간선을 추가할 때 사이클이 생성되는지를 체크해야함
- 새로운 간선이 이미 다른 경로에 의해 연결되어 있는 정점들을 연결할 때 사이클을 형성함
- 즉, 추가할 때 새로운 간선의 양끝 정점이 같은 집합에 속해 있으면 사이클이 형성됨
Union-Find 알고리즘을 이용하여 사이클 생성 여부를 확인할 수 있다!
시간 복잡도
Union-Find 알고리즘을 이용하면 Kruskal 알고리즘의 시간 복잡도는 간선들을 정렬하는 시간에 좌우된다. 즉, 간선 N개를 효율적으로 정렬한다면
O(N logN)
의 시간 복잡도를 갖는다.
3.3. 소스 코드 (C++)
// Krusal
typedef struct kruskal {
int from,to,val;
} Edge;
bool comp(Edge e1, Edge e2){
return e1.val < e2.val;
}
class Kruskal {
private:
int parent[MAX+1];
int res;
vector<Edge> edge;
public:
// 생성자
Kruskal(){
for(int i=1; i<=MAX; i++){
parent[i] = i;
}
go();
}
// Union
void merge(int u,int v){
u = find(u);
v = find(v);
if(u == v) return;
parent[u] = v;
}
// Find
int find(int u){
if(parent[u] == u) return u;
else return parent[u] = find(parent[u]);
}
// GO
void go(){
int V,E;
cin >> V >> E;
// 간선의 정보 입력
for(int i=0; i<E; i++){
Edge e;
cin >> e.from >> e.to >> e.val;
edge.push_back(e);
}
// 간선을 가중치로 오름차순 정렬
sort(edge.begin(),edge.end(),comp);
for(int i=0; i<E; i++){
// 두 정점이 사이클을 형성하지 않는다면
if(find(edge[i].from) != find(edge[i].to)){
// 가중치값 더해줌
res += edge[i].val;
// MST 형성
merge(edge[i].from, edge[i].to);
}
}
cout << res << "\n";
}
};
4. Prim MST 알고리즘
4.1. 이론
시작 정점에서부터 출발하여 신장트리 집합을 단계적으로 확장
해나가는 방법
4.2. 과정
-
시작 단계에서는 시작 정점만이 MST 에 포함된다
-
앞 단계에서 만들어진 MST 집합에 인접한 정점들 중에서 최소 간선으로 연결된 정점을 선택하여 트리를 확장한다
- 즉, 가장 낮은 가중치를 먼저 선택한다
-
위의 과정을 트리가
N-1
개의 간선을 가질 때까지 반복한다
시간 복잡도
1차 반복문이 정점의 수인 n만큼 반복하고, 2차 반복문이 n번 반복된다. 따라서 Prim 알고리즘은
O(N^2)
의 시간 복잡도를 갖는다. 만약, 우선순위 큐를 사용한다면O(E logV)
의 시간 복잡도를 갖는다.따라서 그래프 내의 간선의 숫자가 적은
희소 그래프(Sparse Graph)
의 경우Kruskal 알고리즘
이 유리하고, 그래프 내의 간선의 숫자가 많은밀집 그래프(Dense Graph)
의 경우Prim 알고리즘
이 유리하다.
4.3. 소스 코드 (C++)
// Prim
typedef pair<int, int> pii;
class Prim {
private:
bool visited[MAX+1];
vector<pii> adj[MAX];
public:
Prim(){
memset(visited, false, sizeof(visited));
go();
}
int prim(int start){
int res = 0;
visited[start] = true;
// 우선순위 큐
priority_queue<pii, vector<pii>, greater<pii>> pq;
// 시작정점의 간선들 삽입
for(int i=0; i<adj[start].size(); i++){
pq.push(pii(adj[start][i].first ,adj[start][i].second));
}
while(!pq.empty()){
int curr = pq.top().first;
int cost = pq.top().second;
pq.pop();
if(!visited[curr]){
visited[curr] = true;
res += cost;
for(int i=0; i<adj[curr].size(); i++){
pq.push(pii(adj[curr][i].first ,adj[curr][i].second));
}
}
}
return res;
}
void go(){
int V,E;
cin >> V >> E;
// 간선의 정보 입력
for(int i=0; i<E; i++){
int from, to, val;
cin >> from >> to >> val;
adj[from].push_back(pii(to,val));
adj[to].push_back(pii(from,val));
}
cout << prim(1) << "\n";
}
};
참고 자료
'CS > Algorithm' 카테고리의 다른 글
[AL] 9. 자료구조 (Trie) (0) | 2019.04.23 |
---|---|
[AL] 8. 최단 경로 알고리즘 (2) | 2019.04.20 |
[AL] 6. Union-Find 알고리즘 (0) | 2019.04.05 |
[AL] 5. 자료구조 (Heap, Hash) (0) | 2019.04.01 |
[AL] 4. 자료구조 (Graph) (0) | 2019.04.01 |