1、 原题中文大意;node
对于一个8*8的棋盘,用下列的方式编号 ios
若是它走63步正好通过除起点外的其余位置各一次,这样一种走法则称马的周游路线,设计一个算法,从给定的起点出发,找出它的一条周游路线。马的走法是“日”字形路线。 算法
Input数组
输入有若干行。每行一个整数N(1<=N<=64),表示马的起点。最后一行用-1表示结束,不用处理。 数据结构
Output函数
对输入的每个起点,求一条周游线路。对应地输出一行,有64个整数,从起点开始按顺序给出马每次通过的棋盘方格的编号。相邻的数字用一个空格分开。测试
2、 算法思想及解题用到的主要数据结构;优化
最本质的仍是图的遍历的问题。这里使用深度优先搜索,不用广度优先是由于一般在寻找路径或者迷宫之类的题目中,都是探寻的问题,用深度优先比广度优先容易比较快地找到解。spa
涉及到回溯算法,即走到一个“死胡同”后,返回上一步,选择其余方向。这里就是到达一个不能往其余任何方向走的位置。.net
回溯和深度优先肯定了,而后就是选择非递归算法,由于本身确实理解能力有限,递归的方法不能彻底想明白。因而就本身思考加上查阅资料,使用非递归算法。
其中要提升效率,须要剪枝,即每次选择下一位置时,要经过必定筛选策略选择比较快速高效的一步。这里选择下一步位置具备最少可行步数的一步。
主要数据结构:
1、路径的树节点,使用自定义结构体,
struct Node {
int x, y, num; // x, y 为矩阵坐标(0~7, 0~7 ,num为对应的数值,由x,y算出
int neighbor[8]; // 下一步数组,每个为对应的方向数组,在运算中使用坐标值,能够访问为0, 反之为1;
};
2、每个位置对应的走“日”字的方向数组
int dirx[8] = {2,1,-1,-2,-2,-1,1,2}; // x方向数组
int diry[8] = {-1,-2,-2,-1,1,2,2,1}; // y方向数组
queue<node> states;
3、记录访问顺序的数组
int seq[64]; // 记录走的过程
4、深度优先回溯中用到的栈
stack<Node> t_route; // 树节点栈
5、标记数组
int board[8][8]; // 记录已访问的位置,已访问为 1 ,未访问为0
3、 详细解题思路
1、 对于每组数据,首先把board访问数组清零,step值置0,声明一个Node栈的t_route。
将起始位置初始化为一个Node结点,压栈。
2、进入一个while循环,循环判断条件是栈不为空。
取栈顶元素,seq数组中step下标位置为改栈顶元素的num值,记录路径, step++。若是此时step的值等于64,则说明走完了整个棋盘,退出循环。
同时,board数组也将栈顶元素对应坐标位设为1(已访问)
3、针对栈顶元素,遍历它的8个邻居节点,在可行的邻居节点中选择可行步数最少的一个。
找到后,用flag记录该邻居节点的下标值,未找到则flag为-1
4、找到一个可行节点,将栈顶元素的neighbor数组对应下标位设为1(已访问)将该节点的值初始化(x,y,num,neighbor[8]),压栈。
栈顶元素没有一个可行节点,将step减1,board数组对应下标位置设为0,出栈,回溯。
5、判断step是否等于64,如果,则找到解,一次输出seq数组的值;反之,没有解,输出-1。
其中计算一个位置是否能够到达,调用函数canmove判断,判断条件为该位置坐标不越界合法而且board数组中对应下标位置为0。
每一个Node里面有二维数组的坐标值和自己数组,用一个转换函数xy_to_num,能够利用坐标值算出数值。
计算下一步的可行步数时,调用函数next_neighbor计算,函数中用canmove函数计算。
4、 逐步求精算法描述(含过程及变量说明)
变量及函数说明
// 非递归DFS中树节点的结构体
struct Node {
int x, y, num; // x, y 为矩阵坐标(0~7, 0~7 ,num为对应的数值,由x,y算出
int neighbor[8]; // 下一步数组,每个为对应的方向数组,在运算中使用坐标值,能够访问为0, 反之为1;
};
int dirx[8] = {2,1,-1,-2,-2,-1,1,2}; // x方向数组
int diry[8] = {-1,-2,-2,-1,1,2,2,1}; // y方向数组
int seq[64]; // 记录走的过程
int step; // 指向seq对应的下标进行赋值, 每走一步step + 1,回溯一次step - 1
int board[8][8]; // 记录已访问的位置,已访问为 1 ,未访问为0
int xy_to_num(int x, int y); // 计算坐标对应数值,参数为坐标
bool canmove(int x, int y); // 计算该位置是否可行,参数为坐标
int next_neighbor(int x, int y); // 计算下一位置的可行步数,参数为坐标
初始化起始位置结点,board数组,step;
标记board数组,初始结点压栈;
While(栈不为空) {
seq[step] = 栈顶元素的num;
board中栈顶元素设为已访问
step++;
min = 8; //初始化最小步数
if ( step == 64)
找到路径,退出循环;
遍历栈顶元素的邻居节点,计算邻居节点的可行步数
If(找到一个邻居节点){
栈顶元素的该邻居下标设为1,已访问。
初始化下一步节点的x, y, num, neighbor[8];
压栈
}
Else {
栈顶元素出栈;
Board对应位置设置为 0;
Step--;
}
}
If (step == 64) // 找到解
输出seq数组
Else
输出“-1“
5、 程序注释清单(重要过程的说明);
#include<iostream> #include<stdio.h> #include<string> #include<string.h> #include<cstring> #include<stack> using namespace std; // 非递归DFS中树节点的结构体 struct Node { int x, y, num; // x, y 为矩阵坐标(0~7, 0~7 ,num为对应的数值,由x,y算出 int neighbor[8]; // 下一步数组,每个为对应的方向数组,在运算中使用坐标值,能够访问为0, 反之为1; }; int dirx[8] = {2,1,-1,-2,-2,-1,1,2}; // x方向数组 int diry[8] = {-1,-2,-2,-1,1,2,2,1}; // y方向数组 int seq[64]; // 记录走的过程 int step; // 指向seq对应的下标进行赋值, 每走一步step + 1,回溯一次step - 1 int board[8][8]; // 记录已访问的位置,已访问为 1 ,未访问为0 int xy_to_num(int x, int y); // 计算坐标对应数值,参数为坐标 bool canmove(int x, int y); // 计算该位置是否可行,参数为坐标 int next_neighbor(int x, int y); // 计算下一位置的可行步数,参数为坐标 int main() { int N, min, flag, step, i, nextloc; while(scanf("%d", &N) && N != -1) { if (N >= 1 && N <= 64) { stack<Node> t_route; // 树节点栈 step = 0; memset(board, 0, sizeof(int) * 64); // 初始化根节点状态,压栈 Node ini; ini.y = (N-1) % 8; ini.x = (N-1) / 8; ini.num = N; for (i = 0; i < 8; i++) { if(canmove(ini.x + dirx[i], ini.y + diry[i])) ini.neighbor[i] = 0; else ini.neighbor[i] = 1; } t_route.push(ini); while (!t_route.empty()) { Node temp = t_route.top(); board[temp.x][temp.y] = 1; // 栈顶已访问 seq[step++] = temp.num; // 路径数组赋值 // 找到路径,退出 if (step == 64) break; flag = -1; // 记录有最少可行步数的邻居节点的下标 min = 8; // 最少步数 // 寻找最小步数的节点 for (i = 0; i < 8; i++) { if (temp.neighbor[i] == 0) { int t = next_neighbor(temp.x + dirx[i], temp.y + diry[i]); if (t <= min ) { min = t; flag = i; } } } // 找到最小步数的邻居节点 if (flag != -1) { temp.neighbor[flag] = 1; // 在栈顶结点中将该节点设置为已访问 // 初始化下一步节点的值,压栈 Node newnode; newnode.x = temp.x + dirx[flag]; newnode.y = temp.y + diry[flag]; newnode.num = xy_to_num(newnode.x, newnode.y); for (i = 0; i < 8; i++) { if(canmove(newnode.x + dirx[i], newnode.y + diry[i])) newnode.neighbor[i] = 0; else newnode.neighbor[i] = 1; } t_route.push(newnode); } // 栈顶节点没有能够行走的下一位置,出栈,设为未访问 else { t_route.pop(); board[temp.x][temp.y] = 0; step --; //路径数组下标值同步减1 } } // output if (step == 64) { for( i = 0; i < 63; i++) printf("%d ", seq[i]); printf("%d\n", seq[i]); } else printf("-1\n"); } // end of if (判断起始位置是否合法) else printf("-1\n"); } return 0; } int xy_to_num(int x, int y) { return x * 8 + y + 1; } bool canmove(int x, int y) { if(x >= 0 && x <= 7 && y >= 0 && y <= 7 && board[x][y] == 0) return true; return false; } int next_neighbor(int x, int y) { int i, num; for (i = 0, num = 0; i < 8; i++) if(canmove(x + dirx[i], y + diry[i])) num++; return num; }
6、 测试数据(5-10组有梯度的测试数据,要考虑边界条件)
1、考虑出发位置合法,位于角落位置时。
2、 考虑出发位置合法,位于边界位置时。
3、 考虑出发位置合法,位于中间位置时。
4、出发位置不合法:
5、调试,查看过程当中的回溯信息,在回溯的代码中增长输出:
else {
t_route.pop();
board[temp.x][temp.y] = 0;
step --; //路径数组下标值同步减1
cout << "back "<<endl;
}
从 1-64 逐个调试,发现只有21有回溯信息:
7、 对时间复杂度,空间复杂度方面的分析、估算及程序优化的分析和改进.
时间复杂度:
深度优先非递归实现的话,同样有最好和最坏的状况。对于n*n的棋盘。该题其实是一颗n叉树。最好状况就是一次性就找到。每一步有8次循环寻找下一步节点,一共找n*n-1次。因此时间复杂度为O(n2)。最坏的话,不太好分析,由于很差从数学上证实一个明确的发生回溯的个数,大概就是每层都要回溯(不过实际问题中确定没有每层都回溯),相似于满8叉数的深度优先遍历,则为O(8n*n)。
代码在sicily上耗时是0 s,仍是比较理想,可是若是棋盘更大一点就不必定了,网上资料说若是只用一次剪枝,那么20*20的棋盘就比较慢了。
空间复杂度:
这个空间复杂度就是树的深度,每一个结点使用空间为常数,则为O(n*n)。
程序优化分析改进:
1、一开始想用递归,但是一是由于本身不太明白,想不通递归过程,二是以为递归不太好控制,因而用了非递归。
2、剪枝方案只使用了最简单的一种,就是选择下一步可行位置最少的(Warnsdorff's rule)。在网上查阅了一些资料,发现还有一些剪枝方法:
1)使用Warnsdorff's rule剪枝后,若是能够优先选择的下一个位置不止一个,则优先选择离中心位置较远的位置做为下一步(即靠近边边的位置)。
通俗点理解,第一点的剪枝就是走那些位置可能走到机会比较小的,反正走到的机会小,那么先走完老是好的,要否则你兜一圈回来,仍是要走这一个位置的。
第二点的剪枝就是走法尽可能从边边走,而后是往中间靠。
2)第三点的剪枝,每次都从棋盘的中间点开始出发,而后求出一条合法路径后再平移映射回待求路径。
怎么理解呢?所谓马周游棋盘,最后还要回到起点。也就是在棋盘中找到一条哈密顿回路。那么无论你是从哪里开始的,最后都是会在这个哈密顿回路中的,那么选取的中点的位置也确定是在这个回路上的。
最后,找到这个这个以中点为起点的哈密顿回路后,根据设定起点在这个回路中的序号,映射回以这个位置为起点的马周游路线便可。
3、有一些变量上的细节问题。好比board数组由于只需用到两个值(0,1),因此能够为bool型,更节省空间。还有就是存储邻居节点的时候的数组也能够用bool型。
参考资料:
http://www.haogongju.net/art/2430132
http://huzhihang1103.blog.163.com/blog/static/19779176920135221340255/