优化用于在网格图中查找哈密顿循环的函数?
Optimizing the function for finding a Hamiltionian cycle in a grid graph?
我已经制定了一个在网格图中查找哈密顿循环的工作算法。但是,我实施的方法包括递归检查所有可能的组合,直到找到正确的组合。这在小图(如 6*6)上很好,但在大图上变得太慢了,我需要找到 (30 * 30) 的循环。
在 main 中,我初始化了一个 2D 整数向量,表示图 (board
),并将其初始化为 -1。 -1 表示这个 space 还没有 'filled',而高于它的值表示它们在循环中的位置(0 - 第一个单元格,1 - 第二个单元格等)。我使用初始化 Vector2f(SFML 处理向量的方式,与标准库中的对一样),我用它来步进所有可能的状态。
我还初始化了路径整数,这将有助于 later.And 最后我调用 Hamiltionan 循环查找算法 (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);
};
然后我 hamCycle()
我检查 pos
向量是否超出网格,如果是 return false。否则我给这个单元格的路径值,然后增加。我检查算法是否完成,它是一个循环还是只是一条路径。如果它是一条路径,它 return 是错误的。然后我递归地检查它周围的单元格并重复这个过程。
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;
}
现在它在已经阻塞出口的情况下花费大量时间检查所有可能的路径,这是无效的。我怎样才能改进这一点,所以检查大网格是可行的?就像不检查是否有出口阻塞,但如果您知道任何其他改进方法,请告诉我。
您可以尝试分而治之:拿起您的棋盘,将其分成小块(假设为 4),然后为每个小块找到正确的路径。困难的部分是定义什么是正确的道路。您需要一条从上一块到下一块的路径,经过每个单元格。为此,您可以将这些片段分成更小的片段,依此类推,直到您只有一个单元格的片段。
请注意,此方法不会为您提供所有可能的循环,但几乎总是相同的循环。
在网格图上找到一个哈密顿圈真的不难。我在下面实现了它。我对板使用了 std::array
因为我想训练一下 constexpr 函数的编写。理论解释见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;
}
在网格图中,当且仅当宽度或高度为偶数(或两者)时,才存在汉密尔顿环。从左上角开始,如果高度为奇数,则一直向下,然后反复上下,同时在顶部留下一个space。到达右角后,您可以一直向上并再次向左走。
4*5:
S<<<
v>v^
v^v^
v^v^
>^>^
4*4:
S<<<
v>v^
v^v^
>^>^
对于奇数宽度,只需将其旋转 90 度即可。
这在 O(width*height) 中运行。
PS:我目前正在寻找一种在有限制的网格图中找到汉密尔顿循环的方法(用于实现完美的贪吃蛇玩家)
我已经制定了一个在网格图中查找哈密顿循环的工作算法。但是,我实施的方法包括递归检查所有可能的组合,直到找到正确的组合。这在小图(如 6*6)上很好,但在大图上变得太慢了,我需要找到 (30 * 30) 的循环。
在 main 中,我初始化了一个 2D 整数向量,表示图 (board
),并将其初始化为 -1。 -1 表示这个 space 还没有 'filled',而高于它的值表示它们在循环中的位置(0 - 第一个单元格,1 - 第二个单元格等)。我使用初始化 Vector2f(SFML 处理向量的方式,与标准库中的对一样),我用它来步进所有可能的状态。
我还初始化了路径整数,这将有助于 later.And 最后我调用 Hamiltionan 循环查找算法 (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);
};
然后我 hamCycle()
我检查 pos
向量是否超出网格,如果是 return false。否则我给这个单元格的路径值,然后增加。我检查算法是否完成,它是一个循环还是只是一条路径。如果它是一条路径,它 return 是错误的。然后我递归地检查它周围的单元格并重复这个过程。
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;
}
现在它在已经阻塞出口的情况下花费大量时间检查所有可能的路径,这是无效的。我怎样才能改进这一点,所以检查大网格是可行的?就像不检查是否有出口阻塞,但如果您知道任何其他改进方法,请告诉我。
您可以尝试分而治之:拿起您的棋盘,将其分成小块(假设为 4),然后为每个小块找到正确的路径。困难的部分是定义什么是正确的道路。您需要一条从上一块到下一块的路径,经过每个单元格。为此,您可以将这些片段分成更小的片段,依此类推,直到您只有一个单元格的片段。
请注意,此方法不会为您提供所有可能的循环,但几乎总是相同的循环。
在网格图上找到一个哈密顿圈真的不难。我在下面实现了它。我对板使用了 std::array
因为我想训练一下 constexpr 函数的编写。理论解释见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;
}
在网格图中,当且仅当宽度或高度为偶数(或两者)时,才存在汉密尔顿环。从左上角开始,如果高度为奇数,则一直向下,然后反复上下,同时在顶部留下一个space。到达右角后,您可以一直向上并再次向左走。
4*5:
S<<<
v>v^
v^v^
v^v^
>^>^
4*4:
S<<<
v>v^
v^v^
>^>^
对于奇数宽度,只需将其旋转 90 度即可。
这在 O(width*height) 中运行。
PS:我目前正在寻找一种在有限制的网格图中找到汉密尔顿循环的方法(用于实现完美的贪吃蛇玩家)