HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Time Limit Exceeded in BFS maze solver

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
limitmazebfstimesolverexceeded

Problem

Here's a problem that I tried solving:


Lakshagraha was a house built of lacquer, made by the Kauravas to kill
the Pandavas. The Kauravas wanted to burn the house down when the
Pandavas were asleep at night. But poor Kauravas – once again they
underestimated their cousins. Having been warned of the nefarious
plan, the Pandavas had had an underground set of passages built for
escape.


The underground rooms and passages were in the form of a n*m grid
where every cell is either free or blocked by a pillar. The Pandavas
start at a free cell and they need to reach a destination cell(which
can be free or blocked).


The following are the allowed valid moves:


Move from an empty cell to another adjacent empty cell. (Cells sharing
a common side are considered adjacent).


If an adjacent cell is blocked, then set the edge of the blocked cell
on fire.


If two or more (distinct) edges of a blocked cell are set on fire,
then the blocking pillar burns down and clears the cell. After the
fire, the cell becomes empty.


Initially no edge of any blocked cell is set on fire. Help the
Pandavas find whether it is possible to reach the destination (target
cell), because they are the good guys.


Input: The first line contains T, the number of test cases. The
description of T Test cases follow. The first line of each test case
consists of 2 space separated integers n and m, denoting the
dimensions of the grid (n x m grid). Each of the following n lines
contain m characters each, where the jth character of the ith line
denotes the state of the cell located at the jth column of the ith row
of the grid. Each cell can either be blocked (denoted by ‘*’), or free
(denoted by ‘.’). The next line of each test case consists of 4 space
separated integers sx, sy, ex, ey, where (sx, sy) denotes the cell
where you are initially located at, and (ex, ey) denotes the
destination cell (1 based indices).


Output: For

Solution

Before I get started, I'm writing C++11. If you're using GCC, you'll need to compile with -std=c++11 (consult your compiler documentation otherwise; if you have to use C++98/03, you'll need to modify some of the code). The code I'm giving you is also untested -- you may need to debug it.

OK, as Jamal mentioned, your first priority is to refactor. If your code isn't organized well, you will have a hard time optimizing it. One way to organize code like this is to write down a set of steps, and then write a function for each step. So let's start with main, and work from there. From a high level, you want to do the following things in your program:

  • Read in the list of mazes from the input.



  • Determine whether each maze can be solved.



  • Output the answers.



Note that I've separated the I/O from the meat of the program. This is good programming practice; it's an example of something called separation of concerns.

This separation will require a good data structure to represent the mazes; we may need to modify the data structure later in order to handle our requirements, but for now let's start with this:

class Maze {
  public:
    struct Point {
        const int row, col;
        Point(int row, int col) : row(row), col(col) {}
    };

    Maze(int num_rows, int num_cols)
        : num_rows(num_rows), num_cols(num_cols),
          starting_point{0, 0}, target{0, 0},
          points(num_rows, std::vector(num_cols, true)) {
    }

    // The two versions are for convenience.
    bool is_free(int row, int col) const {
        return points[row - 1][col - 1];  // 1-based points, 0-based indices
    }
    bool is_free(const Point& p) const { return is_free(p.row, p.col); }

    int num_rows() const { return num_rows; }
    int num_cols() const { return num_cols; }
    const Point& starting_point() const { return starting_point; }
    const Point& target() const { return target; }

    void set_is_free(int row, int col, bool is_free) {
        points[row - 1][col - 1] = is_free;
    }
    void set_starting_point(const Point& p) {
        starting_point = p;
    }
    void set_target(const Point& p) {
        target = p;
    }

  private:
    const int num_rows, num_cols;
    const Point starting_point, target;
    // points[row][col] is true iff the space in that row and column is free
    std::vector> points;
};


This class should allow us to do what we know we'll need to: construct a maze from input, then later retrieve the parameters of the problem (width, height, starting point, target) and check whether each cell in the maze is free.

You may notice that I spelled it std::vector, not vector; that is because using namespace std; is a bad idea.

OK, so now the main function:

int main() {
    const std::vector mazes = read_test_cases();
    for (const auto& maze : mazes) {
        if (solve_maze(maze)) {
            std::printf("YES\n");
        } else {
            std::printf("NO\n");
        }
    }
}


Very simple, no? Now we must write the two functions main requires. First, read_test_cases. Now, you used a lot of scanfs; I won't change that, but consider learning how to use C++'s iostream library. However, I will change the names and structure of the code in order to improve readability.

std::vector read_test_cases() {
    std::vector mazes;
    int num_tests;
    std::scanf("%d", &num_tests);
    mazes.reserve(num_tests);
    for (int i = 0; i < num_tests; ++i) {
        int num_rows, num_cols;
        std::scanf("%d%d", &num_rows, &num_cols);
        Maze maze(num_rows, num_cols);
        for (int row = 1; i <= num_rows; ++row) {
            for (int col = 1; j <= num_cols; ++col) {
                char space;
                std::scanf(" %c", &space);
                maze.set_is_free(row, col, space == '.');
            }
        }
        Maze::Point start, target;
        std::scanf("%d%d%d%d", &start.row, &start.col, &target.row, &target.col);
        maze.set_starting_point(start);
        maze.set_target(target);
        mazes.push_back(maze);
    }
    return mazes;
}


