基于贪心算法的马踏棋盘哈密顿回路问题
问题分析
马踏棋盘其实相当于一个解空间的搜索问题,而遍历到每一个节点并要求最后可以回到起点本质上是一个哈密顿回路问题
目前大部分马踏棋盘的相关问题并不要求回到起点,然后实际上在不重复的走完全盘的可行解中寻找最后可以回到起点的最优解更为困难。可行解有非常多个,然而最优解虽然比可行解少得多,但也并不少
问题在于,这个搜索空间十分的庞大,倘若不考虑棋盘位置约束与遍历次数限制,解空间大小将达到指数级的庞大规模。但是即便在这些解中加上棋盘限制与单次访问等的剪枝条件,为了在可行解(64个节点仅各访问一次)中搜寻到可以回到起点的最优解仍然是非常困难的。倘若仅仅固定八个可行方向的顺序,使用深度优先的递归来实施搜索,效率将会非常低下。
这是因为对于靠近边界的四个顶点的区域来说,如果不能尽快的遍历完这些区域,而是按照固定的顺序来遍历会十分容易造成“死节点”,将来不可能再次访问。而与此同时,程序还在徒劳的往更深的方向搜索,极大的影响搜索效率。
贪心算法的改进
为了尽量较快的遍历完一个边界区域,我们应当尽量“靠边走”。故而有以下的贪心策略。再对八个方向进行顺序的考虑时,优先选取拥有较少可行孙节点的子节点方向(边界区域),并给每一个方向一个权值由于方向的排序。代码如下
void sort_direction(int x, int y) {
int x_new, y_new, xx_new, yy_new;
int score[Row] = { Row,Row,Row,Row,Row,Row,Row,Row}; // 定义不同方向的得分权值
for (int i = 0; i < Row; i++) {
int count = 0;
x_new = x + deta_x[i];
y_new = y + deta_y[i];
if (check(x_new, y_new) == 1) { // 检查点a[xx][yy]是否满足条件
for (int j = 0; j < Columns; j++) {
xx_new = x_new + deta_x[j];
yy_new = y_new + deta_y[j];
if (check(xx_new, yy_new) == 1) // 检查点a[xx][yy]是否满足条件
count++;
}
}
score[i] = count;
}
sort_index(score, sorted, Row); // 快速排序
}
基于此思想再结合深度优先的递归算法基本可以秒出(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 |
完整代码见文末
###边界顶点的优化
即便基于以上贪心算法改进后,边界上的四个顶点仍然较难求出最优解。归其本质为边界顶点有且仅有两个节点联通,故而若唯一的回路在中间递归过程被占用的话便永远也回不到起点。故而优化方式便是强行定义一个规则:让唯一的回路关键节点最后一次访问
if (x_0 == 1 && y_0 == 1) {
if (deep <= Total_step - 1)
qipan[2][1] = 1;
else qipan[2][1] = 0;
}
if (x_0 == 1 && y_0 == Columns) {
if (deep <= Total_step - 1)
qipan[2][Columns-2] = 1;
else qipan[2][Columns - 2] = 0;
}
if (x_0 == Row && y_0 == Columns) {
if (deep <= Total_step - 1)
qipan[Row-3][Columns - 2] = 1;
else qipan[Row - 3][Columns - 2] = 0;
}
if (x_0 == Row && y_0 == 1) {
if (deep <= Total_step - 1)
qipan[Row - 2][2] = 1;
else qipan[Row - 2][ 2] = 0;
}
加上以上改进后四个节点的一条哈密顿解亦可秒出
上图便是规模为8时各个顶点不重复的走完全盘但是没有回到起点的次数、可以看出边界区域上的回溯次数明显大于中心区域的点。特别的,(8,7)这个点的回溯次数更是高达5000多,但是从理论上棋盘应该是对称的。
显然这个回溯次数极高的情况应该是本代码的一个bug,希望读者可以帮忙指正,未完待续~
问题规模的推广
显然规模为8还只是一种较为简单的情形。本代码测试时发现规模小于12时可以较快的得到一个哈密顿解。若不要求重新回到起点的话,规模最大可达60。这也是以后需要优化的地方。
因为目前使用的贪心算法主要通过优化边缘路径来提高搜索效率,当矩阵维数过高时中间的点的优先权值都将等于8,贪心也就失去了意义,故而可以通过分块处理采用分治的方法来处理,未完待续~