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

격자 그래프 (Grid Graph)

· 수정 · 📖 약 3분 · 1,022자/단어 #algorithm #graph #grid #bfs #dfs
grid graph, 격자 그래프, grid bfs, grid dfs

정의

격자 그래프 (Grid Graph)2차원 배열 (N × M 격자) 위의 그래프. 각 칸이 노드, 인접한 칸 사이가 간선. 4방향 또는 8방향 이동.

격자 위 최단 경로, 연결 영역, 탐색은 BFS/DFS 의 정형 패턴. PS 에서 매우 빈번.

문제 상황과 동기

2D 맵 / 미로 / 게임 보드에서 탐색 / 최단 경로.

  • 일반 그래프: 인접 리스트 adj[u] = [v1, v2, ...].
  • 격자 그래프: 2차원 배열 grid[x][y]. 인접 = 4방향 (상하좌우) 또는 8방향 (대각선 포함).

격자의 장점:

  • 인접 관계가 암묵적 (explicit edge list 불필요).
  • 좌표 (x, y) 로 직관적.
  • 캐시 지역성 좋음.

핵심 통찰: 격자는 암묵적 그래프 (implicit graph). 간선을 저장하지 않고 좌표 연산 (x±1, y±1) 으로 인접 노드 생성.

응용:

  • 미로 최단 경로 (BFS)
  • 섬 개수 세기 (DFS)
  • 지뢰 찾기, 체스, 게임 AI

시각화

핵심 아이디어

invariant: 칸 (x, y) 의 인접 칸은 (x+dx, y+dy), dx/dy ∈ 방향 집합.

4방향 (상하좌우)

dx = [-1, 1,  0, 0]
dy = [ 0, 0, -1, 1]

neighbors(x, y) = [(x+dx[i], y+dy[i]) for i in 0..3 if valid]

8방향 (대각선 포함)

dx = [-1, -1, -1,  0, 0,  1, 1, 1]
dy = [-1,  0,  1, -1, 1, -1, 0, 1]

경계 체크

valid(x, y) = (0 <= x < N) and (0 <= y < M) and (grid[x][y] ≠ wall)

알고리즘

BFS (최단 경로)

grid_bfs(grid, sx, sy, tx, ty):
    queue q
    dist[sx][sy] = 0
    q.push((sx, sy))
    
    while q not empty:
        (x, y) = q.pop()
        
        if x == tx and y == ty:
            return dist[x][y]
        
        for (dx, dy) in directions:
            nx = x + dx, ny = y + dy
            if valid(nx, ny) and not visited[nx][ny]:
                visited[nx][ny] = true
                dist[nx][ny] = dist[x][y] + 1
                q.push((nx, ny))
    
    return -1  # unreachable

DFS (연결 영역)

grid_dfs(x, y):
    if not valid(x, y) or visited[x][y]:
        return
    
    visited[x][y] = true
    area_size++
    
    for (dx, dy) in directions:
        grid_dfs(x + dx, y + dy)

구현

// 격자 그래프 BFS, 최단 거리
#include <bits/stdc++.h>
using namespace std;

int dx[] = {-1, 1, 0, 0};
int dy[] = {0, 0, -1, 1};

int grid_bfs(vector<string>& grid, int sx, int sy, int tx, int ty) {
  int n = grid.size(), m = grid[0].size();
  vector<vector<int>> dist(n, vector<int>(m, -1));
  queue<pair<int, int>> q;
  
  q.push({sx, sy});
  dist[sx][sy] = 0;
  
  while (!q.empty()) {
      auto [x, y] = q.front(); q.pop();
      
      if (x == tx && y == ty)
          return dist[x][y];
      
      for (int i = 0; i < 4; i++) {
          int nx = x + dx[i], ny = y + dy[i];
          if (nx >= 0 && nx < n && ny >= 0 && ny < m &&
              grid[nx][ny] != '#' && dist[nx][ny] == -1) {
              dist[nx][ny] = dist[x][y] + 1;
              q.push({nx, ny});
          }
      }
  }
  return -1;
}

int main() {
  int n, m, sx, sy, tx, ty;
  cin >> n >> m >> sx >> sy >> tx >> ty;
  
  vector<string> grid(n);
  for (auto& s : grid) cin >> s;
  
  cout << grid_bfs(grid, sx, sy, tx, ty) << "\n";
}
stdin
5 5 0 0 4 4
.....
.###.
.....
.###.
.....
결과
8

복잡도

항목
시간 (BFS)O(N × M)
시간 (DFS)O(N × M)
공간O(N × M)
간선 수최대 4NM (4방향) 또는 8NM (8방향)

각 칸 최대 1회 방문. 인접 칸 탐색 O(1).

변형 / 활용

1. 최단 경로 (BFS)

가중치 없는 격자 최단 경로. 미로 탈출.

int shortest_path = grid_bfs(grid, sx, sy, tx, ty);

2. 섬 개수 / 연결 영역 (DFS)

1 로 연결된 영역 개수.

