- 너비 우선 탐색(BFS, Breadth-First Search)은 그래프를 완전 탐색하는 방법 중 하나로, 시작 노드에서 출발해 시작 노드를 기준으로 가까운 노드를 먼저 방문하면서 탐색하는 알고리즘임
- 큐(Queue) 자료구조를 이용해 구현하며, 최단 경로를 보장하는 특징이 있음
- 그래프의 모든 정점을 한 번씩 방문하며, 레벨 순회(Level-Order Traversal) 방식으로 동작함
주요 특징 비교
| 구분 | BFS | DFS |
|---|---|---|
| 탐색 방식 | 넓게(Breadth) 탐색 | 깊게(Depth) 탐색 |
| 자료구조 | 큐(Queue) | 스택(Stack) 또는 재귀 |
| 시간 복잡도 | $O(V + E)$ | $O(V + E)$ |
| 공간 복잡도 | $O(V)$ | $O(V)$ |
| 최단 경로 | 보장 (가중치 없는 그래프) | 보장 안 됨 |
| 주요 용도 | 최단 거리, 레벨 탐색 | 경로 존재 여부, 사이클 탐지 |
문제 식별 방법
BFS를 사용해야 하는 경우
- 최단 경로를 찾아야 하는 문제 (가중치가 없거나 모두 동일한 경우)
- 레벨별 탐색이 필요한 문제 (거리별로 그룹화)
- 가장 가까운 목표를 찾는 문제
- 그래프가 너무 깊지 않고 넓은 경우
- 모든 경로를 동시에 탐색해야 하는 경우
대표적인 문제 유형
- 미로 탈출 최단 경로
- 게임 맵 최단 거리
- 단어 변환 최소 단계
- 토마토 익히기 (시뮬레이션)
- 네트워크 연결 여부
- 이진 트리 레벨 순회
개념과 동작 원리
BFS의 흐름

동작 원리
-
시작할 노드를 정한 후 사용할 자료구조 초기화하기
- 방문했던 노드는 다시 방문하지 않으므로 방문 배열 필요
- 그래프를 인접 리스트로 표현
- 큐 자료구조 사용 (FIFO)
-
큐에서 노드를 꺼낸 후 꺼낸 노드의 인접 노드를 다시 큐에 삽입하기
- 큐에서 노드를 꺼내면서 인접 노드를 큐에 삽입
- 방문 배열을 체크하여 이미 방문한 노드는 큐에 삽입하지 않음
- 큐에서 꺼낸 노드는 탐색 순서에 기록
-
큐 자료구조에 값이 없을 때까지 반복하기
- 큐에 노드가 없을 때까지 위 과정 반복
- 선입선출 방식으로 탐색하므로 탐색 순서가 DFS와 다름
- 레벨 순서로 탐색이 보장됨
동작 과정 상세 분석
예제 그래프

단계별 실행 과정
-
초기 상태
1 2 3 4 5 6 7 8
시작 노드: 1 인접 리스트: 1 → [2, 3] 2 → [1, 5, 6] 3 → [1, 4] 4 → [3, 6] 5 → [2] 6 → [2, 4]
- 시작 노드(1) 방문

- 노드 1을 큐에 삽입
- 방문 배열 체크:
visited[1] = true
- 노드 1 꺼내고 인접 노드(2, 3) 삽입

- 노드 1 꺼냄 → 탐색 순서:
1 - 인접 노드 2, 3을 큐에 삽입
visited[2] = true,visited[3] = true
-
노드 2 꺼내고 인접 노드(5, 6) 삽입

- 노드 2 꺼냄 → 탐색 순서:
1 → 2 - 인접 노드 중 미방문: 5, 6
- 노드 1은 이미 방문했으므로 스킵
visited[5] = true,visited[6] = true
- 노드 2 꺼냄 → 탐색 순서:
-
노드 3 꺼내고 인접 노드(4) 삽입

