문제 설명
주어진 가중치가 있는 트리에서 두 노드 사이의 최대 거리를 구하는 문제이다.
즉, 트리의 모든 두 노드 사이의 거리 중 가장 긴 값을 구하면 그것이 트리의 지름이다.
입력 조건
- 첫 줄: 노드의 개수 N
- 다음 N줄: 각 줄은 시작 노드 번호와, 해당 노드와 연결된 노드 번호 및 가중치 쌍이 주어지고, -1로 종료된다.
예를 들어, 한 줄의 입력은 "1 3 2 2 3 -1"과 같이 주어질 수 있다.
풀이 과정
📌 문제 요약
- 가중치가 있는 트리에서 두 노드 사이의 최대 거리를 구하는 문제이다.
- 트리의 지름은 임의의 두 노드 사이의 거리 중 가장 긴 값이다.
- DFS를 두 번 수행하는 방식으로 문제를 해결한다.
📌 DFS를 두 번 수행하면 트리의 지름이 나오는 이유?
- 트리의 특성:
트리는 사이클이 없고, 모든 두 노드 사이에 유일한 경로가 존재한다.
따라서 임의의 노드에서 가장 멀리 있는 노드는 항상 트리의 한쪽 끝에 위치한다. - 끝점(Leaf)의 성질:
첫 번째 DFS에서 얻은 노드 B는 끝점일 가능성이 매우 높으며, 트리의 지름은 항상 두 끝점 사이의 최단 경로이다.
두 번째 DFS를 B에서 시작하면, B와 가장 먼 노드 C까지의 경로가 결국 트리의 지름이 된다. - 증명 아이디어:
임의의 노드 A에서 가장 멀리 떨어진 노드를 B라고 하자.
만약 B가 트리의 지름의 한 끝점이 아니라면, 트리의 지름은 A보다 B보다 더 먼 곳에 존재하게 되는데, 이는 A에서 DFS로 B를 선택한 조건과 모순된다.
따라서, B는 트리의 지름의 한 끝점이 되며, B에서 다시 가장 멀리 떨어진 노드 C까지의 거리가 트리의 지름임을 알 수 있다.
📌 해결 전략
- 첫 번째 DFS:
임의의 노드(여기서는 1번 노드)에서 시작하여 가장 멀리 떨어진 노드를 찾는다. - 두 번째 DFS:
첫 번째 DFS에서 찾은 가장 먼 노드에서 시작하여 다시 DFS를 수행해 최대 거리를 구한다. - 결과:
두 번째 DFS에서 구한 최대 거리가 트리의 지름이 된다.
📌 풀이 과정 (DFS)
- DFS 함수는 현재 노드에서 상하(연결된 노드)로 이동하면서 누적 거리를 계산한다.
- 방문 배열(visited)을 사용해 중복 방문을 방지한다.
- DFS 중에 누적 거리가 현재 최대값보다 크면 최대값과 해당 노드를 갱신한다.
- 첫 번째 DFS로 얻은 가장 먼 노드에서 다시 DFS를 수행하여 트리의 지름을 구한다.
📌 시간 복잡도 분석
- 각 DFS는 노드 수 N에 대해 O(N) 시간이 소요된다.
- 두 번의 DFS를 수행하므로 전체 시간 복잡도는 O(2N), 즉 O(N)이다.
- 트리의 특성상 모든 노드를 한 번씩 방문하므로 효율적인 풀이이다. 👍
🔥 최종 정리 및 코드
임의의 노드에서 DFS를 실행해 가장 멀리 있는 노드를 찾고, 그 노드에서 다시 DFS를 수행해 트리의 지름(최대 거리)를 구한다.
방문 배열을 통해 중복 방문을 방지하며, DFS 과정에서 누적 거리와 최대값을 갱신하는 방식으로 문제를 해결한다.
package Graph;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.io.IOException;
/*
BOJ Gold 2, 트리의 지름
문제 링크: https://www.acmicpc.net/problem/1167
트리의 지름이란, 임의의 두 점 사이의 거리 중 가장 긴 것이다.
가중치가 있는 무방향 그래프(트리)에서,
임의의 노드에서 DFS를 통해 가장 멀리 떨어진 노드와 그 거리를 기록하고,
그 노드에서 다시 DFS를 수행하면 트리의 지름을 구할 수 있다.
*/
public class BOJ_G2_1167 {
static int N; // 노드의 개수
static List<int[]>[] tree; // 인접 리스트 (각 노드와 연결된 [노드, 가중치] 쌍들을 저장)
static boolean[] visited; // 방문 여부 배열
static int maxDist = 0; // 현재까지 기록된 최대 거리
static int longestNode = 0; // 현재 DFS에서 가장 먼 노드
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
N = Integer.parseInt(br.readLine()); // 노드의 개수 입력
// 트리 생성: 1번 노드부터 N번 노드까지 사용하므로 N+1 크기로 초기화
tree = new ArrayList[N + 1];
for (int i = 0; i <= N; i++) {
tree[i] = new ArrayList<>(); // 각 노드의 인접 리스트 생성
}
// 각 노드의 인접 정보 입력 (노드 번호, 가중치 쌍들이 -1이 나올 때까지)
for (int i = 0; i < N; i++) {
StringTokenizer st = new StringTokenizer(br.readLine());
int u = Integer.parseInt(st.nextToken()); // 현재 노드 번호
while (true) {
int v = Integer.parseInt(st.nextToken());
if (v == -1) break;
int w = Integer.parseInt(st.nextToken());
tree[u].add(new int[]{v, w}); // 노드 u에 연결된 (노드 v, 가중치 w) 추가
}
}
// 첫 번째 DFS: 임의의 노드 (여기서는 1번)에서 시작하여 가장 먼 노드 탐색
visited = new boolean[N + 1];
dfs(1, 0);
// 두 번째 DFS: 첫 번째 DFS에서 찾은 가장 먼 노드에서 시작하여 트리의 지름 계산
visited = new boolean[N + 1];
maxDist = 0;
dfs(longestNode, 0);
// 트리의 지름 출력
System.out.println(maxDist);
}
// DFS 함수: 현재 노드에서 시작하여 누적 거리를 계산하면서 가장 먼 노드를 찾음
static void dfs(int node, int dist) {
// 현재까지의 누적 거리가 최대값보다 크면 갱신
if (dist > maxDist) {
maxDist = dist;
longestNode = node;
}
visited[node] = true; // 현재 노드 방문 처리
// 인접 리스트에 저장된 모든 연결 노드에 대해 DFS 수행
for (int[] next : tree[node]) {
if (!visited[next[0]]) {
dfs(next[0], dist + next[1]); // 연결 노드로 이동하면서 누적 거리에 가중치 추가
}
}
}
}
'Coding Test > Graph' 카테고리의 다른 글
[ 백준 ] 13023번 ABCDE (Java 자바) (0) | 2025.03.04 |
---|---|
[ 백준 ] 3109번 빵집 (Java 자바) (0) | 2025.03.04 |
[ 백준 ] 2468번 안전 영역 (Java 자바) (0) | 2025.03.04 |
[백준] 1005번 ACM Craft (Python 파이썬) (0) | 2024.07.04 |