int count_islands(vector<vector<int>>& grid) {
    int cnt = 0;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            if (grid[i][j] == 1 && !visited[i][j]) {
                dfs(i, j);
                cnt++;
            }
        }
    }
    return cnt;
}

3. 다익스트라 (가중치 격자)

각 칸에 비용. 우선순위 큐 사용.

priority_queue<tuple<int, int, int>> pq;  // (cost, x, y)
pq.push({0, sx, sy});
dist[sx][sy] = 0;

while (!pq.empty()) {
    auto [cost, x, y] = pq.top(); pq.pop();
    if (cost > dist[x][y]) continue;
    for (auto [dx, dy] : dirs) {
        int nx = x + dx, ny = y + dy, ncost = cost + grid[nx][ny];
        if (valid(nx, ny) && ncost < dist[nx][ny]) {
            dist[nx][ny] = ncost;
            pq.push({ncost, nx, ny});
        }
    }
}

4. 0-1 BFS (벽 부수기)

일반 이동 (비용 0), 벽 부수기 (비용 1). deque 사용.

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;
        int w = (grid[nx][ny] == '#' ? 1 : 0);
        if (valid(nx, ny) && 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});
        }
    }
}

5. 8방향 이동

대각선 포함. 체스 킹, 지뢰 찾기.

int dx[] = {-1, -1, -1,  0, 0,  1, 1, 1};
int dy[] = {-1,  0,  1, -1, 1, -1, 0, 1};

6. 다중 출발점 BFS

여러 시작점 동시 탐색. 토마토 문제.

queue<pair<int, int>> q;
for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
        if (grid[i][j] == 1) {  // 초기 익은 토마토
            q.push({i, j});
            dist[i][j] = 0;
        }
    }
}
// BFS 진행

함정

1. 경계 체크 누락

nx >= 0 && nx < n && ny >= 0 && ny < m 빼먹으면 segfault. 함수로 분리 권장.

bool valid(int x, int y) {
    return x >= 0 && x < n && y >= 0 && y < m && grid[x][y] != '#';
}

2. 방문 체크 타이밍

BFS 에서 큐에 넣을 때 즉시 visited[nx][ny] = true. pop 할 때 체크하면 중복 삽입.

// 잘못 (중복)
if (!visited[nx][ny]) q.push({nx, ny});

// 올바름
if (!visited[nx][ny]) {
    visited[nx][ny] = true;
    q.push({nx, ny});
}

3. 좌표 순서 (x, y) vs (row, col)

grid[x][y]grid[row][col] 인지 확인. x=행, y=열 또는 반대. 일관성 유지.

4. 대각선 이동 시 코너 컷

8방향 이동 시, 대각선으로 벽 사이를 뚫고 가는지 체크.

// 대각선 (x+1, y+1) 이동 시, (x+1, y) 와 (x, y+1) 이 벽이면 막기
if (grid[x+1][y] == '#' && grid[x][y+1] == '#')
    continue;  // 코너 컷 방지

5. 거리 초기화

dist[x][y] = -1 (미방문) vs dist[x][y] = INF (최단 거리). BFS 는 -1, Dijkstra 는 INF.

BOJ 연습 문제

번호제목정답률링크
BOJ 1012유기농 배추45.3%kokoa-lab
BOJ 2178미로 탐색42.1%kokoa-lab
BOJ 2206벽 부수고 이동하기24.8%kokoa-lab
BOJ 2667단지번호붙이기44.5%kokoa-lab
BOJ 7576토마토38.9%kokoa-lab

참고

이 글의 용어 (5개)
0-1 BFSalgorithm
정의 0-1 BFS 는 간선 가중치가 0 또는 1만 존재하는 그래프에서 최단 경로를 O(V + E) 에 구하는 알고리즘. deque 를 사용해 가중치 0 간선은 앞에, 가중치 1…
깊이 우선 탐색 (DFS)algorithm
정의 깊이 우선 탐색 (Depth-First Search, DFS) 는 그래프 G=(V, E) 에서 갈 수 있는 만큼 깊이 들어가다가 막히면 백트래킹하는 알고리즘. 스택 (LIF…
너비 우선 탐색 (BFS)algorithm
정의 너비 우선 탐색 (Breadth-First Search, BFS) 는 그래프 G=(V, E) 에서 시작 정점 s 로부터 가까운 정점부터 순서대로 방문하는 알고리즘. 큐 (F…
다익스트라 알고리즘 (Dijkstra's Algorithm)algorithm
정의 다익스트라 알고리즘 (Dijkstra's Algorithm) 은 음이 아닌 가중치 그래프에서 단일 시작점 s 로부터 모든 정점까지의 최단 거리를 찾는 그리디 알고리즘. Ed…
Flood Fillalgorithm
정의 Flood Fill 은 격자 (grid) 또는 이미지에서 특정 위치와 연결된 같은 색 영역을 모두 칠하는 알고리즘. 그래픽 소프트웨어의 "페인트 버킷 (paint bucke…

이 개념을 다룬 위키 페이지 (2)

💬 댓글

사이트 검색 / 명령어

검색

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