💡Summary & Idea
costs
에는 A섬
, B섬
, A섬과 B섬 사이의 거리
의 쌍이 주어진다n
개의 섬을 모두 연결할 수 있도록 한다여기서 비용을 기준으로 정렬한 뒤 차례대로 연결하면 그게 모든 섬을 연결한다고 보장할 수 있을까?
예를들어 costs= {{0,1,1},{0,2,1}, {1,2,2}, {3,4,3}}
과 같은 예시가 있다고 했을 때, 마지막 값(비용)을 기준으로 정렬을 했다고 했을 때도 순서는 같다. 하지만 여기서 결과적으로 2-0-1-2
섬은 이렇게 연결되어 있고 3-4
섬은 연결되어 있지 않다.
따라서 여기서 고려해야 할 점은 모든 섬이 한 집합에 포함되느냐 (하지만 문제에서 연결되지 않는 경우는 없다고 했다), 연결된 섬이 사이클을 이루지 않느냐 (이미 섬집합에 포함되어 있느냐)이다.
여기서 추가하려는 섬이 이미 섬집합에 포함되어 있는지 아닌지 어떻게 알 수 있을까? 깊이 우선 탐색하며 모두 탐색해보아야 할까? 아니다. 이때 필요한 개념이 Kruskal Algorithm
이다.
📜Kruskal Minimum Spanning Tree Algorithm
- 모든 간선을 가중치의 오름차순으로 정렬
- 스패닝 트리에 하나씩 추가
- 가중치가 작다고 무조건 간선을 트리에 더하는 것이 아니라, 사이클이 생기지 않는 경우에만 트리에 더해준다
- 상호 배타적 집합 자료구조를 사용하여 양 끝 점이 같은 컴포넌트에 속해 있는지 확인하고 그렇지 않다면 두 컴포넌트를 합친다.
struct DisjointSet { vector<int> parent; DisjointSet(int n) : parent(n) { for (int i = 0; i < n; i++) parent[i] = i; } int find(int u) const { if (u == parent[u]) return u; return find(parent[u]); } void merge(int u, int v) { u = find(u); v = find(v); if (u == v) return; parent[u] = v; } }; const int MAX_V = 100; int V; //num of vertex //pair<jointed vertex number, cost> vector<pair<int, int>> adj[MAX_V]; int kruskal(vector<pair<int, int>>& selected) { int ret = 0; selected.clear(); vector<pair<int, pair<int, int>>> edges; for (int u = 0; u < V; ++u) { for (int i = 0; i < adj[u].size(); ++i) { int v = adj[u][i].first; int cost = adj[u][i].second; edges.push_back(make_pair(cost, make_pair(u, v))); } } //order by cost sort(edges.begin(), edges.end()); DisjointSet sets(V); for (int i = 0; i < edges.size(); i++) { int cost = edges[i].first; int u = edges[i].second.first; int v = edges[i].second.second; if (sets.find(u) == sets.find(v)) continue; sets.merge(u,v); selected.push_back(make_pair(u, v)); ret += cost; } return ret; }
find
함수에서는 현재 노드의 부모 노드를 찾을 때까지 반복한다.- 그 후 합치려는 두 노드의 부모 노드가 같을 경우, 이 두 노드는 같은 집합에 있다는 뜻이므로 연산을 하지 않는다
- 그렇지 않은 경우 한 노드(u)의 부모 노드를 (v)로 만들어줌으로써 두 노드를 합친다
이 문제에서 이 알고리즘을 적용해 문제를 해결해본다.
1📚 vector<int> parents
- 각 섬의 부모 섬을 저장하는 벡터를 만든다
- 처음에는 모든 섬이 자신을 부모 섬으로 가진다
for (int i=0; i<n; i++) parents[i]=i;
- 합치려는 두 섬이 같은 루트 섬을 가지고 있다면 합치지 않는다
- 합칠 때는 부모 섬의 값이 더 작은 값을 부모 섬으로 가진다 (반대가 되어도 상관 없다. 그냥 규칙을 만들어주기 위함이다)
bool merge (int u, int v) { int a=find(u); int b=find(v); if (a==b) return false; if (a>b) parents[a]=b; else parents[b]=a; return true; }
📚2. Solution
costs
를 마지막 값, 비용을 기준으로 오름차순 정렬한다bool compare (const vector<int>&a, const vector<int>&b) { if (a[2]<b[2]) return true; return false; }
sort(costs.begin(), costs.end(), compare);
- 그 후 두
costs[i][0]
,costs[i][1]
두 섬을 합칠 수 있다면costs[i][2]
값을 계속 더해준다
Source Code
#include <string> #include <vector> #include <algorithm> using namespace std; const int MAX=100; vector<int> parents(MAX); bool compare (const vector<int>&a, const vector<int>&b) { if (a[2]<b[2]) return true; return false; } int find (int u) { if (u==parents[u]) return u; return find(parents[u]); } bool merge (int u, int v) { int a=find(u); int b=find(v); if (a==b) return false; if (a>b) parents[a]=b; else parents[b]=a; return true; } int solution(int n, vector<vector<int>> costs) { int answer = 0; for (int i=0; i<n; i++) parents[i]=i; //order by cost sort(costs.begin(), costs.end(), compare); for (int i=0; i<costs.size(); i++) { int u=costs[i][0]; int v=costs[i][1]; int cost=costs[i][2]; if (merge(u,v)) answer+=cost; } return answer; }