백준 온라인 저지 | 2178번 미로 탐색 | C++
2178번: 미로 탐색
첫째 줄에 두 정수 N, M(2 ≤ N, M ≤ 100)이 주어진다. 다음 N개의 줄에는 M개의 정수로 미로가 주어진다. 각각의 수들은 붙어서 입력으로 주어진다.
www.acmicpc.net
1. 문제 접근
■ 1.1 4방위 너비 우선 탐색
0 혹은 1로 이루어진 미로에서 한 칸에서 다른 칸으로 이동할 때 서로 인접한 칸으로 이동할 수 있다고 되어 있다. 이것으로 미루어 보아 2차원 평면에서 한 정점에 인접한 사방위 너비 우선 탐색을 수행하는 것이 적절해 보인다.
■ 1.2 최단 거리
시작 위치 (1, 1)에서 출발하여 도착 위치 (N, M)으로 이동할 때 지나야 하는 최소 칸 수를 구한다는 말은 정점 (0, 0)에서 정점 (N-1, M-1)의 최단 거리를 구한다는 말과 동치이다. 시작 위치 (1, 1)에서 도착 위치 (N, M)까지 항상 이동할 수 있는 입력이 주어지므로 정점 (0, 0)에서 너비 우선 탐색을 수행한 다음, 정점 (N-1, M-1)와의 최단 거리를 구하면 된다. 이때 한 정점에서 다른 정점까지의 최단 거리를 저장할 2차원 벡터 dist를 사용한다.
2. 문제 풀이
- 미로 정보를 저장할 2차원 벡터 adj와 정점을 방문했는지와 시작 위치와의 최단 거리를 저장할 2차원 벡터 dist를 선언한다. adj와 dist의 크기는 N×M으로 설정하고 주어진 미로 정보로 adj를 초기화한다.
- 정점 (0, 0)을 큐에 넣고 dist[0][0]을 0으로 초기화한 다음, 너비 우선 탐색을 수행한다.
- 큐에 정점이 있으면 큐에서 정점을 꺼낸다.
- 이 정점으로부터 새로운 사방위 정점을 만든다. 새로운 정점이 배열의 범위를 벗어나거나, 이미 거리가 측정된 정점이거나, adj 정보가 0인 경우 다음 정점으로 넘어간다.
- 그 외 새로운 정점은 이전 정점의 dist에 1만큼 더하고 큐에 넣는다. 그리고 큐가 빌 때까지 위의 과정을 반복한다.
- 시작 위치 (1, 1)부터 도착 위치 (N, M)의 최단 거리인 dist[N-1][M-1]에 1을 더해 출력한다.
3. 문제 해결
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
using namespace std;
void makeGraph();
void bfs(int x, int y);
// 인접 행렬로 미로를 표현합니다.
vector<vector<int>> adj;
// 시작 정점 (0, 0)과의 거리를 저장합니다.
vector<vector<int>> dist;
// 너비 우선 탐색에 사용할 큐입니다.
queue<pair<int, int>> q;
int N, M;
// 인접한 상하좌우 정점의 좌표를 만드는 데 사용합니다.
int dx[4] = { 1, 0, -1, 0 };
int dy[4] = { 0, 1, 0, -1 };
int main(void) {
ios::sync_with_stdio(false);
cin.tie(nullptr);
makeGraph();
bfs(0, 0);
cout << dist[N - 1][M - 1] + 1 << '\n';
return 0;
}
// 주어진 미로 데이터로 인접 행렬 그래프를 만듭니다.
void makeGraph() {
cin >> N >> M;
// 벡터 adj, dist의 크기를 N * M으로 설정합니다.
adj = vector<vector<int>>(N, vector<int>(M, 0));
dist = vector<vector<int>>(N, vector<int>(M, -1));
// 주어진 미로 정보로 adj를 초기화합니다.
for (int i = 0; i < N; ++i) {
string maze;
cin >> maze;
// ASCII 코드의 문자 '0'을 빼서 0 혹은 1로 정점을 초기화합니다.
for (int j = 0; j < M; ++j) {
adj[i][j] = maze[j] - '0';
}
}
}
// 너비 우선 탐색 알고리즘을 구현합니다.
void bfs(int x, int y) {
// 너비 우선 탐색을 시작할 정점을 큐에 집어넣습니다.
dist[x][y] = 0;
q.push({ x, y });
while (!q.empty()) {
// 큐에서 정점을 꺼냅니다.
pair<int, int> pos = q.front();
q.pop();
// 사방위 정점을 확인합니다.
for (int i = 0; i < 4; ++i) {
int nx = pos.first + dx[i];
int ny = pos.second + dy[i];
// 새 정점의 좌표가 배열의 범위를 벗어나는 경우 다음 정점으로 건너뜁니다.
if (nx < 0 || ny < 0 || nx >= N || ny >= M) {
continue;
}
// 새 정점의 정보가 0이거나 이미 방문한 경우 다음 정점으로 건너뜁니다.
if (!adj[nx][ny] || dist[nx][ny] > -1) {
continue;
}
// 새 정점의 최단 거리는 이전 정점의 최단 거리에 1만큼 더한 것입니다.
dist[nx][ny] = dist[pos.first][pos.second] + 1;
// 그외 정점은 큐에 집어넣습니다.
q.push({ nx, ny });
}
}
}
4. 문제 고찰
■ 4.1 붙어서 주어지는 입력
문제에서 주어지는 0 혹은 1의 데이터가 공백으로 구분되어 있다면 이중 for문과 cin 함수를 사용하여 벡터 adj를 초기화할 수 있다. 하지만 0 혹은 1의 데이터가 공백으로 구분되지 않고 연속된 수로 주어진다면 cin만으로는 초기화할 수 없다. 대신에 문자열로 데이터를 받고 각 문자마다 문자 '0'을 빼주어 adj를 초기화해야 한다. 문자 '0'의 ASCII 코드는 48이고 문자 '1'의 ASCII 코드는 49이므로 각 문자에 문자 '0'을 빼면 정수 0(48 - 48 = 0)과 정수 1(49 - 48 = 1)을 구할 수 있다.
// 공백으로 구분된 0과 1 데이터는 다음과 같이 초기화합니다.
for (int i = 0; i < N; ++i) {
for (int j = 0; j < M; ++j) {
cin >> adj[i][j];
}
}
// 공백으로 구분되지 않은 연속된 0과 1 데이터는 다음과 같이 초기화합니다.
for (int i = 0; i < N; ++i) {
string data;
cin >> data;
// 문자열 data에서 ASCII 코드의 문자 '0'을 각각 빼주어 adj를 초기화합니다.
for (int j = 0; j < M; ++j) {
adj[i][j] = string[j] - '0';
}
}
■ 4.2 최단 거리 혹은 최소 이동 횟수
다른 너비 우선 탐색과 달리 최단 거리를 구하는 BFS 문제는 vector<vector<bool>> visited 대신 vector<vector<int>> dist를 사용한다. visited는 정점의 방문 여부를 true와 false로 나타냈지만, dist는 정점을 방문하지 않았으면 -1, 정점을 방문했다면 시작 위치와의 최단 거리인 양수 k로 나타낼 수 있기 때문이다.
한편, 어떤 한 정점의 최단 거리는 이전 정점의 최단 거리에 1만큼 더한 것이다. 사방위 정점은 중앙 정점으로부터 한 칸씩 떨어져 있기 때문이다. 이러한 속성을 사용하면 이전 정점의 최단 거리인 dist[pos.first][pos.second]에 1만큼 더해 새로운 정점의 최단 거리인 dist[nx][ny]를 구할 수 있다.
'Data Structures and Algorithms > 백준 온라인 저지' 카테고리의 다른 글
백준 온라인 저지 | 10872번 팩토리얼 | C++ (0) | 2022.08.04 |
---|---|
백준 온라인 저지 | 10825번 국영수 | C++ (0) | 2022.07.24 |
백준 온라인 저지 | 1926번 그림 | C++ (0) | 2022.03.08 |
백준 온라인 저지 | 5397번 키로거 | C++ (0) | 2022.02.27 |
백준 온라인 저지 | 1406번 에디터 | C++ (0) | 2022.02.26 |
댓글