안녕하세요.
오늘은 유니온 파인드(Union-Find) 알고리즘에 대해 알아보고, 직접 구현해보는 시간을 갖도록 하겠습니다.
유니온 파인드(Union-Find)는 서로소 집합을 표현할 때 사용하는 그래프 알고리즘으로, 임의의 두 노드(원소)가 서로 같은 그래프(집합)에 속하는지 판별하는 알고리즘입니다.
이때 서로소 집합(Disjoint-Set)이란 공통 원소가 없는 "상호 배타적인" 부분집합들로 나눠진 원소들에 대한 정보를 표현하는 자료구조입니다.
유니온 파인드는 두 개의 그룹을 하나로 합치는 Union
연산과, 어떤 원소 x
가 속한 그룹을 알아내는 Find
연산으로 이루어집니다.
처음에 자기 번호를 그룹 번호로 갖는다고 해보겠습니다.
Union(1, 5) == 1번 원소가 속한 그룹에 5번 원소가 속한 그룹을 합치기.
-> 5번 원소의 그룹을 1로 바꾸기
Union(2, 3) == 2번 원소가 속한 그룹에 3번 원소가 속한 그룹 합치기.
-> 3번 원소의 그룹을 2로 바꾸기
이 상태에서, 1번 원소가 속한 그룹과 3번 원소가 속한 그룹을 Union하면 어떻게 될까요?
유니온 파인드 알고리즘의 핵심은 각각의 집합을 하나의 트리로 나타내는 것입니다.
실제로는 처음에 그룹 번호를 -1로 초기화 합니다. -1은 루트 노드임을 뜻합니다.
vector<int> p(11, -1);
int find(int x){
while(p[x] > 0){
x = p[x];
}
return x;
}
bool uni(int x, int y){
u = find(x);
v = find(y);
if(u == v) return false;
p[v] = u;
return true;
앞에서 구현했던 것처럼 직관적으로 union
을 하면, 위 그림과 같은 불균형한 트리 구조가 만들어질 수 있습니다. 이러한 구조는 Find
시 의 시간복잡도로 동작합니다.
최적화를 위한 두 가지 방법으로, union by rank와 경로압축이 있습니다. 이 둘을 적용해야만 효율적으로 Union-Find를 구현할 수 있게 됩니다.
bool uni(int x, int y){
u = find(x);
v = find(y);
if(u == v) return false;
if(p[v] < p[u]) { // v의 랭크가 더 큰 경우(음수니까)
swap(u, v)
}
// 이제 랭크는 u >= v 임
if(p[u] == p[v]) {
p[u]--; // 랭크가 같으면 붙였을 때 랭크가 1 증가함
}
p[v] = u; // v를 u의 자식으로 만들기
return true;
vector<int> p(11, -1);
int find(int x){
if(p[x] < 0)
return x;
return p[x] = find(p[x]);
내용 | 시간복잡도 |
---|---|
최적화1 - Union by rank | |
최적화2 - 경로 압축 | Amortized |
최적화1 + 최적화2 | Amortized |
Union-Find는 서로소 집합으로 나타낸 자료구조에서, 각 집합을 합치고 그 집합을 알아내는 알고리즘입니다. 직관적으로 구현했을 때 최악의 경우 시간복잡도 으로 동작하는데, 두 가지 최적화 기법 union by rank와 경로 압축을 적용하면 거의 만에 동작할 수 있습니다.