격자 그래프 (Grid Graph)
정의
격자 그래프 (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";
}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…
💬 댓글