알고리즘
[SWEA] - 서로소 집합 / 3289번 (Java)
JinHxxxxKim
2024. 2. 21. 15:46
[SWEA] - 서로소 집합 / 3289번 (Java)
[SWEA] - 서로소 집합 / 3289번 (Java)
1. 문제 접근
총 n개의 단일 원소를 갖는 집합에 대해 `0`(두 집합을 합치기), `1`(두 집합이 거로 같은 집합에 속하는지) 연산을 실행하고 `1`번 연산에 대해 결과를 출력하면 된다.
기본적인 Union Find 알고리즘이다.
Union Find에는 총 3개의 연산이 존재한다
- `makeSet()`: 초기 집합 상태를 만드는 연산
- `union()`: 두 집합을 하나의 집합으로 합치는 연산
- `getRoot()`: 특정 노드의 집합을 식별하는 연산
따라서 `0`번 입력이 들어오면 `union()`을, `1`번 연산이 들어오면 `getRoot()`연산을 통해 결과를 얻으면 된다.
switch (op) {
case UNION:
union(node1, node2);
break;
case IS_SAME_SET:
int node1Root = getRoot(node1);
int node2Root = getRoot(node2);
if(node1Root == node2Root) {
localSb.append(1);
}else {
localSb.append(0);
}
break;
}
추가적으로 Union Find 알고리즘을 실행할 때, 경로 압축(Path Compression)을 통해 최적화를 진행하였다.
// 매개변수 node의 root를 찾는 메소드
private static int getRoot(int node) {
if (parents[node] == node) return node;
else return parents[node] = getRoot(parents[node]); // 경로 압축
}
2. 코드
package swea;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.StringTokenizer;
/**
* [SWEA] - 서로소 집합 / 3289번 (Java)
* @author JinHxxxxKim
*
* 1. 테스트 케이스의 수 TC를 입력받는다.
* 2. n(1≤n≤1,000,000), m(1≤m≤100,000)을 입력받는다.
* n: 노드의 개수, m: 연산의 개수
* - Make-Set() 연산 실행
* 3. m개의 연산에 대해 반복
* - 1: 두 집합의 대표자를 찾고 같다면 1을 append, 아니라면 0을 append
* - 0: 두 집합에 대해 union 연산을 실행한다.
* 4. 출력
*/
public class Sol_3289 {
private static final int UNION = 0;
private static final int IS_SAME_SET = 1;
private static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
private static StringBuilder sb = new StringBuilder();
private static StringTokenizer st;
private static int N, M;
// 각 노드의 부모(대표자)를 저장하는 배열
private static int[] parents;
public static void main(String[] args) throws Exception {
int TC = Integer.parseInt(br.readLine().trim());
for (int testCase = 1; testCase <= TC; testCase++) {
StringBuilder localSb = new StringBuilder();
st = new StringTokenizer(br.readLine().trim());
N = Integer.parseInt(st.nextToken());
M = Integer.parseInt(st.nextToken());
parents = new int[N+1];
// Make-Set 연산
for(int node = 1;node<=N;++node) {
parents[node] = node;
}
// 연산 시작
for (int opCnt = 0; opCnt < M; ++opCnt) {
st = new StringTokenizer(br.readLine().trim());
int op = Integer.parseInt(st.nextToken());
int node1 = Integer.parseInt(st.nextToken());
int node2 = Integer.parseInt(st.nextToken());
switch (op) {
case UNION:
union(node1, node2);
break;
case IS_SAME_SET:
int node1Root = getRoot(node1);
int node2Root = getRoot(node2);
if(node1Root == node2Root) {
localSb.append(1);
}else {
localSb.append(0);
}
break;
}
}
sb.append(String.format("#%d %s\n", testCase, localSb.toString()));
}
System.out.println(sb);
}
// 두 집합을 합치는 메소드
private static void union(int node1, int node2) {
int node1Root = getRoot(node1);
int node2Root = getRoot(node2);
parents[node1Root] = node2Root;
}
// 매개변수 node의 root를 찾는 메소드
private static int getRoot(int node) {
if (parents[node] == node)
return node;
else
return parents[node] = getRoot(parents[node]); // 경로 압축
}
}
3. 디버깅
각 노드의 대표자(root)를 찾을 때, 경로 압축(Path Compression)을 통해 최적화를 진행하고, Union 연산 실행 시, 매개변수로 전달 받은 노드의 부모(`parent`)를 수정하는 것이 아닌, 해당 노드의 대표자(root)의 부모를 수정하는 부분이 중요하다.