Computer >> 컴퓨터 >  >> 프로그래밍 >> C++

C++ 미로 문제 III: 공이 구멍에 떨어지는 최단 경로 구하기

문제 소개

빈 칸과 벽으로 이루어진 미로 안에 공 하나가 놓여 있다고 가정해 보겠습니다. 공은 위(u), 아래(d), 왼쪽(l), 오른쪽(r) 네 방향으로 굴러갈 수 있으며, 한 번 굴러가기 시작하면 벽에 부딪힐 때까지 멈추지 않습니다. 공이 멈춘 지점에서는 다음 방향을 다시 선택할 수 있습니다. 또한 미로에는 구멍(hole)이 하나 존재하는데, 공이 구멍 위를 지나가면 그대로 구멍 속으로 떨어집니다.

공의 시작 위치, 구멍의 위치, 그리고 미로 정보가 주어졌을 때, 공이 구멍에 떨어질 수 있는 최단 거리의 이동 경로를 찾아야 합니다. 여기서 거리란 시작 지점(제외)부터 구멍(포함)까지 공이 지나간 빈 칸의 개수를 의미합니다.

결과는 'u', 'd', 'l', 'r' 문자로 구성된 이동 경로 문자열로 반환합니다. 최단 경로가 여러 개 존재할 수 있으므로, 그중 사전순(lexicographically)으로 가장 작은 경로를 출력해야 합니다. 만약 공이 구멍에 도달할 수 없다면 "impossible"을 출력합니다.

미로는 2차원 이진 행렬로 표현되며, 1은 벽, 0은 빈 칸을 뜻합니다. 공과 구멍의 위치는 각각 행(row)과 열(column) 인덱스로 주어집니다.

예시

예를 들어 다음과 같은 입력이 주어졌다고 해봅시다.

C++ 미로 문제 III: 공이 구멍에 떨어지는 최단 경로 구하기

이때 출력은 'lul'입니다. 왼쪽으로 굴린 뒤 위로, 다시 왼쪽으로 굴린다는 의미입니다. 'ul'(위 → 왼쪽) 역시 가능한 경로인데, 두 경로 모두 길이는 6으로 동일하지만 'ul'은 'lul'보다 사전순으로 크기 때문에 정답에서 제외됩니다.

접근 방법

이 문제는 우선순위 큐(priority queue)를 활용한 다익스트라(Dijkstra) 알고리즘으로 해결할 수 있습니다. 거리가 가장 짧은 상태를 먼저 처리하고, 거리가 같다면 경로 문자열이 사전순으로 앞서는 상태를 먼저 처리하도록 비교자(comparator)를 구성하는 것이 핵심입니다.

  • 거리(dist), 경로 문자열(d), 좌표(x, y)를 담는 Data 구조체를 정의합니다.
  • 4방향 이동 배열 dir := {{1, 0}, {0, -1}, {0, 1}, {-1, 0}}을 선언합니다.
  • 각 방향에 대응하는 문자 배열 dirst := {'d', 'l', 'r', 'u'}를 선언합니다.
  • 두 좌표가 동일한지 검사하는 ok() 함수를 정의합니다.

알고리즘 단계

  1. n := 미로의 행 크기, m := n이 0이 아니면 미로의 열 크기, 아니면 0으로 설정합니다.
  2. 우선순위 큐 pq를 생성하고, 초기 상태 (거리 0, 공의 좌표, 빈 문자열 "")를 삽입합니다.
  3. n × m 크기의 visited 2차원 배열을 준비합니다.
  4. pq가 빌 때까지 다음 과정을 반복합니다.
    • pq의 최상단 원소를 curr로 꺼내 x, y, dist, d 값을 추출합니다.
    • ok(x, y, hole[0], hole[1])가 참이라면, 즉 현재 위치가 구멍이라면 d를 반환합니다.
    • visited[x][y]를 true로 표시하고 pq에서 해당 원소를 제거합니다.
    • k를 0부터 3까지 증가시키며 4방향에 대해 다음을 수행합니다.
      • nx := x, ny := y, tempDist := 0으로 초기화합니다.
      • 다음 칸이 미로 범위 안에 있고 벽이 아니라면 공을 계속 굴립니다. nx와 ny를 이동 방향만큼 갱신하고 tempDist를 1씩 증가시킵니다.
      • 굴러가는 도중 현재 위치가 구멍이라면 내부 반복문을 종료합니다.
      • visited[nx][ny]가 false라면 새로운 Data(dist + tempDist, nx, ny, d + dirst[k])를 pq에 삽입합니다.
  5. 큐가 비었는데도 구멍에 도달하지 못했다면 "impossible"을 반환합니다.

구현 예시

아래 코드를 통해 더 자세히 이해해 보겠습니다.

#include <bits/stdc++.h>
using namespace std;
int dir[4][2] = {{1, 0}, {0, -1}, {0, 1}, {-1, 0}};
char dirst[4] = {'d', 'l', 'r', 'u'};
class Solution {
public:
   struct Data {
      int dist;
      string d;
      int x, y;
      Data(int a, int b, int c, string s) {
         d = s;
         dist = a;
         x = b;
         y = c;
      }
   };
   struct Comparator {
      bool operator()(Data a, Data b) {
         return a.dist != b.dist ? !(a.dist < b.dist) : !(a.d < b.d);
      }
   };
   bool ok(int x1, int y1, int x2, int y2) { return x1 == x2 && y1 == y2; }
   string findShortestWay(vector<vector<int>> &maze, vector<int>&ball,
      vector<int> &hole) {
         int n = maze.size();
         int m = n ? maze[0].size() : 0;
         priority_queue<vector<Data>, vector<Data>, Comparator> pq;
         pq.push(Data(0, ball[0], ball[1], ""));
         vector<vector<bool>> visited(n, vector<bool>(m));
         while (!pq.empty()) {
            Data curr = pq.top();
            int x = curr.x;
            int y = curr.y;
            int dist = curr.dist;
            string d = curr.d;
            if (ok(x, y, hole[0], hole[1])) {
               return d;
            }
            visited[x][y] = true;
            pq.pop();
            for (int k = 0; k < 4; k++) {
               int nx = x;
               int ny = y;
               int tempDist = 0;
               while (nx + dir[k][0] < n && nx + dir[k][0] >= 0 && ny + dir[k][1] < m && ny + dir[k][1] >= 0 && !maze[nx + dir[k][0]][ny + dir[k][1]]) {
                  nx += dir[k][0];
                  ny += dir[k][1];
                  tempDist++;
                  if (ok(nx, ny, hole[0], hole[1]))
                     break;
               }
               if (!visited[nx][ny]) {
                  pq.push(Data(dist + tempDist, nx, ny, d + dirst[k]));
               }
            }
         }
         return "impossible";
   }
};
main() {
   Solution ob;
   vector<vector<int>> v = {
      {0, 0, 0, 0, 0},
      {1, 1, 0, 0, 1},
      {0, 0, 0, 0, 0},
      {0, 1, 0, 0, 1},
      {0, 1, 0, 0, 0}};
   vector<int> v1 = {4, 3}, v2 = {0, 1};
   cout << (ob.findShortestWay(v, v1, v2));
}

입력

vector<vector<int>> v = {{0, 0, 0, 0, 0},
{1, 1, 0, 0, 1},
{0, 0, 0, 0, 0},
{0, 1, 0, 0, 1},
{0, 1, 0, 0, 0}};
vector<int> v1 = {4, 3}, v2 = {0, 1};

출력

lul