참고한 것들
Disjoint Set
- Disjoint set (DSU) 은 말 그대로 전체를 겹치지 않는 부분집합으로 나눈 자료구조이다. 따라서 다음의 두 조건을 만족해야 한다:
- 모든 부분집합의 합집합은 전체집합
- 어떤 두 부분집합도 교집합을 가지지 않음
- 보통 부분집합을 합치는
unionoperation 과 부분집합을 찾는findoperation 을 지원한다.
Tree 를 이용한 구현
- 보통 이 DSU 를 구현할 때는 tree 를 사용한다.
- tree 의 각 root 가 하나의 부분집합이 되는 셈.
초기화
- 두 개의 vector 를 사용한다:
parent와height.parent는 indexi의 부모 index 를 저장한다. 처음에는 모두 자기자신이 되도록parent[i] = i로 초기화한다.height는 본인 아래에 달려 있는 sub-tree 의 높이를 의미한다. 처음에는 모두 자기자신밖에 없기 때문에 1 (혹은 0 - 상관없음) 로 초기화한다.
- 이
height는 사용하지 않아도 상관없으나, 최적화를 위한 것이다.- 당연히 tree 라는 것이 본디 height 가 낮을수록 빠르기 때문에 각 tree 의 height 를 낮게 유지하기 위한 목적인 것.
- 만약 이
height가 없다면 최악의 경우 linked list 가 돼버린다.
find
- Tree root 가 부분집합의 abstract 이므로, 어떤 원소가 속한 부분집합을 찾는
find()operation 은 자연스레 해당 원소가 속한 tree 의 root 를 찾는 문제가 된다. - 그래서 그냥 재귀를 사용하던 iterator 를 사용하던
parent를 타고 올라가며 root 를 찾으면 된다.
union
- 두 원소가 속하는 부분집합을 합치는 연산.
- 마찬가지로 tree root 가 부분집합의 abstract 이므로, 두 원소에 대한 root 를 찾은 후 한 root 의 parent 가 나머지 root 가 되도록 한다.
- 이때,
height가 최소화되도록 해야 하므로height가 작은 쪽이 큰 쪽으로 포섭될 수 있도록 하고, 만약에 같다면 어느 쪽으로 포섭해도 상관없지만 대신 root 가 바뀌지 않은 쪽의height가 1 증가한다.
코드 (C++)
class DisjointSet {
vector<int> parent;
vector<int> height;
public:
DisjointSet(int size) {
parent = vector<int>(size);
height = vector<int>(size, 1);
for (int i = 0; i < size; i++) {
parent[i] = i;
}
}
void union_elems(int idx0, int idx1) {
int root0 = find_root(idx0);
int root1 = find_root(idx1);
if (root0 == root1) {
return;
}
if (height[root0] == height[root1]) {
parent[root0] = root1;
height[root1]++;
} else if (height[root0] > height[root1]) {
parent[root1] = root0;
} else /* height[root0] < height[root1] */ {
parent[root0] = root1;
}
}
int find_root(int idx) {
int iter = idx;
while (parent[iter] != iter) {
iter = parent[iter];
}
return iter;
}
};