본문으로 건너뛰기
김신건의 로그

0-1 BFS

· 수정 · 📖 약 3분 · 1,059자/단어 #algorithm #graph #shortest-path #0-1-bfs
0/1 BFS, 0-1 bfs, 0-1 너비 우선 탐색, deque BFS

정의

0-1 BFS 는 간선 가중치가 0 또는 1만 존재하는 그래프에서 최단 경로를 O(V + E) 에 구하는 알고리즘. deque 를 사용해 가중치 0 간선은 앞에, 가중치 1 간선은 뒤에 추가하여 BFS 의 단조성을 유지한다.

Dial’s algorithm (1969) 의 특수 케이스로, 다익스트라의 우선순위 큐 대신 deque 로 충분.

문제 상황과 동기

가중치 그래프의 최단 경로 알고리즘:

  • BFS: 간선 가중치가 모두 같을 때 (unweighted). O(V + E).
  • Dijkstra: 간선 가중치 ≥ 0. O((V + E) log V) (우선순위 큐 사용).
  • 0-1 BFS: 간선 가중치가 0 또는 1만. O(V + E).

예: 격자에서 “벽을 최대 1개 부수고 이동” 류 문제. 일반 이동 (비용 0), 벽 부수기 (비용 1). Dijkstra 는 느리고 BFS 는 불가능. 0-1 BFS 가 딱 맞는다.

핵심 통찰: 가중치가 0, 1 두 종류만 있으면 거리 증가 순서가 최대 2단계 (d, d+1) 만 큐에 공존. deque 앞쪽 = d, 뒷쪽 = d+1. BFS 의 단조성 유지.

시각화

핵심 아이디어

invariant: deque 내 거리가 비감소 (non-decreasing). 앞쪽이 항상 최소.

dist[v] = ∞ for all v
dist[s] = 0
deque dq = {s}

while dq not empty:
    u = dq.pop_front()
    for (v, w) in adj[u]:   # w ∈ {0, 1}
        if dist[u] + w < dist[v]:
            dist[v] = dist[u] + w
            if w == 0:
                dq.push_front(v)
            else:
                dq.push_back(v)

왜 작동하는가:

  • 가중치 0: 거리 그대로. 앞에 추가 → 먼저 처리.
  • 가중치 1: 거리 +1. 뒤에 추가 → 나중 처리.
  • deque 내부 거리가 [d, d, …, d+1, d+1, …] 형태로 정렬 유지.
  • BFS 와 동일하게 각 노드 최대 1회 처리 (최초 pop 시 최단).

Dijkstra 와의 차이:

알고리즘자료구조시간 복잡도
DijkstraPriority QueueO((V + E) log V)
0-1 BFSDequeO(V + E)

가중치 범위가 {0, 1} 로 제한되면 deque 의 앞/뒤 삽입 O(1) 이 heap 의 O(log V) 보다 빠르다.

알고리즘

0-1-BFS(G, s):
    dist[v] ← ∞ for all v ∈ V
    dist[s] ← 0
    dq ← deque()
    dq.push_back(s)
    
    while dq not empty:
        u ← dq.pop_front()
        
        for each edge (u, v, w) in adj[u]:  # w ∈ {0, 1}
            if dist[u] + w < dist[v]:
                dist[v] ← dist[u] + w
                
                if w == 0:
                    dq.push_front(v)
                else:
                    dq.push_back(v)
    
    return dist

구현

// 0-1 BFS, deque push_front (w=0) / push_back (w=1)
#include <bits/stdc++.h>
using namespace std;
const int INF = 1e9;

int main() {
  int n, m, s, t;
  cin >> n >> m >> s >> t;  // n nodes, m edges, source s, target t
  
  vector<vector<pair<int, int>>> adj(n + 1);
  for (int i = 0; i < m; i++) {
      int u, v, w;
      cin >> u >> v >> w;  // w ∈ {0, 1}
      adj[u].push_back({v, w});
      adj[v].push_back({u, w});  // undirected
  }
  
  vector<int> dist(n + 1, INF);
  deque<int> dq;
  dist[s] = 0;
  dq.push_back(s);
  
  while (!dq.empty()) {
      int u = dq.front(); dq.pop_front();
      
      for (auto [v, w] : adj[u]) {
          if (dist[u] + w < dist[v]) {
              dist[v] = dist[u] + w;
              if (w == 0) dq.push_front(v);
              else dq.push_back(v);
          }
      }
  }
  
  cout << (dist[t] == INF ? -1 : dist[t]) << "\n";
}
stdin
5 7 1 5
1 2 0
1 3 1
2 3 0
2 4 1
3 4 0
3 5 1
4 5 0
결과
2

