신장 트리
- 그래프에서 모든 노드를 포함하면서 사이클이 존재하지 않는 부분 그래프를 의미
- 트리의 조건 : 모든 노드가 포함되어 서로 연결되면서 사이클이 존재하지 않아야 함
사이클(cycle)이란 ?
- 그래프에서 시작 노드에서 출발해서 다른 노드를 거쳐 다시 시작 노드로 돌아갈 수 있는 경로가 존재한다면, 이는 하나의 사이클이 됩니다.
최소 신장 트리
최소한의 비용으로 신장 트리를 찾기 위한 방법
1, 2, 3번 지역에 도로를 2개 놓아 전체 지역을 연결시키도록 하는 경우에서는 1->2, 2->3을 연결하는 것이 가장 저렴하게 연결할 수 있는 방법입니다.
즉, 최소 신장 트리는 말 그대로 최소한의 비용(가중치의 합이 최소==간선의 비용)으로 신장 트리를 찾아내는 알고리즘이고, 그리디 알고리즘으로 분류됩니다.
예시로는 크루스칼 알고리즘이 있습니다.
크루스칼 알고리즘(임의의 간선 선택, 간선이 적은 경우) 과정
- 간선 데이터를 비용에 따라 오름차순 정렬
- 간선을 하나씩 확인하며 현재의 간선이 사이클을 발생시키는지 확인합니다.
- 사이클이 발생하지 않는 경우, 최소 신장 트리에 포함시킵니다.
- 사이클이 발생하는 경우, 최소 신장 트리에 포함시키지 않습니다.
- 모든 간선에 대해 2번의 과정 반복
- 간선의 개수가 E개 일때, O(ElogE)의 시간 복잡도를 가집니다.
- 가장 많은 시간을 요구하는 곳은 간선을 정렬하는 부분입니다. (E개의 데이터를 정렬하기 위한 시간 복잡도 : O(ElogE)
- 간선이 적은 경우에 사용하기 적합한 알고리즘입니다.
<코드>
#include <iostream>
#include <vector>
using namespace std;
int V, E; // 노드의 개수(V), 간선(union 연산)의 개수(E)
int parent[100001]; // 부모 테이블 초기화
vector<pair<int, pair<int,int>>> edges;
// 모든 간선을 담을 ㄷ리스트와, 최종 비용을 담을 변수
int result = 0;
// 특정 원소가 속한 집합을 찾기
int findParent(int x)
{
// 루트 노드가 아니라면, 루트 노드를 찾을 때 까지 재귀적으로 호출
if(x == parent[x]) return x;
// 경로 압축
return parent[x] = findParent(parent[x]);
}
// 두 원소가 속한 집합을 합치기
void unionParent(int a, int b)
{
a = findParent(a);
b = findParent(b);
if(a < b) parent[b] = a;
else parent[a] = b;
}
int main()
{
cin>>V>>E;
// 부모 테이블 초기화
for(int i = 1; i <= V; i++){
parent[i] = i;
}
for(int i=0; i<E; i++)
{
int a, b, cost;
cin >> a >> b >> cost;
// 비용순으로 정렬하기 위해 튜플의 첫 번째 원소를 비용으로 설정
edges.push_back({cost,{a,b}});
}
// 간선을 비용순으로 정렬
sort(edges.begin(),edges.end());
for(int i=0;i<edges.size();i++)
{
int cost = edges[i].first;
int a = edges[i].second.first;
int b = edges[i].second.second;
// 사이클이 발생하지 않는 경우에만 집합에 포함
if(findParent(a) != findParent(b))
{
unionParent(a, b);
result += cost;
}
}
cout<<result<<'\n';
}
프림 알고리즘(임의의 정점 선택, 간선이 많은 경우)
- 그래프에서의 임의의 정점 선택
- 정점으로부터 가중치가 낮은 간선을 선택하면서 정점을 추가해나가는 알고리즘
- 최소 신장 트리를 찾는 알고리즘
- G(그래프)(V(노드), E(간선)), 평균 시간복잡도 O(ElogV), O(V^2, 최악경우)
- 간선이 많은 경우에 사용하기 적합한 알고리즘입니다.
<코드>
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
using namespace std;
vector<pair<int, int>> G[10001];
priority_queue<pair<int, int>, vector<pair<int,int>>,greater<pair<int,int>>> pq;
int V, E;
int a,b,c;
long long ans;
bool visited[10001];
int main()
{
cin>>V>>E;
for(int i=0;i<E;i++)
{
cin>>a>>b>>c;
G[a].push_back(make_pair(b,c));
G[b].push_back(make_pair(a,c));
}
pq.push(make_pair(0,1));
while(!pq.empty())
{
int w = pq.top().first;
int node =pq.top().second;
pq.pop();
if(visited[node]) continue;
visited[node] = true;
ans += w;
for (int i = 0; i < G[node].size(); i++) { //현재 정점에서 이동 할 수 있는 방문하지 않은 정점 푸쉬
pq.push(make_pair(G[node][i].second, G[node][i].first));
}
}
cout<<ans<<'\n';
}
<비슷한 유형의 문제>
https://school.programmers.co.kr/learn/courses/30/lessons/42861
<참고 자료>
https://www.youtube.com/watch?v=Gj7s-Nrt1xE
'PS > 그리디 알고리즘[Greedy]' 카테고리의 다른 글
벨만-포드(bellman-ford) 알고리즘 (0) | 2023.04.22 |
---|---|
다익스트라(Dijkstra) 최단 경로 알고리즘 (0) | 2023.03.02 |
백준 2138 전구와 스위치(c++) (1) | 2023.01.31 |
백준 1080 행렬(c++) (0) | 2023.01.31 |
백준 1173 운동(c++) (1) | 2022.10.06 |