다중 소스 BFS (Multi-Source BFS)

여러 시작점에서 동시에 BFS를 실행하는 기법으로, 모든 소스에 대한 최단 거리를 O(V+E) 한 번으로 구하는 방법과 대표 활용 패턴을 알아봅니다.

· 4 min read · PALDYN Team

지난 글에서 DFS의 동작 원리와 사이클 감지를 다뤘습니다. 이번에는 BFS의 강력한 변형인 **다중 소스 BFS(Multi-Source BFS)**를 소개합니다. 여러 시작점에서 동시에 확산하는 이 기법은 단일 BFS K번 실행의 O(K×(V+E))를 O(V+E) 로 줄여줍니다.

문제 예시

“그리드의 각 칸에서 가장 가까운 소스(S) 셀까지의 거리를 구하라.”

단순 접근: 각 소스 S에서 BFS를 돌린다 → O(K × R×C)
다중 소스 BFS: 모든 S를 동시에 큐에 넣고 BFS 한 번 → O(R×C)

다중 소스 BFS 개념

핵심 아이디어

모든 소스에서 동시 출발합니다. 각 소스는 이미 거리 0인 상태로 큐에 들어가므로, BFS가 퍼져 나가면서 모든 셀에 가장 가까운 소스까지의 거리가 자동으로 채워집니다.

이는 가상 슈퍼소스(virtual super-source) 패턴과 동일합니다. 모든 실제 소스에 가중치 0 간선으로 연결된 슈퍼소스 S를 만들고, S에서 단일 BFS를 실행하는 것과 같습니다.

구현

function multiSourceBfs(grid) {
  const R = grid.length, C = grid[0].length;
  const dist = Array.from({ length: R }, () => new Array(C).fill(-1));
  const queue = [];

  // ① 모든 소스를 거리 0으로 초기화하며 큐에 투입
  for (let r = 0; r < R; r++)
    for (let c = 0; c < C; c++)
      if (grid[r][c] === 'S') {
        dist[r][c] = 0;
        queue.push([r, c]);
      }

  // ② 일반 BFS와 동일하게 진행
  const dr = [-1, 1, 0, 0];
  const dc = [0, 0, -1, 1];
  let head = 0;

  while (head < queue.length) {
    const [r, c] = queue[head++];
    for (let d = 0; d < 4; d++) {
      const nr = r + dr[d], nc = c + dc[d];
      if (nr >= 0 && nr < R && nc >= 0 && nc < C
          && grid[nr][nc] !== '#' && dist[nr][nc] === -1) {
        dist[nr][nc] = dist[r][c] + 1;
        queue.push([nr, nc]);
      }
    }
  }
  return dist; // dist[r][c]: 가장 가까운 소스까지 거리
}

다중 소스 BFS 구현

대표 문제

01 매트릭스

matrix[i][j]가 0 또는 1인 그리드에서 각 1 셀의 가장 가까운 0 셀까지 거리를 구합니다.

function updateMatrix(matrix) {
  const R = matrix.length, C = matrix[0].length;
  const dist = matrix.map(row => row.map(v => v === 0 ? 0 : -1));
  const queue = [];

  // 모든 0 셀이 소스
  for (let r = 0; r < R; r++)
    for (let c = 0; c < C; c++)
      if (matrix[r][c] === 0) queue.push([r, c]);

  // BFS 확산
  const dr = [-1, 1, 0, 0], dc = [0, 0, -1, 1];
  let head = 0;
  while (head < queue.length) {
    const [r, c] = queue[head++];
    for (let d = 0; d < 4; d++) {
      const nr = r + dr[d], nc = c + dc[d];
      if (nr >= 0 && nr < R && nc >= 0 && nc < C && dist[nr][nc] === -1) {
        dist[nr][nc] = dist[r][c] + 1;
        queue.push([nr, nc]);
      }
    }
  }
  return dist;
}

썩는 귤 (Rotting Oranges)

처음부터 썩은 귤(2)이 소스입니다. BFS로 확산하면서 모든 신선한 귤(1)이 언제 감염되는지 구합니다. 도달 못하는 1이 있으면 -1을 반환합니다.

from collections import deque

def orangesRotting(grid):
    R, C = len(grid), len(grid[0])
    q = deque()
    fresh = 0
    for r in range(R):
        for c in range(C):
            if grid[r][c] == 2:
                q.append((r, c, 0))
            elif grid[r][c] == 1:
                fresh += 1
    time = 0
    for r, c, t in q:
        for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
            nr, nc = r+dr, c+dc
            if 0<=nr<R and 0<=nc<C and grid[nr][nc]==1:
                grid[nr][nc] = 2
                fresh -= 1
                time = max(time, t+1)
                q.append((nr, nc, t+1))
    return -1 if fresh > 0 else time

복잡도

시간공간
다중 소스 BFSO(V + E)O(V)
소스 K개 개별 BFSO(K × (V+E))O(V)

지난 글: 그래프 DFS

다음 글: 0-1 BFS


읽어주셔서 감사합니다. 😊