[자료구조] 서로소 집합 자료구조 (Disjoint Set Union, DSU) - Union Find🧠 CS/자료구조2025. 3. 3. 21:54
Table of Contents
이 글은 서로소 집합 자료구조(Disjoint Set Union, DSU)에 대해서 정리한 글입니다.
DSU와 Union Find
✅ "Union Find"라는 이름은 합치기를 뜻하는 Union과 찾기를 뜻하는 Find라는 두 가지의 주요 연산에서 유래한 표현으로 서로소 집합 자료구조(Disjoint Set Union, DSU)과 동일한 자료구조를 의미
✅ DSU는 "서로소(disjoint)인 여러 집합을 관리하고, 필요할 때 이 집합들을 합쳐(Union)주면서 집합의 대표나 구조를 빠르게 찾는(Find) 기법"을 의미하는 더 일반적이며 학술적인 표현
Union Find랑 DSU는 동일한 자료구조를 나타내는 용어이며, 이 글에서 사용하는 Union Find와 DSU는 동일한 개념으로 이해하면 됩니다.
DSU(Disjoint Set Union)의 기본 개념
서로소(Disjoint) 집합
- 서로소 집합 또는 상호베타 집합은 서로 중복되는 원소가 없는 여러 개의 집합을 의미
- ex. 집합 A와 집합 B에 대하여 $ A \cap B = \varnothing $ 이면, 집합 A와 B는 서로소 집합
- 대표자를 통해 각 집합을 구분
- 대표자 (representative) : 집합에 속한 하나의 특정 멤버(원소)
DSU 자료구조
- 원소들이 각각 어떤 집합(그룹)에 속해 있는지를 관리
- Find와 Union이라는 주요 2가지 연산이 존재
find(x): 원소x가 어느 집합에 속해 있는지 대표(루트, root)를 찾아 반환union(a, b): 원소a가 속한 집합과 원소b가 속한 집합을 결합
활용 예시
- 그래프에서 사이클 판별
- 무방향 그래프에서 두 노드를 연결할 때(간선 추가), 이미 두 노드가 같은 집합이라면 사이클이 형성된다는 것을 알 수 있음
- 최소 신장 트리(MST) - Kruskal 알고리즘
- 간선을 가중치 오름차순으로 정렬 후, 간선을 하나씩 추가하며 Union-Find로 사이클 여부를 확인
- 동적 연결 요소 관리
- 유저나 네트워크 노드가 "연결" 혹은 "친구" 상태가 되었는지 실시간으로 합치는 연산에 사용할 수 있음
DSU(Union Find)의 기본 동작 원리
대표 노드(루트, root)의 개념
- 각 집합은 트리(tree) 구조로 표현할 수 있으며, "대표 노드(루트)" 하나가 집합 전체를 대표
- 모든 원소는 자신이 속한 집합의 루트를 찾을 수 있음 (Find 연산)
초기화 (Initialization)
- 보통
n개의 원소(0~n-1)가 있을 때, 처음에는 각각이 자기 자신만을 포함하는 집합으로 초기화함parent[i] = i(자기 자신을 부모로 하는 트리 구조)
Union 연산
- 원소
a,b가 주어졌을 때,Find(a)와Find(b)로 각자의 루트를 찾음 - 두 루트가 서로 다르면, 한쪽 루트를 다른 쪽 트리에 연결하여 두 집합을 결합
Find 연산
- 원소
x의 루트를 찾기 위해서는parent[x]를 따라 거슬러 올라가 최종적으로 자기 자신의 부모로 갖는 노드(루트)를 만나면 그게 원소x가 속한 집합의 대표 - 일반적으로 경로 압축(path compression)을 사용하여
Find연산을 할 때 트리 높이를 줄여 다음 번에 더 빠른 탐색을 가능하게 함
Union Find 구현
/*
입력 예시
6개 노드 3개 간선 정보
6 3
1 2
4 5
2 5
*/
#include<iostream>
using namespace std;
// 대표 노드 체크를 위한 DAT
int parent[7]; // index : 노드 번호 / value : 대표 노드 번호
// Find : 특정 그룹의 대표 찾기 ( 재귀 )
int Find(int n) {
//대표가 본인인 경우 return
if (n == parent[n]) return n;
//root 노드까지 찾아가기
int root = Find( parent[n] );
//대표 반환
return root;
}
// Union : 그룹을 합친다.
void Union(int A, int B) {
//그룹을 합칠 때, 같은 그룹인지 체크해야 한다.
int rootA = Find(A);
int rootB = Find(B);
//같은 그룹이라면 종료
if (rootA == rootB) return;
//대표 정보 업데이트
parent[rootB] = rootA;
}
int main() {
// 초기 세팅 : 모두가 대표
for (int i = 1; i <= 6; i++) {
parent[i] = i;
}
int N, M;
cin >> N >> M;
for (int i = 0; i < M; i++) {
int boss, employee;
cin >> boss >> employee;
Union(boss, employee);
}
return 0;
}
parent배열- 각 노드가 속한 집합의 대표 노드를 기록하고,
union과find연산을 통해 갱신
- 각 노드가 속한 집합의 대표 노드를 기록하고,
Find(int n)함수n번 노드가 어떤 대표(루트)에 소속되어 있는지 재귀적으로 추적- 대표가 자기 자신인 노드를 찾으면 그 노드가 루트
Union(int A, int B)함수A와B의 루트를 비교해 다를 경우, 둘을 하나의 집합으로 결합- 위 코드는 간단한 구현으로,
parent[rootB] = rootA;로 처리
- 초기 세팅 : 초기화
- 모든 노드가 자기 자신만 포함하는 집합이 되도록 설정 (
parent[i] = i) - 입력 간선을 받아
Union을 반복 호출해 집합을 통합
- 모든 노드가 자기 자신만 포함하는 집합이 되도록 설정 (
성능 개선 기법
경로 압축 (path compression)
- Find 연산을 수행하는 동안 만나는 모든 노드의 부모를 최종 루트로 직접 연결해주는 방식
- 트리의 높이가 극도로 낮아져 이후 Find 연산이 거의 $ O(1) $ 에 가깝게 수행
/*
입력 예시
6개 노드 3개 간선 정보
6 3
1 2
4 5
2 5
*/
#include<iostream>
using namespace std;
// 대표 노드 체크를 위한 DAT
int parent[7]; // index : 노드 번호 / value : 대표 노드 번호
// Find : 특정 그룹의 대표 찾기 ( 재귀 )
int Find(int n) {
//대표가 본인인 경우 return
if (n == parent[n]) return n;
//root 노드까지 찾아가기 - 경로압축
//해당 노드의 root 갱신
parent[n] = Find( parent[n] );
return parent[n];
}
// Union : 그룹을 합친다.
void Union(int A, int B) {
//그룹을 합칠 때, 같은 그룹인지 체크해야 한다.
int rootA = Find(A);
int rootB = Find(B);
//같은 그룹이라면 종료
if (rootA == rootB) return;
//대표 정보 업데이트
parent[rootB] = rootA;
}
int main() {
// 초기 세팅 : 모두가 대표
for (int i = 1; i <= 6; i++) {
parent[i] = i;
}
int N, M;
cin >> N >> M;
for (int i = 0; i < M; i++) {
int boss, employee;
cin >> boss >> employee;
Union(boss, employee);
}
return 0;
}
union-by-rank (또는 union-by-size)
- 트리 높이나 노드 개수를 관리하여 항상 더 큰(또는 더 깊은) 트리에 작은 트리를 합치는 방식을 적용하면 트리의 높이를 최소화하여 Find를 더 빠르게 만들 수 있음
대표 연습 문제
핵심 요약
- DSU 혹은 Union Find는 여러 집합을 효율적으로 합치고, 특정 원소가 속한 집합의 대표(루트)를 빠르게 찾는 자료구조
Find(x)와Union(a, b)가 핵심 연산으로,Find(x)는 원소x가 어느 집합에 속해 있는지 루트를 찾는 역할을 수행하며,Union(a, b)는 원소a의 집합과 원소b의 집합을 합치는 역할을 수행- 경로 압축(path compression)과 union-by-rank, union-by-size 방식을 사용하여 최적화 가능
'🧠 CS > 자료구조' 카테고리의 다른 글
| [자료구조] C++ Container Library : Sequential Containers - vector (0) | 2025.04.26 |
|---|---|
| [자료구조] 우선순위 큐(priority queue)의 정렬 안정성 (0) | 2025.04.06 |
| [자료구조] 스택(Stack)과 큐(Queue) (0) | 2025.02.27 |
| [자료구조] 정적 배열과 동적 배열 (Static Array & Dynamic Array) (0) | 2025.02.12 |
| [자료구조] 배열(Array)과 DAT(Direct Access Table) (0) | 2025.01.27 |
@청월누리 :: DevKuk 개발 블로그
since 2025.01.27. ~ 개발자를 향해....🔥