I've made a working algorithm for finding a Hamiltonian cycle in a grid graph. However, the approach I implemented includes recursively checking all the possible combinations until I find the right one. This is fine on small graphs (like 6*6), but becomes way too slow on bigger ones, the ones that I need to find the cycle for (30 * 30).
In main I initialize a 2D vector of ints, representing out graph (board
), and initalize it to -1. -1 represents that this space hasn't been 'filled' yet, while values above that represent their place in the cycle (0 - first cell, 1 - second cell etc.). And I use initialize a Vector2f (SFML's way of doing vectors, same as pairs in standard library), which I use to step all the possible states.
And I also initialize the path integer, which will help up later.And lastly I call the Hamiltionan cycle finding algorithm (HamCycle()
).
int main(){
int path = 0;
int bx = 8;
std::vector<std::vector<int>> board{ 8 };
Vector2f pos = { 4 , 4 };
for (int i = 0; i < bx; i++) {
board[i].resize(bx);
for (int j = 0; j < bx; j++) board[i][j] = -1;
}
hamCycle(board, pos, path, bx);
};
Then I hamCycle()
I check if pos
vector goes outside of the grid, and if so return false. Else I give this cell the value of path, which is then increased. I check if the algorithm is done, and if it's a cycle or just a path. If it's a path, it returns false. Then I recursively check the cells around it and repeat the process.
bool hamCycle(std::vector<std::vector<int>> &board,Vector2f pos, int &path, int bx) {
//check if it's outside the box and if it's already occupied
if (pos.x >= bx || pos.x < 0 || pos.y >= bx || pos.y < 0) return false;
if (board[pos.x][pos.y] != -1) return false;
board[pos.x][pos.y] = path;
path++;
//check if the cycle is completed
bool isDone = true;
if (path != (bx * bx)) isDone = false;
//check if this cell is adjacent to the beggining and if so it's done
if (isDone) {
if (pos.x != 0 && pos.x != (size - 1) && pos.y != 0 && pos.y != (size - 1)) {
if ((board[pos.x + 1][pos.y] == 0) || (board[pos.x - 1][pos.y] == 0) || (board[pos.x][pos.y
+ 1] == 0)
|| (board[pos.x][pos.y - 1] == 0)) {
return true;
}
path--;
board[pos.x][pos.y] = -1;
return false;
}
else {
path--;
board[pos.x][pos.y] = -1;
return false;
};
}
//recursion time
if (hamCycle(board, Vector2f(pos.x + 1, pos.y), path, bx)) return true;
if (hamCycle(board, Vector2f(pos.x - 1, pos.y), path, bx)) return true;
if (hamCycle(board, Vector2f(pos.x, pos.y + 1), path, bx)) return true;
if (hamCycle(board, Vector2f(pos.x, pos.y - 1), path, bx)) return true;
path--;
board[pos.x][pos.y] = -1;
return false;
}
Right now it spends a lot of time checking all possible paths when it has already blocked an exit, which is innefficent. How can I improve this, so checking big grids is feasible? Like not checking if has a blocked exit, but if you know any other methods for improvement, please let me know.
You could try divide and conquer : take your board, divide it into small pieces (let's say 4), and find the right path for each of those pieces. The hard part is to define what is the right path. You need a path coming from the previous piece and going into the next one, passing by each cell. To do that, you can divide those pieces into smaller one, etc, until you have pieces of only one cell.
Note that this approach doesn't give you all the cycles possible, but almost always the same ones.