너비 우선 탐색 (Breadth-First Search, BFS) 는 그래프 G=(V, E) 에서 시작 정점 s 로부터 가까운 정점부터 순서대로 방문하는 알고리즘. 큐 (FIFO) 를 사용해 level order 보장.
무가중 그래프에서 최단 경로 O(V + E) 를 구하는 표준 기법.
문제 상황과 동기
“정점 s 에서 모든 정점까지의 최단 거리 (간선 개수) 를 구하라.”
naive DFS: 깊이 우선으로 가면 먼 경로를 먼저 탐색할 수 있음. 최단 보장 안 됨.
BFS: 거리 d 인 정점을 모두 방문한 뒤 d+1 로 진행. 먼저 도달한 경로가 항상 최단.
핵심 통찰: 큐 FIFO 순서 = 거리 비감소 순서. 첫 방문 시각 = 최단 거리.
시각화
핵심 아이디어
invariant: 큐에 있는 정점들의 거리는 최대 1 차이. 큐 앞: 거리 d, 큐 뒤: 거리 d 또는 d+1.
dist[s] = 0큐: [s]while 큐 not empty: u = 큐.pop_front() # 거리 d for v in adj[u]: if not visited[v]: visited[v] = true dist[v] = dist[u] + 1 # 거리 d+1 큐.push_back(v)
첫 도달 시각이 곧 최단 거리. 다시 방문 불가 (visited[v] 로 막음).
알고리즘
BFS(G, s): Q ← new queue visited[s] = true dist[s] = 0 parent[s] = null Q.enqueue(s) while Q is not empty: u = Q.dequeue() for v in G.adj[u]: if not visited[v]: visited[v] = true dist[v] = dist[u] + 1 parent[v] = u Q.enqueue(v) return dist, parent
구현
// BFS 최단 거리, O(V + E)#include <bits/stdc++.h>using namespace std;vector<int> adj[100005];int dist[100005];bool visited[100005];void bfs(int s) { queue<int> q; visited[s] = true; dist[s] = 0; q.push(s); while (!q.empty()) { int u = q.front(); q.pop(); for (int v : adj[u]) { if (!visited[v]) { visited[v] = true; dist[v] = dist[u] + 1; q.push(v); } } }}int main() { int n, m, s; cin >> n >> m >> s; for (int i = 0; i < m; i++) { int u, v; cin >> u >> v; adj[u].push_back(v); adj[v].push_back(u); } fill(dist, dist + n + 1, -1); bfs(s); for (int i = 1; i <= n; i++) cout << dist[i] << " "; cout << "\n";}
# collections.deque 로 O(1) popfrom collections import dequeimport sysinput = sys.stdin.readlinen, m, s = map(int, input().split())adj = [[] for _ in range(n + 1)]for _ in range(m): u, v = map(int, input().split()) adj[u].append(v) adj[v].append(u)dist = [-1] * (n + 1)q = deque([s])dist[s] = 0while q: u = q.popleft() for v in adj[u]: if dist[v] == -1: dist[v] = dist[u] + 1 q.append(v)print(*dist[1:])
// ArrayDeque 사용, O(V + E)import java.util.*;import java.io.*;public class Main { static List<Integer>[] adj; static int[] dist; static void bfs(int s) { Queue<Integer> q = new ArrayDeque<>(); dist[s] = 0; q.add(s); while (!q.isEmpty()) { int u = q.poll(); for (int v : adj[u]) { if (dist[v] == -1) { dist[v] = dist[u] + 1; q.add(v); } } } } public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); StringTokenizer st = new StringTokenizer(br.readLine()); int n = Integer.parseInt(st.nextToken()); int m = Integer.parseInt(st.nextToken()); int s = Integer.parseInt(st.nextToken()); adj = new ArrayList[n + 1]; for (int i = 0; i <= n; i++) adj[i] = new ArrayList<>(); dist = new int[n + 1]; Arrays.fill(dist, -1); for (int i = 0; i < m; i++) { st = new StringTokenizer(br.readLine()); int u = Integer.parseInt(st.nextToken()); int v = Integer.parseInt(st.nextToken()); adj[u].add(v); adj[v].add(u); } bfs(s); for (int i = 1; i <= n; i++) System.out.print(dist[i] + " "); System.out.println(); }}
stdin
5 5 11 21 32 43 44 5
결과
0 1 1 2 3
복잡도
항목
값
시간 (인접 리스트)
O(V + E)
시간 (인접 행렬)
O(V²)
공간
O(V) (큐 + visited + dist)
최단 경로 보장
✓ (무가중 그래프)
변형
알고리즘
변경점
0/1 BFS
가중치 0/1 만 있을 때. deque 양쪽 삽입: 0 간선은 push_front
Multi-source BFS
시작 정점 여러 개. 모두 큐에 넣고 dist=0 초기화
Flood Fill
2D grid 에서 BFS. 상하좌우 4방향 인접
Layer BFS
같은 거리 정점을 한 layer 로 묶어 처리 (while 안에 for size)
함정
1. visited 대신 dist 로 판정
dist[v] == -1 로 미방문 체크하면 visited 배열 불필요. 더 간결.
2. 큐 초기화 실수
시작 정점 s 를 큐에 넣고 visited[s] = true 도 꼭 해야 함. 안 하면 무한 루프.
3. 양방향 간선 입력
무향 그래프는 u-v 간선 하나를 adj[u].push(v), adj[v].push(u) 두 번 추가.
4. parent 배열 미관리
경로 복원하려면 parent[v] = u 기록 필요. s 에서 v 까지 경로를 parent 를 따라 역추적.
5. 거리 vs 방문 순서
거리는 간선 개수. 방문 순서 (discovery time) 와 다름. 필요한 값 명확히.
💬 댓글