Skip to main content Link Search Menu Expand Document (external link)

Shortest path in a grid

Given a grid with a source cell, a destination cell and obstacle cells, find the shortest path from the source to destination, where every direction change along the path costs 1.

Say the grid has dimensions n times n.

A simple BFS traversal in time \(O(n^3)\)

Consider a graph, where every vertex is a cell of the grid, and there is an edge between two cells of the same column or the same row if they are not separated by an obstacle. Now the problem consists in finding a shortest path in this unweighted graph, and this can be done using a BFS traversal of the graph. There are \(O(n^2)\) vertices and \(O(n)\) edges, which leads to the claimed complexity.

A better analysis shows time is \(O(n^2)\) in fact

When you visit a cell u at distance dist[u] from the source, then you will explore all neighbors: For each of the 8 directions you will explore the cells in that direction at increasing distance from u, and stop once you reached a cell v that is either on the boundary of the grid, or has already been visited. But you can also stop if dist[v] equals dist[u]. Because this means that when later you visit vertex v, anyway you will explore the vertices which are beyond v in the u-v direction.

As a result you will look at a cell v at most 8 times, once for each direction. Hence the algorithm runs in linear time.

#include <cstdio>
#include <utility>
#include <deque>
#include <cstring>

/* Wandering Queen
http://www.spoj.com/problems/QUEEN/

BFS
but be careful in implementation

tricks (don't know if all are necessary)
- store only cells in queue not (cell,direction)
- flatten the grid in a 1 dimensional matrix, so queue can store single integers not pairs
- add border around grid so we can avoid checking boundaries
*/

using namespace std;

#define MAXN 1002

char grid[MAXN * MAXN];                      // too big to be placed on heap
int dist[MAXN * MAXN];


int main() {
  int testcases;
  scanf("%d", &testcases);
  while (testcases-->0) {
    int nr, nc;
    scanf("%d%d", &nr, &nc);                 // read input
    char *p = grid;
    for (int c=0; c < nc + 2; c++)           // add top border
      *p++ = 'X';
    for (int r = 0; r < nr; r++) {
      *p++ = 'X';                            // add left border
      scanf("%s", p);
      p += strlen(p);
      *p++ = 'X';                            // add right border
    }
    for (int c=0; c < nc + 2; c++)           // add bottom border
      *p++ = 'X';
    *p = '\0';                               // mark end of string
    nc += 2;                                 // count for the additional border
    nr += 2;

    int source = strstr(grid, "S") - grid;   // find source

    const int infinity = MAXN * MAXN;        // big enough number
    fill(dist, dist + MAXN * MAXN, infinity);

    //                                       -- direction offsets
    //             E      NE     N      NW    W   SW     S     SE
    int diff[8] = {-1, -1 - nc, -nc, -nc + 1, 1, 1 + nc, nc, nc - 1};

    deque<int> gray;                         // FIFO queue

    dist[source] = 0;                        // start with single source in queue
    gray.push_front(source);

    int answer = -1;                         // default answer if F unreachable from S

    while ( ! gray.empty()) {                // BFS
      int u = gray.front();
      gray.pop_front();

      if (grid[u] == 'F') {                  // target found
        answer = dist[u];
        break;
      }

      for (int a=0; a < 8; a++) {            // look all 8 neighbors
        int v = u;
        while (true) {                       // walk in one direction
          v += diff[a];
          if (grid[v] == 'X' || dist[v] <= dist[u])   // until obstacle reached or already visited
            break;
          if (dist[v] == infinity) {         // new vertex ?
            dist[v] = dist[u] + 1;
            gray.push_back(v);
          }
        }
      }
    }
    printf("%d\n", answer);
  }
  return 0;
}

Improvement through Algorithm A* ?

The algorithm A* is an improvement of the standard Dijkstra shortest path algorithm. All we need is a lower bound on the shortest path to the destination. We model the problem as follows. The queen always stands in some grid cell facing some direction. She can either walk for free one step in her direction or at the cost of 1 unit walk one step in another direction. Suppose that the queen stands in position (0,0) and the destination cell is (x,y). For the lower bound assume that the grid has infinite dimensions and is free of obstacles. Then the queen could reach the destination in 0, 1, or 2 steps, depending on the directions she is currently facing. The cost can be determined through a careful case analysis.

/* Compares (x,y) to the 8 halflines starting at the origin.
   Returns the minimum number of moves the queen must do in order to reach (x,y)
   if she starts at the origin and walks in direction dir.
*/
int heuristic(int x, int y, int dir) {
  // order : NW, N, NE, E, SE, S, SW, W
  bool is_on[8] = { x== -y && x < 0,   y > 0 && x==0,  x == y && x > 0,
                    x == 0 && x > 0, x == -y && x > 0, x == 0 && y < 0,
              x == y && x < 0, y == 0 && x < 0};
  bool is_left[8] = {x > -y, x > 0, x > y, y < 0, x < -y, x < 0, x < y, y > 0};
  // test if (x,y) lies on a halfline
  for(size_t i = 0; i < 8; ++i)
  {
    if (is_on[i]) {
      if (dir == i)
        return 0;
      else if (dir == i + 3 || dir == i + 5)
        return 2;
      else
        return 1;
    }
  }
  // test if (x,y) lies between two halflines
  for(size_t i = 0; i < 8; ++i)
  {
    if (is_left[i] && ! is_left[ (i + 1) % 8]) {
      if (dir == i + 4 || dir == i + 5)
        return 2;
      else
        return 1;
    }
  }
  // this part should never be reached
  return 0;
}

Experiments show that this solution is slower than the BFS described above.

  • See here for another explanation by Hasan Zobayer.

Comments