728x90
반응형
문제 살펴보기!!
문제 링크 : https://www.acmicpc.net/problem/1967
반응형
솔루션 살펴보기!!
import sys
from collections import defaultdict
def main():
sys.setrecursionlimit(1000000) # 재귀 호출 제한을 크게 설정 (큰 트리 처리 가능)
input = sys.stdin.read
data = input().split()
idx = 0
n = int(data[idx]) # 노드 수 입력 받음
idx += 1
if n == 1: # 노드가 1개인 경우, 트리의 지름은 0
print(0)
return
adj = defaultdict(list) # 인접 리스트로 트리 구조 저장
for _ in range(n-1): # 간선 정보 입력
parent = int(data[idx])
child = int(data[idx+1])
weight = int(data[idx+2])
idx += 3
adj[parent].append((child, weight))
adj[child].append((parent, weight))
def iterative_dfs(start):
stack = [(start, 0)] # 스택에 (노드, 거리) 저장
visited = [False] * (n + 1) # 방문 체크 리스트
max_distance = 0 # 최대 거리 초기화
farthest_node = start # 가장 먼 노드를 저장할 변수
while stack:
node, dist = stack.pop()
if not visited[node]: # 방문하지 않은 노드일 경우
visited[node] = True # 방문 처리
if dist > max_distance: # 더 먼 거리를 찾으면 업데이트
max_distance = dist
farthest_node = node
# 인접한 노드를 스택에 추가 (방문하지 않은 노드만)
for neighbor, weight in adj[node]:
if not visited[neighbor]:
stack.append((neighbor, dist + weight))
return farthest_node, max_distance
# 첫 번째 DFS: 1번 노드에서 가장 먼 노드를 찾음
u, _ = iterative_dfs(1)
# 두 번째 DFS: u에서 가장 먼 노드를 찾음 (이때의 거리가 트리의 지름)
_, diameter = iterative_dfs(u)
print(diameter) # 트리의 지름 출력
if __name__ == "__main__":
main()
풀이 전략
이 문제는 트리의 지름을 구하는 문제입니다. 트리의 지름이란 트리에서 두 노드 사이의 가장 긴 경로를 의미합니다. 이를 구하기 위해서는 두 번의 DFS(깊이 우선 탐색)를 이용합니다.
풀이 전략 단계:
- DFS를 두 번 수행하여 트리의 지름을 계산합니다.
- 첫 번째 DFS: 임의의 노드(예: 1번 노드)에서 가장 먼 노드를 찾습니다. 이 노드를 트리의 한쪽 끝으로 간주할 수 있습니다.
- 두 번째 DFS: 첫 번째 DFS에서 찾은 노드에서 다시 DFS를 수행하여 이 노드에서 가장 먼 노드를 찾습니다. 이 때의 거리가 트리의 지름입니다.
- 트리 구조는 인접 리스트로 표현합니다. 각 노드는 다른 노드와 연결되며, 그 사이에는 가중치가 있습니다.
- DFS는 스택을 사용한 반복적 탐색으로 구현합니다. 스택을 이용한 DFS는 트리의 깊이가 매우 깊을 때 발생할 수 있는 재귀 호출 깊이 초과 문제를 방지할 수 있습니다.
- 최종 출력은 트리의 지름입니다.
주요 코드 흐름
입력 처리:
- 트리의 노드와 간선 정보를 읽어들여, 인접 리스트를 구성합니다.
- adj는 defaultdict(list)로 각 노드와 연결된 이웃 노드와 가중치를 저장합니다.
adj = defaultdict(list)
for _ in range(n-1):
parent = int(data[idx])
child = int(data[idx+1])
weight = int(data[idx+2])
idx += 3
adj[parent].append((child, weight))
adj[child].append((parent, weight))
DFS를 사용한 탐색:
- DFS를 스택을 사용해 반복적으로 구현합니다. 스택에 (노드, 거리) 형태의 값을 저장하여 탐색 중인 노드와 그 노드까지의 거리를 함께 관리합니다.
- 방문한 노드는 visited 리스트로 체크하고, 탐색 과정에서 현재까지의 최대 거리를 저장하여 트리의 한쪽 끝을 찾습니다.
def iterative_dfs(start):
stack = [(start, 0)]
visited = [False] * (n + 1)
max_distance = 0
farthest_node = start
while stack:
node, dist = stack.pop()
if not visited[node]:
visited[node] = True
if dist > max_distance:
max_distance = dist
farthest_node = node
for neighbor, weight in adj[node]:
if not visited[neighbor]:
stack.append((neighbor, dist + weight))
return farthest_node, max_distance
스택 구조:
- 현재 노드와 그 노드까지의 거리를 스택에 저장한 후, 연결된 이웃 노드를 탐색합니다.
- 방문하지 않은 노드를 스택에 넣고, 탐색을 계속합니다.
- DFS가 끝나면 가장 먼 노드와 그 노드까지의 거리를 반환합니다.
트리의 지름 계산:
- 첫 번째 DFS에서 1번 노드에서 가장 먼 노드를 찾습니다. 이 노드를 u로 둡니다.
- 두 번째 DFS에서는 u에서 가장 먼 노드를 찾고, 이 때의 거리가 트리의 지름입니다
u, _ = iterative_dfs(1)
_, diameter = iterative_dfs(u)
코드 흐름 요약
- 입력 처리: 인접 리스트로 트리의 구조를 저장합니다.
- DFS 탐색:
- 첫 번째 DFS로 임의의 노드에서 가장 먼 노드를 찾습니다.
- 두 번째 DFS로 첫 번째 DFS에서 찾은 노드에서 가장 먼 노드를 찾아 그 거리를 계산합니다.
- 결과 출력: 트리의 지름을 출력합니다.
이 방식은 트리에서 가장 먼 두 노드를 찾는 가장 효율적인 방법 중 하나입니다. DFS를 두 번만 수행하여 트리의 지름을 구할 수 있으며, 시간 복잡도는 O(N)으로 매우 효율적입니다.
주요 개념
1. 그래프 및 트리 데이터 구조
- 트리는 특별한 형태의 그래프입니다. 이 문제에서는 트리의 구조가 주어지며, 트리의 노드와 간선 정보로 트리의 **지름(가장 긴 경로)**을 계산하는 문제입니다.
- 트리의 특징:
- 트리는 사이클이 없는 연결 그래프입니다.
- 노드의 개수가 N개일 때, 간선의 개수는 항상 N-1개입니다.
- 트리의 경로 찾기 문제는 면접에서 자주 등장합니다. 트리의 높이, 지름, 최소 공통 조상(LCA) 등을 묻는 문제가 많습니다.
2. 깊이 우선 탐색 (DFS, Depth-First Search)
- 이 문제에서 트리의 지름을 구하기 위해 DFS(깊이 우선 탐색)를 사용합니다.
- DFS는 그래프나 트리에서 모든 노드를 탐색하는 기본적인 탐색 알고리즘으로, 스택이나 재귀를 사용해 구현할 수 있습니다.
- DFS의 시간 복잡도는 그래프의 노드와 간선의 수에 비례하며, O(N) 시간 복잡도를 가집니다. 이는 트리의 모든 노드를 한 번씩 방문하는 방식으로 지름을 구하는 데 적합합니다.
- 이 문제에서는 반복적 DFS를 사용하여 재귀 호출의 깊이 제한 문제를 피하면서도 효율적으로 탐색을 수행하고 있습니다.
3. 트리의 지름 (Diameter of a Tree)
- 트리의 지름은 두 노드 간의 가장 긴 경로입니다. 이 문제에서는 지름을 구하는 방법으로 DFS를 두 번 사용하는 방법을 적용했습니다.
- 첫 번째 DFS: 임의의 노드에서 가장 먼 노드를 찾습니다.
- 두 번째 DFS: 첫 번째 DFS에서 찾은 노드에서 가장 먼 노드를 다시 찾습니다.
- 이 방법은 트리에서 지름을 구하는 효율적인 방법 중 하나로, 면접에서 자주 사용되는 트리 문제 중 하나입니다.
4. 인접 리스트 (Adjacency List)
- 트리(혹은 그래프)를 저장하는 방식 중 하나인 인접 리스트를 사용하였습니다. 인접 리스트는 각 노드에 연결된 이웃 노드들의 리스트를 저장하는 자료구조입니다.
- 이는 메모리 효율적이고, 탐색할 때도 간단하게 이웃 노드로 이동할 수 있습니다. 이 방식은 특히 희소 그래프에서 유리합니다.
- defaultdict(list)를 사용하여 구현하는 방법은 파이썬에서 직관적이고 유연하게 인접 리스트를 관리할 수 있는 좋은 방법입니다.
5. 스택을 이용한 반복적 DFS
- 재귀 대신 스택을 사용하여 반복적으로 DFS를 구현하는 방식입니다. 이 방식은 깊이 제한이 없는 환경에서 재귀 호출로 인한 스택 오버플로우 문제를 방지할 수 있습니다.
- 면접 팁: 면접 중에 재귀 대신 반복문을 사용할 수 있다는 점을 어필하는 것은 좋은 전략입니다. 특히 재귀 호출 제한이 있는 언어에서는 이러한 방법이 유용하게 사용됩니다.
6. 시간 복잡도 분석
- 이 코드의 시간 복잡도는 **O(N)**입니다. DFS는 트리의 각 노드를 한 번씩 방문하므로, N개의 노드와 N-1개의 간선에 대해 선형적으로 탐색합니다.
- 면접에서 시간 복잡도 분석은 매우 중요한 부분입니다. 알고리즘이 얼마나 효율적인지, 그리고 그 효율성을 설명할 수 있는 능력은 면접관이 확인하고자 하는 핵심 사항 중 하나입니다.
면접에서 추가적으로 고려할 사항
- 코드 최적화:
- 면접관은 코드의 효율성과 최적화를 중요하게 봅니다. 트리의 지름을 구하는 이 알고리즘은 DFS를 두 번만 수행하기 때문에 최적화 측면에서 매우 효율적입니다.
- 확장 가능한 코드 작성:
- 코드가 확장 가능하고, 다른 입력이나 추가적인 요구사항이 주어졌을 때 쉽게 변경할 수 있는지 확인합니다.
- 명확한 설명:
- 코딩 면접에서는 자신이 작성한 코드의 흐름과 알고리즘 선택 이유를 명확하게 설명하는 것이 중요합니다. 어떤 이유로 특정 알고리즘을 사용했는지, 시간 복잡도는 어떻게 되는지, 경계 조건 처리는 어떻게 했는지 등 면접관에게 논리적으로 설명할 수 있어야 합니다.
728x90
반응형
'알고리즘' 카테고리의 다른 글
백준 알고리즘 문제 풀이 가이드: 코딩 면접 대비 완벽 준비-5347 LCM 편 (python) (0) | 2024.10.09 |
---|---|
백준 알고리즘 문제 풀이 가이드: 코딩 면접 대비 완벽 준비-3584 가장 가까운 공통 조상 편 (python) (0) | 2024.10.08 |
백준 알고리즘 문제 풀이 가이드: 코딩 면접 대비 완벽 준비-5639 이진 검색 트리 편 (python) (0) | 2024.10.04 |
백준 알고리즘 문제 풀이 가이드: 코딩 면접 대비 완벽 준비-20924 트리의 기둥과 가지 편 (python) (0) | 2024.09.30 |
백준 알고리즘 문제 풀이 가이드: 코딩 면접 대비 완벽 준비-9489 사촌 편 (python) (0) | 2024.09.27 |