Knight's tour backtrack 实现选择步长数组

Posted

技术标签:

【中文标题】Knight\'s tour backtrack 实现选择步长数组【英文标题】:Knight's tour backtrack implementation choosing the step arrayKnight's tour backtrack 实现选择步长数组 【发布时间】:2014-02-02 14:24:42 【问题描述】:

所以我想出了这个实现来解决 8*8 棋盘的骑士巡回赛。 但似乎它需要很长时间才能运行(以至于我不得不停止它)。但是,如果我将 dx、dy 数组替换为用 cmets 编写的数组,程序就会像魔术一样工作并给出输出。他们说它巧妙地选择了数组,因此暴力算法很幸运能够快速找到解决方案。

但是如何首先提出这个数组,这个数组 (dx,dy) 我是从其他代码中得到的。那么谁能解释一下为什么这段代码适用于这些数组(在评论中)而不是我的。

#include <cstdio>

using namespace std;

int dx[8] =   1,  1, 2,  2,  -1, -1, -2, -2;
int dy[8] =   2, -2, 1, -1,  2,  -2,  1, -1;

//int dx[8] =   2, 1, -1, -2, -2, -1,  1,  2 ;
//int dy[8] =   1, 2,  2,  1, -1, -2, -2, -1 ;

bool solve(bool arr[8][8],int x,int y,int moves)
    if(moves==0)return true;
    if(x<0 || y<0 || x>7 || y>7 || arr[x][y])return false;
    arr[x][y]=1;

    for(int i=0;i<8;i++)
        if(solve(arr,x+dx[i],y+dy[i],moves-1))
            printf(" (%d,%d) ",x,y);
            return 1;
        
    arr[x][y]=0;
    return false;


int main()

    bool arr[8][8];
    for(int i=0;i<8;i++)    for(int j=0;j<8;j++)    arr[i][j]=0;
    solve(arr,0,0,64);
    puts("");

【问题讨论】:

我的建议是使用调试器单步执行代码,跟踪变量值。 "如果从 0,0 开始的游览如此之快,那么它可能是纯粹的机会,或者数组 xMove 和 yMove 被巧妙地初始化,因此 (0,0) 的解决方案很快就找到了。”,从***.com/questions/18073258/…谷歌搜索时发现了这个 所以写这篇文章的人实际上并不知道这些值被巧妙地选择了。 可见heuristic的重要性。 【参考方案1】:

总结

注释掉的dx/dy 数组比您的初始数组效果更好的原因是它以不同的顺序执行深度优先搜索解决方案 - 选择的顺序是考虑到特定的解决方案,并且因此能够相对较快地找到该解决方案。

详情

Depth-first search 从树的根部开始,检查通往叶子的每条路径。例如,这棵树的深度优先搜索将首先检查仅访问 a 节点的路径 (a -&gt; a -&gt; a),然后稍微回溯并检查 a -&gt; a -&gt; b,然后是 a -&gt; a -&gt; c,等等。

如果树很大并且没有从访问 a 开始的解决方案,这可能会花费很多时间,因为您必须浪费大量时间检查所有路径从a 开始,然后才能继续前进。

如果您碰巧知道有一个以d 开头的好解决方案,您可以通过重新排序树的节点来加快速度,这样您就可以从检查以d 开头的路径开始:

您已经删除了程序必须执行的 7/8 工作,因为您无需费心寻找以 d 以外的其他内容开头的路径!通过为其余节点选择良好的顺序,您可以获得类似的加速。

如果您查看程序的输出,您会看到这种情况:

(0,7)  (1,5)  (3,4)  (1,3)  (0,1)  (2,0)  (4,1)  (6,0)  (7,2)  (5,3)  (7,4)
(6,2)  (7,0)  (5,1)  (4,3)  (3,1)  (5,0)  (7,1)  (5,2)  (7,3)  (6,1)  (4,0)
(3,2)  (4,4)  (2,3)  (0,2)  (1,0)  (2,2)  (3,0)  (1,1)  (0,3)  (2,4)  (1,2)
(0,4)  (1,6)  (3,7)  (2,5)  (3,3)  (5,4)  (6,6)  (4,5)  (6,4)  (7,6)  (5,5)
(4,7)  (2,6)  (0,5)  (1,7)  (3,6)  (5,7)  (6,5)  (7,7)  (5,6)  (3,5)  (1,4)
(0,6)  (2,7)  (4,6)  (6,7)  (7,5)  (6,3)  (4,2)  (2,1)  (0,0)

第一步(从底部读取)是从(0,0)(2,1),对应dx=2dy=1——果然,在注释掉的dx/dy列表中,就是检验的第一种可能性。事实上,这个解决方案的前三步使用了dx=2dy=1,这实际上意味着您只需要搜索一个很小的子树而不是整个子树

【讨论】:

这个问题其实是对称的,有多种解法。因此,对于以 dx=2 和 dy=1 开头的解决方案,将会有另一个以 dx=1 和 dy=2 开头的解决方案(这是未注释代码使用的第一个值)。我认为改进更多地与列表中的第二项以及它与第一项的关系有关。 当然,这很公平。我想得出的结论是,增量的排序——尤其是前几个——是一种启发式方法,可以调整以快速找到特定的解决方案。【参考方案2】:

国际象棋中有八个有效的马步:

这两个数组列出了这八个动作。

两个版本的代码以不同的顺序尝试移动。碰巧一个版本比另一个更快地找到有效的解决方案。

仅此而已。

【讨论】:

哥们,你没有回答我的问题,为什么会这样,用这个数组,我也尝试了一些其他的组合,但它们不起作用 @Jignesh:不必无礼,尤其是对那些试图帮助你的人。 粗鲁?嘿,我只是更清楚地说明了我的问题,如果您有这样的感觉,我很抱歉

以上是关于Knight's tour backtrack 实现选择步长数组的主要内容,如果未能解决你的问题,请参考以下文章

Knight's Tour C++ 使用堆栈

Knight's Tour 代码陷入无限循环,无法解决

Kali 和BackTrack有啥不同吗?

Leetcode总结之Backtracking

Jan 23 - Gray Code; BackTracking;

复习backtracking 并总结