问题分析
马踏棋盘其实相当于一个解空间的搜索问题,而遍历到每一个节点并要求最后可以回到起点本质上是一个哈密顿回路问题
目前大部分马踏棋盘的相关问题并不要求回到起点,然后实际上在不重复的走完全盘的可行解中寻找最后可以回到起点的最优解更为困难。
问题在于,这个搜索空间十分的庞大,倘若不考虑棋盘位置约束与遍历次数限制,解空间大小将达到指数级的庞大规模。但是即便在这些解中加上棋盘限制与单次访问等的剪枝条件,为了在可行解(64 个节点仅各访问一次)中搜寻到可以回到起点的最优解仍然是非常困难的。倘若仅仅固定八个可行方向的顺序,使用深度优先的递归来实施搜索,效率将会非常低下。
这是因为对于靠近边界的四个顶点的区域来说,如果不能尽快的遍历完这些区域,而是按照固定的顺序来遍历会十分容易造成“死节点”,将来不可能再次访问。而与此同时,程序还在徒劳的往更深的方向搜索,极大的影响搜索效率。
贪心算法的改进
为了尽量较快的遍历完一个边界区域,我们应当尽量“靠边走”。故而有以下的贪心策略。再对八个方向进行顺序的考虑时,优先选取拥有较少可行孙节点的子节点方向(边界区域),并给每一个方向一个权值由于方向的排序。代码如下
1 | void sort_direction(int x, int y) { |
基于此思想再结合深度优先的递归算法基本可以秒出( 0.02s 以内)绝大多数点(除去四个边界顶点与 (8,7) 这个点)的哈密顿回路。
42 | 33 | 26 | 9 | 62 | 13 | 28 | 11 |
---|---|---|---|---|---|---|---|
25 | 8 | 41 | 58 | 27 | 10 | 63 | 14 |
34 | 43 | 32 | 61 | 40 | 59 | 12 | 29 |
7 | 24 | 57 | 50 | 31 | 64 | 15 | 52 |
44 | 35 | 46 | 39 | 60 | 51 | 30 | 1 |
23 | 6 | 49 | 56 | 47 | 18 | 53 | 16 |
36 | 45 | 4 | 21 | 38 | 55 | 2 | 19 |
5 | 22 | 37 | 48 | 3 | 20 | 17 | 54 |
完整代码见文末
边界顶点的优化
即便基于以上贪心算法改进后,边界上的四个顶点仍然较难求出最优解。归其本质为边界顶点有且仅有两个节点联通,故而若唯一的回路在中间递归过程被占用的话便永远也回不到起点。故而优化方式便是强行定义一个规则: 让唯一的回路关键节点最后一次访问
1 | if (x_0 == 1 && y_0 == 1) { |
加上以上改进后四个节点的一条哈密顿解亦可秒出
上图便是各个顶点不重复的走完全盘但是没有回到起点的次数、可以看出边界区域上的回溯次数明显大于中心区域的点。特别的,(8,7) 这个点的回溯次数更是高达 5000 多,但是从理论上棋盘应该是对称的。
显然这个回溯次数极高的情况应该是本代码的一个bug, 希望读者可以帮忙指正,未完待续~
完整代码
新手程序写的很冗长,大佬勿喷
1 |
|