- 노드 3 꺼냄 → 탐색 순서:
1 → 2 → 3 - 인접 노드 중 미방문: 4
- 노드 1은 이미 방문
visited[4] = true
- 노드 3 꺼냄 → 탐색 순서:
-
노드 5 꺼내기

- 노드 5 꺼냄 → 탐색 순서:
1 → 2 → 3 → 5 - 인접 노드(2)는 이미 방문
- 큐에 추가할 노드 없음
- 노드 5 꺼냄 → 탐색 순서:
-
노드 6 꺼내기

- 노드 6 꺼냄 → 탐색 순서:
1 → 2 → 3 → 5 → 6 - 인접 노드(2, 4)는 이미 방문
- 큐에 추가할 노드 없음
- 노드 6 꺼냄 → 탐색 순서:
-
노드 4 꺼내고 탐색 완료

- 노드 4 꺼냄 → 탐색 순서:
1 → 2 → 3 → 5 → 6 → 4 - 인접 노드(3, 6)는 이미 방문
- 큐가 비어 탐색 종료
- 노드 4 꺼냄 → 탐색 순서:
레벨별 분석
1
2
3
Level 0: [1] ← 시작 노드
Level 1: [2, 3] ← 1과 인접한 노드
Level 2: [5, 6, 4] ← 2, 3과 인접한 노드
- BFS는 레벨 순서로 탐색하므로 최단 경로를 보장함
시간 및 공간 복잡도
시간 복잡도: $O(V + E)$
- V
- 정점(Vertex)의 개수
- E
- 간선(Edge)의 개수
- 모든 정점을 한 번씩 방문
- $O(V)$
-
모든 간선을 한 번씩 확인
- $O(E)$
-
분석
1 2 3 4 5 6
for each vertex v in Graph: // O(V) if not visited[v]: visit v for each edge (v, u): // O(E) if not visited[u]: enqueue u
공간 복잡도: $O(V)$
- 방문 배열
- $O(V)$
- 큐
- 최악의 경우 모든 노드 저장 $O(V)$
-
인접 리스트
- $O(V + E)$
-
최악의 경우
- 완전 그래프(Complete Graph)에서 큐에 모든 정점이 동시에 들어갈 수 있음
Java 구현
기본 구현 (인접 리스트)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.*;
public class BFS {
/**
* BFS를 이용한 그래프 탐색
* @param graph 인접 리스트로 표현된 그래프
* @param start 시작 노드
* @return 탐색 순서를 담은 리스트
*/
public static List<Integer> bfs(List<List<Integer>> graph, int start) {
List<Integer> result = new ArrayList<>();
boolean[] visited = new boolean[graph.size()];
Queue<Integer> queue = new LinkedList<>();
// 시작 노드를 큐에 삽입하고 방문 처리
queue.offer(start);
visited[start] = true;
// 큐가 빌 때까지 반복
while (!queue.isEmpty()) {
// 큐에서 노드 꺼내기
int current = queue.poll();
result.add(current);
// 인접한 노드들을 큐에 삽입
for (int neighbor : graph.get(current)) {
if (!visited[neighbor]) {
queue.offer(neighbor);
visited[neighbor] = true;
}
}
}
return result;
}
public static void main(String[] args) {
// 그래프 생성 (1~6번 노드)
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < 7; i++) {
graph.add(new ArrayList<>());
}
// 간선 추가 (양방향)
graph.get(1).addAll(Arrays.asList(2, 3));
graph.get(2).addAll(Arrays.asList(1, 5, 6));
graph.get(3).addAll(Arrays.asList(1, 4));
graph.get(4).addAll(Arrays.asList(3, 6));
graph.get(5).add(2);
graph.get(6).addAll(Arrays.asList(2, 4));
List<Integer> result = bfs(graph, 1);
System.out.println("BFS 탐색 순서: " + result);
// 출력: [1, 2, 3, 5, 6, 4]
}
}
최단 거리 구하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import java.util.*;
public class BFSShortestPath {
/**
* BFS를 이용한 최단 거리 계산
* @param graph 인접 리스트
* @param start 시작 노드
* @return 각 노드까지의 최단 거리 배열
*/
public static int[] bfsDistance(List<List<Integer>> graph, int start) {
int n = graph.size();
int[] distance = new int[n];
boolean[] visited = new boolean[n];
Queue<Integer> queue = new LinkedList<>();
// 초기화: 거리를 무한대로 설정
Arrays.fill(distance, Integer.MAX_VALUE);
// 시작 노드 설정
queue.offer(start);
visited[start] = true;
distance[start] = 0;
while (!queue.isEmpty()) {
int current = queue.poll();
for (int neighbor : graph.get(current)) {
if (!visited[neighbor]) {
queue.offer(neighbor);
visited[neighbor] = true;
// 현재 노드까지의 거리 + 1
distance[neighbor] = distance[current] + 1;
}
}
}
return distance;
}
public static void main(String[] args) {
List<List<Integer>> graph = new ArrayList<>();
for (int i = 0; i < 7; i++) {
graph.add(new ArrayList<>());
}
graph.get(1).addAll(Arrays.asList(2, 3));
graph.get(2).addAll(Arrays.asList(1, 5, 6));
graph.get(3).addAll(Arrays.asList(1, 4));
graph.get(4).addAll(Arrays.asList(3, 6));
graph.get(5).add(2);
graph.get(6).addAll(Arrays.asList(2, 4));
int[] distances = bfsDistance(graph, 1);
System.out.println("노드 1로부터의 최단 거리:");
for (int i = 1; i < distances.length; i++) {
System.out.println("노드 " + i + ": " +
(distances[i] == Integer.MAX_VALUE ? "도달 불가" : distances[i]));
}
/* 출력:
노드 1: 0
노드 2: 1
노드 3: 1
노드 4: 2
노드 5: 2
노드 6: 2
*/
}
}
2차원 배열에서의 BFS
미로 탐색 (상하좌우 이동)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.*;
public class BFS2D {
// 상, 하, 좌, 우 이동 방향
static int[] dx = {-1, 1, 0, 0};
static int[] dy = {0, 0, -1, 1};
static class Point {
int x, y, distance;
Point(int x, int y, int distance) {
this.x = x;
this.y = y;
this.distance = distance;
}
}
/**
* 2D 그리드에서 BFS를 이용한 최단 경로 찾기
* @param grid 0: 이동 가능, 1: 벽
* @param startX 시작 x 좌표
* @param startY 시작 y 좌표
* @param endX 목표 x 좌표
* @param endY 목표 y 좌표
* @return 최단 거리 (도달 불가 시 -1)
*/
public static int bfs2D(int[][] grid, int startX, int startY,
int endX, int endY) {
int n = grid.length;
int m = grid[0].length;
boolean[][] visited = new boolean[n][m];
Queue<Point> queue = new LinkedList<>();
// 시작점 초기화
queue.offer(new Point(startX, startY, 0));
visited[startX][startY] = true;
while (!queue.isEmpty()) {
Point current = queue.poll();
// 목표 지점 도착
if (current.x == endX && current.y == endY) {
return current.distance;
}
// 4방향 탐색
for (int i = 0; i < 4; i++) {
int nx = current.x + dx[i];
int ny = current.y + dy[i];
// 범위 체크
if (nx >= 0 && nx < n && ny >= 0 && ny < m) {
// 이동 가능하고 미방문
if (grid[nx][ny] == 0 && !visited[nx][ny]) {
queue.offer(new Point(nx, ny, current.distance + 1));
visited[nx][ny] = true;
}
}
}
}
return -1; // 도달 불가
}
public static void main(String[] args) {
int[][] grid = {
{0, 0, 1, 0, 0},
{0, 1, 1, 0, 1},
{0, 0, 0, 0, 0},
{1, 0, 1, 1, 0},
{0, 0, 0, 0, 0}
};
int distance = bfs2D(grid, 0, 0, 4, 4);
System.out.println("최단 거리: " + distance);
// 출력: 최단 거리: 8
}
}
시각화
1
2
3
4
5
6
7
8
9
10
11
시작(0,0) → 목표(4,4)
0 0 X 0 0
0 X X 0 X
0 0 0 0 0
X 0 X X 0
0 0 0 0 0
최단 경로 (거리 8):
(0,0) → (0,1) → (1,0) → (2,0) → (2,1)
→ (2,2) → (2,3) → (2,4) → (3,4) → (4,4)
BFS와 DFS 비교
동일한 그래프에서 탐색 순서 비교

