Initialization)각 원소는 자기 자신을 부모로 설정 → 독립된 집합으로 시작합니다.
for (int i = 0; i < n; i++)
_parent[i] = i;
Find)특정 노드가 속한 집합의 대표 노드(루트)를 찾는 연산입니다.
int Find(int u) {
if (u == _parent[u])
return u;
return Find(_parent[u]); // 루트를 찾을 때까지 재귀 호출
}
Union or Merge)두 개의 집합을 하나로 합치는 연산입니다.
void Merge(int u, int v) {
u = Find(u); // u의 루트 찾기
v = Find(v); // v의 루트 찾기
if (u == v) return; // 이미 같은 집합이면 무시
_parent[u] = v; // u 집합이 v 집합 밑으로 들어감
}
Find 연산 시 경로를 압축해서 트리의 깊이를 줄입니다.
int Find(int u) {
if (u == _parent[u])
return u;
return _parent[u] = Find(_parent[u]); // 부모를 루트로 직접 설정
}
💡 효과: 이후의
Find는 O(1)에 가까운 속도로 처리됩니다.
병합할 때 트리의 높이가 낮은 집합을 높은 집합 밑으로 붙입니다.
void Merge(int u, int v) {
u = Find(u);
v = Find(v);
if (u == v) return;
if (_rank[u] > _rank[v]) // 랭크가 큰 쪽을 루트로
swap(u, v);
_parent[u] = v;
if (_rank[u] == _rank[v])
_rank[v]++;
}
📌 트리의 구조가 균형을 유지하게 되어 성능이 좋아집니다.
| 연산 | 최적화 X | 최적화 O (경로 압축 + 랭크) |
|---|---|---|
| Find | O(N) | O(α(N)) ≒ O(1) (아커만 함수) |
| Merge | O(N) | O(1) |
class NaiveDisjointSet {
public:
NaiveDisjointSet(int n) : _parent(n) {
for (int i = 0; i < n; i++)
_parent[i] = i;
}
int Find(int u) {
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;
}
private:
vector<int> _parent;
};
❌ 단점: 트리의 깊이가 깊어질 수 있음 → 성능 저하
class DisjointSet {
public:
DisjointSet(int n) : _parent(n), _rank(n, 1) {
for (int i = 0; i < n; i++)
_parent[i] = i;
}
int Find(int u) {
if (u == _parent[u]) return u;
return _parent[u] = Find(_parent[u]);
}
void Merge(int u, int v) {
u = Find(u);
v = Find(v);
if (u == v) return;
if (_rank[u] > _rank[v])
swap(u, v);
_parent[u] = v;
if (_rank[u] == _rank[v])
_rank[v]++;
}
private:
vector<int> _parent;
vector<int> _rank;
};
✅ 경로 압축 + 유니온 바이 랭크 최적화 적용 → 거의 O(1)
int main() {
DisjointSet teams(1000);
teams.Merge(10, 1);
cout << teams.Find(10) << " " << teams.Find(1) << endl;
teams.Merge(3, 2);
cout << teams.Find(3) << " " << teams.Find(2) << endl;
teams.Merge(1, 3);
cout << teams.Find(1) << " " << teams.Find(2) << " " << teams.Find(3) << endl;
return 0;
}
🧪 여러 집합을 병합하고 루트 노드가 어떻게 변하는지 확인할 수 있음
Find 연산의 이론적 시간 복잡도는 O(α(N)) (아커만 역함수)