Reputation:
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.
Upvotes: 3
Views: 407
Reputation: 65
In a grid graph there is a hamilton cycle if and only if the width or the height are even (or both). Start in the top left corner, if the height is odd go all the way down, then up and down repeatedly while leaving one space at the top. When having reached the right corner you can go all the way up and to the left again.
4*5:
S<<<
v>v^
v^v^
v^v^
>^>^
4*4:
S<<<
v>v^
v^v^
>^>^
For odd width, just turn it 90 degree.
This runs in O(width*height).
PS: I'm currently looking for a way to find Hamilton Cycles in a grid graph with restrictions (for implementing a perfect snake player)
Upvotes: 0
Reputation: 5085
Finding one Hamiltonian cycle on a grid graph is really not that hard. I implemented it below. I used an std::array
for the board because I wanted to train a bit the writing of constexpr functions. For the theoritical explanation, see here.
#include <iostream>
#include <array>
#include <optional>
#include <algorithm>
// Allows iterating of a two dimensional array in the cross direction.
template<typename Iter>
struct cross_iterator {
using difference_type = typename Iter::difference_type;
using value_type = typename Iter::value_type;
using pointer = typename Iter::pointer;
using reference = typename Iter::reference;
using iterator_category = typename Iter::iterator_category;
constexpr cross_iterator(Iter it, size_t pos) : _it(it), _pos(pos)
{}
constexpr auto& operator*() {
return (*_it)[_pos];
}
constexpr auto& operator++() {
++_it;
return *this;
}
constexpr auto& operator++(int) {
_it++;
return *this;
}
constexpr auto& operator--() {
--_it;
return *this;
}
constexpr auto& operator--(int) {
_it--;
return *this;
}
constexpr bool operator==(const cross_iterator<Iter> &other) const {
return _pos == other._pos && _it == other._it;
}
constexpr bool operator!=(const cross_iterator<Iter> &other) const {
return !(*this == other);
}
constexpr auto& operator+=(difference_type n) {
_it += n;
return *this;
}
Iter _it;
const size_t _pos;
};
template<typename Iter>
cross_iterator(Iter it, size_t pos) -> cross_iterator<std::decay_t<decltype(it)>>;
template<size_t N, size_t M = N>
using board = std::array<std::array<int, N>, M>;
template<size_t N, size_t M = N>
constexpr std::optional<board<N, M>> get_ham_cycle() {
if constexpr ( N%2 == 1 && M%2 == 1 ) {
if constexpr( N == 1 && M == 1 ) {
return {{{{0}}}};
}
else {
// There is no Hamiltonian Cycle on odd side grid graphs with side lengths > 1
return {};
}
} else
{
std::optional<board<N,M>> ret {std::in_place};
auto &arr = *ret;
int count = 0;
arr[0][0] = count++;
if constexpr ( N%2 == 0 ) {
for(auto i = 0ul; i < N; ++i) {
// We fill the columns in alternating directions
if ( i%2 == 0 ) {
std::generate(std::next(begin(arr[i])), end(arr[i]), [&count] () { return count++; });
} else {
std::generate(rbegin(arr[i]), std::prev(rend(arr[i])), [&count] () { return count++; });
}
}
std::generate(cross_iterator(rbegin(arr), 0), std::prev(cross_iterator(rend(arr), 0)), [&count] () { return count++; });
} else {
for(auto j = 0ul; j < M; ++j) {
// We fill the rows in alternating directions
if ( j%2 == 0 ) {
std::generate(std::next(cross_iterator(begin(arr)), j), cross_iterator(end(arr), j), [&count] () { return count++; });
} else {
std::generate(cross_iterator(rbegin(arr), j), std::prev(cross_iterator(rend(arr), j)), [&count] () { return count++; });
}
}
std::generate(rbegin(arr[0]), std::prev(rend(arr[0])), [&count] () { return count++; });
}
return ret;
}
}
int main() {
auto arr = *get_ham_cycle<30>();
for(auto i = 0ul; i < 30; ++i) {
for(auto j = 0ul; j < 30; ++j) {
std::cout << arr[i][j] << '\t';
}
std::cout << '\n';
}
return 0;
}
Upvotes: 0
Reputation: 547
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.
Upvotes: 1