0

I was reading antti lakesonen's competitive programming handbook and I came across backtracking for finding grid paths with some optimizations applied. In nutshell here are the rules for a valid path across the grid :

0. Path search starts from top left cell and ends at bottom right cell.
1. A valid path has traversed all the cells of the grid once.
2. From current cell you can move right, left, top, bottom and only if the move doesn't leave the grid.

Now I have written this code that works fine with square grid of dimension = 7 with time taken ~400ms and total paths : 111712. I have two queries here :

--> With dimension = 9 its taking like a lot of time to report even the first valid path in its search. Am I stuck into the infinite loop or what?

--> Grid path search for even number of dimension like 2,4,6 are giving answer 0. I tried with finding path for 2*2 and 4*4 grid manually but I see that it's impossible to find such paths, but i don't know how to formally or logically prove that even dimensions square grids cannot have such paths mentioned above.

Here is my code :

#include <bits/stdc++.h>

using namespace std;

#define LOG_PATH 1;
#define ARRAY_BASED_CONSTANT_GRID 1;

struct Cell {
    int x,y;
};

const int HORIZONTAL_DIRECTION = 0, VERTICAL_DIRECTION = 1;

long long validPathCount, recursiveCallCount, visitedCellCount, currentDirection;

#ifdef ARRAY_BASED_CONSTANT_GRID
    const int DIMENSION = 7;
    bool grid[DIMENSION][DIMENSION];
#else
    int DIMENSION;
    vector<vector<bool>> grid;
#endif



/* *-*-*-*-*-*-*-*- UTILITY FUNCTIONS -*-*-*-*-*-*-*-*-*-*-*-*-*-* */
bool hasPathCompleted(Cell);
bool isPathValid();
void validPathCheckAndUpdate();

void setState(Cell);
void resetState(Cell);

void updateAndLogPathCounter();
void backtrackPathSearching(Cell);
void moveAlongTheGrid(Cell);
bool isOccupied(Cell);

bool canMoveRight(Cell);
bool canMoveLeft(Cell);
bool canMoveUp(Cell);
bool canMoveDown(Cell);

bool isRightBorder(Cell);
bool isLeftBorder(Cell);
bool isUpBorder(Cell);
bool isDownBorder(Cell);

Cell generateRight(Cell);
Cell generateLeft(Cell);
Cell generateUp(Cell);
Cell generateDown(Cell);

bool isThereHorizontalPartition();
bool isThereVerticalPartition();

void printGrid();
/* *-*-*-*-*-*-*-*- UTILITY FUNCTIONS -*-*-*-*-*-*-*-*-*-*-*-*-*-* */

Cell generateRight(Cell position) { 
    currentDirection = HORIZONTAL_DIRECTION;
    return {position.x, position.y + 1};
}
Cell generateLeft(Cell position) { 
    currentDirection = HORIZONTAL_DIRECTION;
    return {position.x, position.y - 1};
}
Cell generateUp(Cell position) { 
    currentDirection = VERTICAL_DIRECTION;
    return {position.x - 1, position.y};
}
Cell generateDown(Cell position) { 
    currentDirection = VERTICAL_DIRECTION;
    return {position.x + 1, position.y};
}

bool isRightBorder(Cell position) { return position.y == DIMENSION - 1; }
bool isLeftBorder(Cell position) { return position.y == 0; }
bool isUpBorder(Cell position) { return position.x == 0; }
bool isDownBorder(Cell position) { return position.x == DIMENSION - 1; }

bool isOccupied(Cell position) { return grid[position.x][position.y]; }

bool canMoveRight(Cell position) { return !isRightBorder(position) && !isOccupied(generateRight(position)); }
bool canMoveLeft(Cell position) { return !isLeftBorder(position) && !isOccupied(generateLeft(position)); }
bool canMoveUp(Cell position) { return !isUpBorder(position) && !isOccupied(generateUp(position)); }
bool canMoveDown(Cell position) { return !isDownBorder(position) && !isOccupied(generateDown(position)); }

bool hasPathCompleted(Cell position) { return position.x == DIMENSION - 1 && position.y == DIMENSION - 1; }

bool isPathValid() { return visitedCellCount == DIMENSION * DIMENSION; }

void validPathCheckAndUpdate() {
    if (isPathValid()) { updateAndLogPathCounter(); }
}

void updateAndLogPathCounter() {
    validPathCount++;
    #ifdef LOG_PATH
    cout << "\rFound " << validPathCount << " paths";
    cout.flush();
    #endif
}

void setState(Cell position) {
    recursiveCallCount++;
    visitedCellCount++;
    grid[position.x][position.y] = true;
}

void resetState(Cell position) {
    visitedCellCount--;
    grid[position.x][position.y] = false;
}

