user12468788
user12468788

Reputation:

Optimizing the function for finding a Hamiltionian cycle in a grid graph?

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

Answers (3)

Tobs40
Tobs40

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

n314159
n314159

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

Alois Christen
Alois Christen

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

Related Questions