OK, now let's get your search algorithm into solve_maze.

```
bool solve_maze(const Maze& maze) {
// A bunch of stuff that you had in struct data is now stored in maze.
struct cell_search_state {
int num_reachable_walls = 0;
bool already_visited = false;
};
// The array ar that you declared is not standard-compliant C++;
// arrays must have constant dimensions.
std::vector> cell_search_state(
maze.num_rows(), std::vector(maze.num_cols()));
// You're removing elements from the front of your search queue
// and adding them to the end; this is inefficient with std::vector.
// You want to use a queue for your data structure instead.
//
// Changing v from a vector to a queue will probably get you a decent
// speedup by itself.
std::deque search_queue = { maze.get_starting_point() };
while (!search_queue.empty())

Code Snippets

class Maze {
  public:
    struct Point {
        const int row, col;
        Point(int row, int col) : row(row), col(col) {}
    };

    Maze(int num_rows, int num_cols)
        : num_rows(num_rows), num_cols(num_cols),
          starting_point{0, 0}, target{0, 0},
          points(num_rows, std::vector<bool>(num_cols, true)) {
    }

    // The two versions are for convenience.
    bool is_free(int row, int col) const {
        return points[row - 1][col - 1];  // 1-based points, 0-based indices
    }
    bool is_free(const Point& p) const { return is_free(p.row, p.col); }

    int num_rows() const { return num_rows; }
    int num_cols() const { return num_cols; }
    const Point& starting_point() const { return starting_point; }
    const Point& target() const { return target; }

    void set_is_free(int row, int col, bool is_free) {
        points[row - 1][col - 1] = is_free;
    }
    void set_starting_point(const Point& p) {
        starting_point = p;
    }
    void set_target(const Point& p) {
        target = p;
    }

  private:
    const int num_rows, num_cols;
    const Point starting_point, target;
    // points[row][col] is true iff the space in that row and column is free
    std::vector<std::vector<bool>> points;
};
int main() {
    const std::vector<Maze> mazes = read_test_cases();
    for (const auto& maze : mazes) {
        if (solve_maze(maze)) {
            std::printf("YES\n");
        } else {
            std::printf("NO\n");
        }
    }
}
std::vector<Maze> read_test_cases() {
    std::vector mazes;
    int num_tests;
    std::scanf("%d", &num_tests);
    mazes.reserve(num_tests);
    for (int i = 0; i < num_tests; ++i) {
        int num_rows, num_cols;
        std::scanf("%d%d", &num_rows, &num_cols);
        Maze maze(num_rows, num_cols);
        for (int row = 1; i <= num_rows; ++row) {
            for (int col = 1; j <= num_cols; ++col) {
                char space;
                std::scanf(" %c", &space);
                maze.set_is_free(row, col, space == '.');
            }
        }
        Maze::Point start, target;
        std::scanf("%d%d%d%d", &start.row, &start.col, &target.row, &target.col);
        maze.set_starting_point(start);
        maze.set_target(target);
        mazes.push_back(maze);
    }
    return mazes;
}
bool solve_maze(const Maze& maze) {
    // A bunch of stuff that you had in struct data is now stored in maze.
    struct cell_search_state {
        int num_reachable_walls = 0;
        bool already_visited = false;
    };
    // The array ar that you declared is not standard-compliant C++;
    // arrays must have constant dimensions.
    std::vector<std::vector<cell_search_state>> cell_search_state(
        maze.num_rows(), std::vector<cell_search_state>(maze.num_cols()));
    // You're removing elements from the front of your search queue
    // and adding them to the end; this is inefficient with std::vector.
    // You want to use a queue for your data structure instead.
    //
    // Changing v from a vector to a queue will probably get you a decent
    // speedup by itself.
    std::deque<Maze::Point> search_queue = { maze.get_starting_point() };
    while (!search_queue.empty()) {
        const Maze::Point p = search_queue.front();
        search_queue.pop_front();
        search_state[p.row][p.col].already_visited = true;
        // By using get_neighbors, we can prevent the repetition
        // of code your function had.
        for (const auto& neighbor : get_neighbors(p, maze)) {
            cell_search_state& neighbor_state =
                search_state[neighbor.row][neighbor.col];
            if (neighbor_state.already_visited) continue;
            if (neighbor.is_free()
                    || ++neighbor_state.num_reachable_walls > 1) {
                if (neighbor == maze.get_target())
                    return true;
                search_queue.push_back(neighbor);
            }
        }
    }
    return false;
}
bool operator==(const Maze::Point& l, const Maze::Point& r) {
    return l.col == r.col && l.row == r.row;
}

std::vector<Maze::Point> get_neighbors(const Maze::Point& p, const Maze& m) {
    std::vector neighbors;
    if (p.col < m.num_cols())
        neighbors.emplace_back(p.row, p.col + 1);
    if (1 < p.col)
        neighbors.emplace_back(p.row, p.col - 1);
    if (p.row < m.num_rows())
        neighbors.emplace_back(p.row + 1, p.col);
    if (1 < p.row)
        neighbors.emplace_back(p.row - 1, p.col);
    return neighbors;
}

Context

StackExchange Code Review Q#38904, answer score: 2

Revisions (0)

No revisions yet.