void printGrid() {
    cout << "-------------------------------" << endl;
    for (int i = 0; i < DIMENSION; i++) {
        for (int j = 0; j < DIMENSION; j++) {
            cout << grid[i][j] << " ";
        }
        cout << endl;
    }
}

void moveAlongTheGrid(Cell position) {
    if (canMoveRight(position)) {
        backtrackPathSearching(generateRight(position));
    }

    if (canMoveLeft(position)) {
        backtrackPathSearching(generateLeft(position));
    }

    if (canMoveUp(position)) {
        backtrackPathSearching(generateUp(position));
    }

    if (canMoveDown(position)) {
        backtrackPathSearching(generateDown(position));
    }
}

bool isThereHorizontalPartition(Cell position) {
    if (currentDirection == VERTICAL_DIRECTION) {
        if (!canMoveUp(position) && !canMoveDown(position)) {
            if (canMoveLeft(position) && canMoveRight(position)) {
                return true;
            }
        }
    }
    return false;
}

bool isThereVerticalPartition(Cell position) {
    if (currentDirection == HORIZONTAL_DIRECTION) {
        if (!canMoveLeft(position) && !canMoveRight(position)) {
            if (canMoveUp(position) && canMoveDown(position)) {
                return true;
            }
        }
    }
    return false;
}

// OPTIMIZATION : 4 > Direction awareness
// Optimization 3 followed border awareness but this concept can be generalized 
// and we can make the grid aware of its direction
// If path can't go forward in current direction but either can turn into both cross axis path
// then there is partition
void backtrackPathSearching(Cell position) {    
    setState(position);

    // printGrid();
    // cout << "x : " << position.x << " y : " << position.y << endl;
    // cout << "validPathCount : " << validPathCount << endl;
    // string s;
    // getline(cin, s);

    if (hasPathCompleted(position)) {
        validPathCheckAndUpdate();
    }
    else if (!isThereHorizontalPartition(position) && !isThereVerticalPartition(position)) {
        moveAlongTheGrid(position);
    }

    resetState(position);
}

#ifndef ARRAY_BASED_CONSTANT_GRID
    void inputFromUser() {
        cout << "Give Dimension of box grid : ";
        cin >> DIMENSION;

        for (int i = 0; i < DIMENSION; i++) grid.push_back(vector<bool>(DIMENSION, false));
    }
#endif

int main() {
    #ifndef ARRAY_BASED_CONSTANT_GRID
        inputFromUser();
    #endif

    validPathCount = recursiveCallCount = 0;
    visitedCellCount = 1;
    grid[0][0] = 1;
    currentDirection = VERTICAL_DIRECTION;

    backtrackPathSearching({1, 0});

    cout << endl;
    cout << "Paths : " << validPathCount * 2 << endl;
    cout << "Calls : " << recursiveCallCount << endl;
    return 0;
}

/*
Paths : 111712
Calls : 26790463

real    0m0.281s
user    0m0.281s
sys 0m0.000s
*/

Just comment ARRAY_BASED_CONSTANT_GRID to enable user input for grid dimension.

Can someone help me with this? Thanks in advance :)

PS: Due to my coding practices I had to paste the whole code in order to make it sensible. I know in stackoverflow only minimal required code has to be pasted but here I don't know what's really needed in order to analyze the code!

Patel Yash
  • 21
  • 1
  • 1
    The proof that even-sized grids have no solution is based on a checkerboard pattern. Assume the start and end squares are black. So the first move is from black to white. Every move after that alternates between black and white. And the last move must be from white to black. So the shortest path is BWB, and any longer path must add an equal number of white and black, e.g. BWBWB. As you can see, a valid path can only have an odd number of moves. So the grid must have an odd number of cells. – user3386109 May 30 '21 at 08:04
  • Okay I got the understanding of the proof, Thanks @user3386109 – Patel Yash May 30 '21 at 08:17
  • 1
    The large delay before finding the first solution is a clue, but not really a bug. It's just the way the code works. The code in `main` hardcodes the first move as **down**. The search order that the code uses is **right** /left/up/down. Hence, the code starts by moving down/right/right (DRR), and then continues the search from there. The problem is that there are no solutions that start with DRR. So all the time spent searching after DRR is just wasted. It's not until the code gets around to DRU that it will find any solutions. – user3386109 Jun 01 '21 at 20:50
  • Thank you @user3386109 for helping me out. You were right, there are no bugs in code. I changed the backtracking sequence from Right-Left-Up-Down to Down-Right-Up-Left and now its acknowledging paths right away from second 1. Thanks once again bruh! – Patel Yash Jun 02 '21 at 04:49

0 Answers0