-
BFS 탐색 순서 (레벨 순회)
1 2 3 4
1 → 2 → 3 → 4 → 5 → 6 Level 0: [1] Level 1: [2, 3] Level 2: [4, 5, 6]
-
DFS 탐색 순서 (깊이 우선)
1 2 3 4
1 → 2 → 4 → 5 → 3 → 6 또는 1 → 3 → 6 → 2 → 4 → 5 (방문 순서에 따라 다름)
상세 비교표
| 비교 항목 | BFS | DFS |
|---|---|---|
| 자료구조 | 큐(Queue) | 스택(Stack) 또는 재귀 |
| 탐색 방식 | 가까운 노드부터 (넓게) | 깊은 노드부터 (깊게) |
| 최단 경로 | 보장 (가중치 없을 때) | 보장 안 됨 |
| 메모리 사용 | 많음 (모든 레벨 저장) | 적음 (현재 경로만) |
| 구현 난이도 | 중간 (큐 사용) | 쉬움 (재귀 가능) |
| 적용 사례 | 최단 거리, 레벨 탐색 | 경로 존재, 사이클 탐지 |
| 완전 탐색 | 가능 | 가능 |
| 무한 그래프 | 더 안전 | 스택 오버플로 위험 |
사용 시나리오 결정 플로우

