BFS로 최단거리 찾기
문제 소개
백준 1697번 "숨바꼭질"은 BFS(너비 우선 탐색)를 이용한 최단거리 찾기의 대표적인 문제입니다.
문제 요약
- 수빈이는 현재 점 N에 있고, 동생은 점 K에 있음
- 1초마다 3가지 이동 가능:
- X-1 (뒤로 한 칸)
- X+1 (앞으로 한 칸)
- X×2 (순간이동)
- 동생을 찾는 최단 시간 구하기
예제
입력: 5 17
출력: 4
경로: 5 → 10 → 9 → 18 → 17 (4초)
왜 BFS를 사용해야 할까?
BFS vs DFS 비교
구분 | BFS | DFS |
탐색 방식 | 너비 우선 (층별로) | 깊이 우선 (한 길로) |
최단거리 | ✅ 보장 | ❌ 보장 안됨 |
구현 | Queue 사용 | 재귀/Stack 사용 |
메모리 | 많이 사용 | 적게 사용 |
BFS가 최단거리를 보장하는 이유
시간 0: [5]
시간 1: [4, 6, 10] ← 1초 만에 갈 수 있는 모든 곳
시간 2: [3, 8, 7, 12, 9, 11, 20] ← 2초 만에 갈 수 있는 모든 곳
시간 3: [...] ← 3초 만에 갈 수 있는 모든 곳
시간 4: [...17...] ← 17에 처음 도달! (최단시간)
핵심: 물결이 퍼지듯이 가까운 곳부터 탐색하므로, 목표에 처음 도달하는 순간이 최단거리!
문제 접근 방법
Step 1: 문제 분석
javascript
// 매 초마다 가능한 이동
현재 위치: X
1초 후 가능한 위치: [X-1, X+1, X×2]
// 제약 조건
0 ≤ N, K ≤ 100,000
Step 2: 알고리즘 설계
- 큐에 시작점 넣기: queue = [[N, 0]] (위치, 시간)
- 방문 체크 배열 생성: 중복 방문 방지
- BFS 탐색:
- 큐에서 하나씩 꺼내기
- 3가지 이동 시도
- 목표 도달 시 종료
- 미방문 위치는 큐에 추가
Step 3: 시각적 이해
[5] 시작
↓
[4] [6] [10] 1초 후
↓
... 계속 탐색
↓
[17] 4초 후 도달!
💻 전체 코드 구현
기본 구현
javascript
const fs = require("fs");
const input = fs.readFileSync("/dev/stdin").toString().trim().split(" ");
const [N, K] = input.map(Number);
// 같은 위치면 0초
if (N === K) {
console.log(0);
return;
}
// BFS 초기 설정
const queue = [[N, 0]]; // [위치, 시간]
const visited = new Array(100001).fill(false);
visited[N] = true;
let front = 0; // 큐 인덱스 (shift 대신 사용)
// BFS 탐색
while (front < queue.length) {
const [pos, time] = queue[front++];
// 3가지 이동 방법
const nextPositions = [pos - 1, pos + 1, pos * 2];
for (const next of nextPositions) {
// 목표 도달!
if (next === K) {
console.log(time + 1);
return;
}
// 범위 체크 & 방문 체크
if (next >= 0 && next <= 100000 && !visited[next]) {
visited[next] = true;
queue.push([next, time + 1]);
}
}
}
최적화된 구현
javascript
const fs = require("fs");
const input = fs.readFileSync("/dev/stdin").toString().trim().split(" ");
const [N, K] = input.map(Number);
// 예외 처리: 같은 위치
if (N === K) {
console.log(0);
process.exit(0);
}
// 예외 처리: N > K인 경우 (뒤로만 이동)
if (N > K) {
console.log(N - K);
process.exit(0);
}
const MAX = 100000;
const visited = new Array(MAX + 1).fill(-1); // -1: 미방문, 0이상: 도달 시간
visited[N] = 0;
const queue = [N];
let front = 0;
while (front < queue.length) {
const pos = queue[front++];
const time = visited[pos];
// 3가지 이동 시도
for (const next of [pos - 1, pos + 1, pos * 2]) {
// 목표 도달
if (next === K) {
console.log(time + 1);
process.exit(0);
}
// 유효 범위 & 미방문
if (next >= 0 && next <= MAX && visited[next] === -1) {
visited[next] = time + 1;
queue.push(next);
}
}
}
코드 상세 설명
1. 큐 최적화
javascript
// ❌ 비효율적 (shift는 O(n))
const current = queue.shift();
// ✅ 효율적 (인덱스 사용은 O(1))
const current = queue[front++];
2. 방문 체크의 중요성
javascript
// 방문 체크를 안 하면?
5 → 4 → 5 → 4 → ... (무한 반복!)
// 방문 체크를 하면
5 → 4 (4 방문 표시)
5 → 6 (6 방문 표시)
4는 이미 방문했으므로 다시 가지 않음
3. 범위 체크 필수
javascript
// 범위를 벗어나면 배열 에러!
if (next >= 0 && next <= 100000) {
// 안전한 범위
}
시간복잡도 분석
- 시간복잡도: O(N)
- 최대 100,001개의 위치를 한 번씩 방문
- 각 위치에서 3가지 이동 확인 = O(3N) = O(N)
- 공간복잡도: O(N)
- visited 배열: 100,001 크기
- queue: 최대 100,001개 요소
핵심 정리
BFS 최단거리 템플릿
javascript
// 1. 초기화
const queue = [[start, 0]];
const visited = new Array(MAX).fill(false);
visited[start] = true;
// 2. BFS 탐색
while (queue.length > 0) {
const [current, time] = queue.shift();
// 3. 다음 위치들 확인
for (const next of getNextPositions(current)) {
// 4. 목표 확인
if (next === target) return time + 1;
// 5. 방문 체크 & 큐 추가
if (isValid(next) && !visited[next]) {
visited[next] = true;
queue.push([next, time + 1]);
}
}
}
마무리
"숨바꼭질" 문제는 BFS의 최단거리 특성을 보여주는 대표적인 문제입니다.
요약:
- BFS는 가중치가 같은 그래프에서 최단거리 보장
- 방문 체크로 중복 방지
- 범위 체크로 에러 방지
- 큐 최적화로 성능 향상
'알고리즘 풀이' 카테고리의 다른 글
백준 | 18870번 좌표 압축 (JavaScript 풀이) (0) | 2025.10.05 |
---|---|
백준 | 2805번 나무 자르기 (JavaScript 풀이) (0) | 2025.10.04 |
백준 | Class3 핵심 알고리즘 개념 정리 (2) | 2025.09.20 |
백준 | 2606번 바이러스 (JavaScript 풀이) (0) | 2025.09.09 |
백준 | 1463번 1로 만들기 (JavaScript 풀이) (0) | 2025.09.06 |