복잡도

항목
시간 (최선)O(V + E)
시간 (평균)O(V + E)
시간 (최악)O(V + E)
공간O(V)
조건간선 가중치 ∈ {0, 1}

각 노드는 deque 에 최대 2회 삽입 (가중치 0 으로 한 번, 1 로 한 번). 따라서 여전히 O(V + E).

변형 / 활용

1. 0-K BFS

간선 가중치가 0..K (K 작음) 이면 deque 대신 K+1 개 bucket 사용. O(V + E) 유지. K=1 이 0-1 BFS.

2. 격자 그래프 + 벽 부수기

격자에서 일반 이동 (비용 0), 벽 부수기 (비용 1). 0-1 BFS 로 “벽 최소 부숨” 경로.

// 격자 0-1 BFS, (x, y, 벽 부순 횟수) 상태
dist[x][y] = INF;
deque<pair<int,int>> dq;
dq.push_back({sx, sy});
dist[sx][sy] = 0;

while (!dq.empty()) {
    auto [x, y] = dq.front(); dq.pop_front();
    for (auto [dx, dy] : dirs) {
        int nx = x + dx, ny = y + dy;
        if (!valid(nx, ny)) continue;
        int w = (grid[nx][ny] == '#' ? 1 : 0);
        if (dist[x][y] + w < dist[nx][ny]) {
            dist[nx][ny] = dist[x][y] + w;
            if (w == 0) dq.push_front({nx, ny});
            else dq.push_back({nx, ny});
        }
    }
}

3. 최대 K 번 벽 부수기

상태를 (x, y, 부순 횟수 k) 로 확장. 3차원 배열 dist[x][y][k]. 0-1 BFS 그대로.

함정

1. deque 방향 착각

w == 0 일 때 push_front, w == 1 일 때 push_back. 반대로 하면 단조성 깨져 틀림.

2. 중복 방문 처리

deque 에 같은 노드가 여러 번 들어갈 수 있다. dist[v] 갱신 후 push. pop 시 이미 최소 거리면 skip.

while (!dq.empty()) {
    int u = dq.front(); dq.pop_front();
    // 이미 처리된 노드 (거리 갱신 없음)
    // if (processed[u]) continue;  // 선택적
    for (auto [v, w] : adj[u]) { ... }
}

3. 가중치 범위 확인

간선 가중치가 {0, 1} 이 아니면 0-1 BFS 불가능. 2 이상 있으면 Dijkstra.

4. 음수 가중치

0-1 BFS 는 음수 가중치 불가. 가중치 0, 1 ≥ 0 이므로 OK. 음수 있으면 Bellman-Ford.

BOJ 연습 문제

번호제목정답률링크
BOJ 1261알고스팟33.2%kokoa-lab
BOJ 13549숨바꼭질 328.5%kokoa-lab
BOJ 14442벽 부수고 이동하기 223.7%kokoa-lab
BOJ 17837새로운 게임 222.8%kokoa-lab

참고

이 글의 용어 (3개)
격자 그래프 (Grid Graph)algorithm
정의 격자 그래프 (Grid Graph) 는 2차원 배열 (N × M 격자) 위의 그래프. 각 칸이 노드, 인접한 칸 사이가 간선. 4방향 또는 8방향 이동. 격자 위 최단 경로…
너비 우선 탐색 (BFS)algorithm
정의 너비 우선 탐색 (Breadth-First Search, BFS) 는 그래프 G=(V, E) 에서 시작 정점 s 로부터 가까운 정점부터 순서대로 방문하는 알고리즘. 큐 (F…
다익스트라 알고리즘 (Dijkstra's Algorithm)algorithm
정의 다익스트라 알고리즘 (Dijkstra's Algorithm) 은 음이 아닌 가중치 그래프에서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 그리디 알고리즘. Ed…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기