주의사항 및 한계
주의사항
-
방문 체크 시점
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
// 잘못된 방법: 큐에서 꺼낼 때 체크 while (!queue.isEmpty()) { int cur = queue.poll(); if (visited[cur]) continue; // 중복 방문! visited[cur] = true; } // 올바른 방법: 큐에 넣을 때 체크 while (!queue.isEmpty()) { int cur = queue.poll(); for (int next : neighbors) { if (!visited[next]) { visited[next] = true; // 즉시 체크 queue.offer(next); } } }
-
메모리 초과 주의
- 완전 그래프나 밀집 그래프에서 큐가 급격히 커질 수 있음
- 2차원 배열이 크면 방문 배열도 메모리 차지
-
무한 루프 방지
- 반드시 방문 체크 필수
- 양방향 간선일 때 특히 주의
한계
-
가중치가 있는 그래프
- BFS는 가중치를 고려하지 못함
- 해결
- 다익스트라(Dijkstra) 알고리즘 사용
-
메모리 사용량
- 넓은 그래프에서 큐가 매우 커질 수 있음
- 해결
- 양방향 BFS 또는 반복적 깊이 증가 탐색(IDDFS)
-
최적 경로가 많을 때
- BFS는 하나의 최단 경로만 반환
- 해결
- 경로를 추적하는 로직 추가
정리
- BFS는 큐 자료구조를 사용하여 시작점에서 가까운 노드부터 레벨 순서로 탐색하는 알고리즘임
- 가중치가 없는 그래프에서 최단 경로를 보장하는 특징이 있음
- 시간 복잡도는 $O(V + E)$, 공간 복잡도는 $O(V)$임
- DFS에 비해 메모리 사용량이 많지만 최단 경로를 찾는 문제에 적합함
- 방문 체크는 큐에 삽입할 때 즉시 수행해야 중복 방문을 방지할 수 있음
- 2D 배열 탐색, 레벨 순회, 최단 거리 문제 등에 활용됨
- 가중치가 있는 그래프에는 다익스트라 알고리즘을 사용해야 함