0-1 너비 우선 탐색 알고리즘으로 풀 수 있는 문제다.
풀면서 두 가지 이슈로 삽질했던 경험을 정리해 두려 한다.
2차원 배열 또는 행렬을 다룰 때, map[row][col]
과 같이 첫 번째 인덱스가 행, 두 번째 인덱스가 열을 가리킨다.
BFS를 활용한 미로찾기 등에서 자주 등장하는 패턴인데, 상하좌우를 이동하기 위해서 아래와 같이 dx
, dy
배열을 사용한다.
int dx[4] = {0, 1, 0, -1};
int dy[4] = {-1, 0, 1, 0};
문제는 x, y가 좌표평면 상에서 가로, 세로에 해당하는데, 행렬에서는 첫 번째 인덱스인 row가 세로에 해당한다는 것이다. map[y][x]
보다는 map[x][y]
가 자연스럽기 때문에 많이 헷갈렸다. 심지어는 행렬임에도 map[x][y]
성분을 (x, y)
로 표현하기도 한다.
근데 헷갈려도 크게 상관 없다는 것이 결론이다. 위의 두 배열은 아래와 같이 사용되는데
int nextRow = row + dx[i];
int nextCol = col + dy[i];
해석하기에 따라 상하좌우의 순서만 달라질 뿐이다.
x
,y
를
위 코드는 x
, y
의 자연스러운 순서를 위해서 행과 열로 해석한 것이라 볼 수 있다.
결과는 같더라도 분명히 다른 코드이다. 코드가 어떻게 동작하는지 명확히 알아야 한다.
나처럼 헷갈려 한 사람이 꽤 있는듯하다. 혼동을 야기할 수 있는 이런 것들을 좀 통일해주면 어떻겠냐는 질문에 대한 답을 아래 링크에서 해결할 수 있다.
변수나 문자에 의미를 부여하는 것은 사람이다. 스스로 기준을 정해서 혼란스럽지 않으면 어떤 문자를 쓰든지 상관없다. 하지만 부여한 의미를 혼자서만 알면 좋은 코드가 아니기 때문에, 가독성을 위해서 관습을 따르는 것이 좋다. 개발할 때 변수명 정하는게 가장 힘들다는 것이 괜히 나오는 말이 아니다.
'입력따위' 에 관해서 고민하는 단계는 뛰어 넘었다고 자만하던 나를... 일깨워 준 좋은 문제다.
이 문제의 입력이 어떻게 들어오는지부터 보면, 아주 불친절하게도 숫자를 한 줄(행)씩 붙여서 넣어준다. 숫자 하나하나를 내가 떼어내서 2차원 배열에 넣어줘야 한다.
case 1 case 2
011 0001
111 1000
110
되게 쉬운 문제인데, 내가 하고싶었던 것은 C언어의 scanf
가 아니라 C++의 cin
으로 해결하는 것이었다. C의 입출력이나 구조체를 사용하면 편리하지만, C++이라면 가급적 C++ 문법만 사용하고 싶은 마음이 있다. cin
으로 해결하고자 하면서 드러난 부족한 지식을 정리해보자.
char map[101][101];
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> map[i][j];
}
}
연속된 문자열 입력을 받을 때, 문자 하나씩 뜯어내기 위해서 char
형 배열을 선언하고 입력받는 방식을 많이 사용했었다. 같은 방식으로 입력을 받은 것인데, 제대로 동작하지 않았다.
그런데 출력해보니 제대로 0과 1이 잘 들어가 있지 않은가? 대체 왜 안 되는거지..
char
형은 임베디드에서 메모리 공간을 절약하기 위해서 int
형 대신 많이 쓰인다. char
형 변수에 값을 할당하고, 연산을 통해서 활용하기도 한다. 이 경험 때문에 헷갈리는 것 같다.
char
형은 특수하다. 이름에서부터 알 수 있듯이 태생이 문자를 다루기 위해 만들어졌다. char
형으로 입력과 출력을 하고자 하면 임베디드에서의 활용과 달라진다. 우선 키보드로 입력을 하면 무조건 문자로 인식된다. 1을 입력하면 정수 1이 입력되는 것이 아니라 문자 1이 입력되어, 저장되는 값은 1의 아스키 코드에 해당하는 49이다. 반대로 char
형 변수에 49를 할당하고 해당 변수를 출력하면 1이 출력된다.
내가 출력해서 확인해보고 잘 들어가 있다고 판단한 것은 눈으로 보기에는 0과 1이 출력되었기 때문이다.
입력받은 map[i][j]
값은 아래 코드에서 참 거짓 판별에 사용된다. 0은 아스키 코드값 48이기 때문에 0이든 1이든 참으로 해석된다. 올바른 연산을 하기위해 문자 0과 1을 정수 0, 1로 바꿔야 한다.
if (map[nextRow][nextCol]) dq.push_back({nextRow, nextCol, cnt + 1});
그래서 아래와 같이 문자열 다룰 때 흔히 나오는 스킬인 48을 빼주어서 해결할 수 있다.
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> map[i][j];
map[i][j] -= 48;
}
}
#include <iostream>
#include <deque>
using namespace std;
char map[101][101];
int visit[101][101];
int dx[4] = {0, 1, 0, -1};
int dy[4] = {-1, 0, 1, 0};
struct Info {
int x, y, cnt;
};
int main()
{
int m, n;
cin >> m >> n;
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cin >> map[i][j];
map[i][j] -= 48;
}
}
deque<Info> dq;
dq.push_back({1, 1, 0});
visit[1][1] = 1;
while (!dq.empty()) {
Info cur = dq.front(); dq.pop_front();
int row = cur.x;
int col = cur.y;
int cnt = cur.cnt;
if (row == n && col == m) {
cout << cnt << endl;
break;
}
for (int i = 0; i < 4; ++i) {
int nextRow = row + dx[i];
int nextCol = col + dy[i];
if (nextRow < 1 || nextRow > n || nextCol < 1 || nextCol > m) continue;
if (visit[nextRow][nextCol]) continue;
visit[nextRow][nextCol] = 1;
if (map[nextRow][nextCol]) dq.push_back({nextRow, nextCol, cnt + 1});
else dq.push_front({nextRow, nextCol, cnt});
}
}
return 0;
}