
노드끼리 연결되어있는지를 확인하는 문제로 일반적으로 Union-find를 생각해볼 수 있다. 일반적인 Union-find문제와 다르다고 생각한 점은 크게 2가지가 있었다.
import java.io.*;
import java.util.*;
public class Main {
static Map<String, String> parent;
static Map<String, Integer> nameCnt;
static int cnt;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
int T = Integer.parseInt(br.readLine());
StringBuilder sb = new StringBuilder();
while (T-- > 0) {
int F = Integer.parseInt(br.readLine());
parent = new HashMap<>();
nameCnt = new HashMap<>();
for (int i = 0; i < F; i++) {
st = new StringTokenizer(br.readLine());
String name1 = st.nextToken();
String name2 = st.nextToken();
if(!parent.containsKey(name1)){
parent.put(name1, name1);
}
if(!parent.containsKey(name2)){
parent.put(name2, name2);
}
union(name1, name2);
sb.append(cnt).append("\n");
}
}
System.out.println(sb);
br.close();
}
static String find(String name){
if(parent.get(name).equals(name)){
return name;
}
return parent.put(name,find(parent.get(name)));
}
static void union(String name1, String name2){
name1 = find(name1);
name2 = find(name2);
if(!name1.equals(name2)){
if(!nameCnt.containsKey(name1) && !nameCnt.containsKey(name2)){
parent.put(name2, name1);
nameCnt.put(name1, 2);
cnt = 2;
}else if(nameCnt.containsKey(name1) && !nameCnt.containsKey(name2)){
parent.put(name2, name1);
cnt = nameCnt.get(name1) + 1;
nameCnt.put(name1, cnt);
}else if(!nameCnt.containsKey(name1) && nameCnt.containsKey(name2)){
parent.put(name1, name2);
cnt = nameCnt.get(name2) + 1;
nameCnt.put(name2, cnt);
}else{
parent.put(name2, name1);
cnt = nameCnt.get(name1) + nameCnt.get(name2);
nameCnt.put(name1, cnt);
}
}
}
}
for (int i = 0; i < F; i++) {
st = new StringTokenizer(br.readLine());
String name1 = st.nextToken();
String name2 = st.nextToken();
if(!parent.containsKey(name1)){
parent.put(name1, name1);
}
if(!parent.containsKey(name2)){
parent.put(name2, name2);
}
...
}
Map<String, String> parent 저장한다.static String find(String name){
if(parent.get(name).equals(name)){
return name;
}
return parent.put(name,find(parent.get(name)));
}
static void union(String name1, String name2){
name1 = find(name1);
name2 = find(name2);
if(!name1.equals(name2)){
if(!nameCnt.containsKey(name1) && !nameCnt.containsKey(name2)){
parent.put(name2, name1);
nameCnt.put(name1, 2);
cnt = 2;
}else if(nameCnt.containsKey(name1) && !nameCnt.containsKey(name2)){
parent.put(name2, name1);
cnt = nameCnt.get(name1) + 1;
nameCnt.put(name1, cnt);
}else if(!nameCnt.containsKey(name1) && nameCnt.containsKey(name2)){
parent.put(name1, name2);
cnt = nameCnt.get(name2) + 1;
nameCnt.put(name2, cnt);
}else{
parent.put(name2, name1);
cnt = nameCnt.get(name1) + nameCnt.get(name2);
nameCnt.put(name1, cnt);
}
}
}
Map<String, Integer> nameCnt 는 해당 정점이 몇개의 노드와 연결이 되어있는지 확인하는 것으로, 코드가 많지만 이유는 간단하다. 순서대로 다음과 같다.
find 함수에서 put 메소드의 오버헤드라고 판단하여 이를 단순히 부모 값을 리턴하는 코드를 작성했지만 이번에는 메모리 초과가 아닌 틀렸다는 결과를 볼 수 있었다.import java.io.*;
import java.util.*;
public class Main {
static Map<String, Integer> cnt;
static Map<String, String> parent;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
int T = Integer.parseInt(br.readLine());
for (int i = 0; i < T; i++) {
int num = Integer.parseInt(br.readLine());
parent = new HashMap<>();
cnt = new HashMap<>();
for (int j = 0; j < num; j++) {
String[] name = br.readLine().split(" ");
for (int z = 0; z < 2; z++) {
if (!parent.containsKey(name[z])) {
parent.put(name[z], name[z]);
cnt.put(name[z], 1);
}
}
union(name[0], name[1]);
bw.write((cnt.get(find(name[1]))) + "\n");
}
}
bw.flush();
bw.close();
br.close();
}
static String find(String x){
if (!x.equals(parent.get(x))) {
parent.replace(x, find(parent.get(x)));
}
return parent.get(x);
}
static void union(String x, String y){
x = find(x);
y = find(y);
if(!x.equals(y)){
parent.replace(y, x);
cnt.replace(x, cnt.get(x) + cnt.get(y));
}
}
}
static String find(String x){
if (!x.equals(parent.get(x))) {
parent.replace(x, find(parent.get(x)));
}
return parent.get(x);
}
크게 2가지를 변경했다.
자기 자신이 정점이 아닐 때 로 조건문을 수정해서 parent.get(x) 로 리턴하도록 수정put이 아닌 replace 를 사용이미 존재하는 key의 value값을 업데이트 할때, replace를 사용하는 것이 당연할 수 있겠지만 왜 그런건지 내부 동작을 보면서 살펴봤다.
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) {
if (table == null || table.length == 0) resize();
int i = (n - 1) & hash;
if (table[i] != null) {
...
}
table[i] = newNode(hash, key, value, null);
if (++size > threshold) resize();
return oldValue;
}
...
final Node<K, V>[] resize() {
Node<K, V>[] oldTab = this.table;
int oldCap = oldTab == null ? 0 : oldTab.length;
int oldThr = this.threshold;
int newThr = 0;
int newCap;
...
}
public V replace(K key, V value) {
Node e;
if ((e = this.getNode(key)) != null) {
V oldValue = e.value;
e.value = value;
this.afterNodeAccess(e);
return oldValue;
} else {
return null;
}
}
replace 는 탐색을 수행하고 새로운 값으로 변경하기 때문에 메모리 관점에서 훨씬 유리하게 작용하는 것이다.static void union(String x, String y){
x = find(x);
y = find(y);
if(!x.equals(y)){
parent.replace(y, x);
cnt.replace(x, cnt.get(x) + cnt.get(y));
}
}
Map<String, Integer> cnt 에 x 값을 기준으로 cnt를 더해서 저장한다.1
2
Fred Barney
Betty Fred
{Barney=Fred, Betty=Betty, Fred=Betty}