您的位置:首页 > 其它

<OJ_Sicily>1152简单的马周游问题

2016-04-25 14:08 417 查看
Description
在一个5 * 6的棋盘中的某个位置有一只马,如果它走29步正好经过除起点外的其他位置各一次,这样一种走法则称马的周游路线,试设计一个算法,从给定的起点出发,找出它的一条周游路线。
为了便于表示一个棋盘,我们按照从上到下,从左到右对棋盘的方格编号,如下所示:
1 2 3 4 5 6
7 8 9 10 11 12
13 14 15 16 17 18
19 20 21 22 23 24
25 26 27 28 29 30
马的走法是“日”字形路线,例如当马在位置15的时候,它可以到达2、4、7、11、19、23、26和28。但是规定马是不能跳出棋盘外的,例如从位置1只能到达9和14。
Input
输入有若干行。每行一个整数N(1<=N<=30),表示马的起点。最后一行用-1表示结束,不用处理。
Output
对输入的每一个起点,求一条周游线路。对应地输出一行,有30个整数,从起点开始按顺序给出马每次经过的棋盘方格的编号。相邻的数字用一个空格分开。

#include <string.h>
#include <stdlib.h>
#define M 5
#define N 6
#define num M*N
using namespace std;
bool map[M]
;
int Jump[8][2] = {{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1},{-2,-1}};   //对于1个位置可以有8种方法走日
int path[30];    //用于存储输出结果
int currentIndex ;
//构造坐标点结构体
struct coordinate{
public:
int m_x;
int m_y;
coordinate():m_x(0),m_y(0){};
coordinate(int x, int y):m_x(x),m_y(y){};
int getIndex(){return (m_x * N + m_y + 1);};
};

bool canJump(int x,int y,int t){
if (x >= 0 && x < M && y >= 0 && y < N) {     //该位置还在棋盘内
int index = x * N + y + 1;
for (int i = 0; i <= t; i++) {
if (index == path[i]) {   //该位置已经走过
return false;
}
}
return true;
}
return false;
}

void printPath(){                       //结果输出
for (int i = 0; i < num-1; i++) {
cout << path[i] << " ";
}
cout << path[num -1] << endl;
}
int Backtrack(coordinate ci,int t){
path[t] = ci.getIndex();
if (t >= num-1) {
return 1;
}
else{
int k = 0;
struct coordinate nextStep[8];
for (int i = 0; i < 8; i ++) {
if(canJump(ci.m_x + Jump[i][0],ci.m_y + Jump[i][1],t)){
nextStep[k].m_x = ci.m_x + Jump[i][0];
nextStep[k].m_y = ci.m_y + Jump[i][1];
k++;
}
}
for (int i = 0; i < k; i++) {
coordinate d = nextStep[i];
if(Backtrack(d, t+1) ==1)return 1;
}
}
return 0;
}
int main(int argc, const char * argv[]) {
// insert code here...
int begin;
while (cin >> begin) {
if (begin == -1 || begin < 1 || begin > 30) break;
else{
memset(map, false, sizeof(map));
memset(path, 0, sizeof(path));
currentIndex = 0;
int x = (begin-1) / N ;             //备注1
int y = begin - x*N -1;
coordinate tmp(x,y);
if(Backtrack(tmp,0) == 1)printPath();
}
}
return 0;
}


后记:

1.一直没有通过,最后发现在备注1那里,根据位置号求其在矩阵中位置的时候,写成int x = begin / M了,导致结果一直出错

2.由于这里只需要输出一种解,在进行回溯递归的时候会输出所有的情况,因此对函数Backtrack使用了返回值来判断是否已经得出一种解。刚开始的时候使用的是exit(1)函数来跳出递归函数,但是发现exit(1)是直接中断整个程序,对于多个输入无法处理。

在上面问题的基础上,我们原来的棋盘不再是5x6了,而是8x8,当再使用以上代码对8X8棋盘进行相同的操作,结果发现,搜索出一个结果需要很长的时间,这已经超出了时间限制。因此对于8x8棋盘,我们需要在选择下一步的时候外加一些条件,使得在最快的时间内找出一种解。

解题思路:我们发现,在上面的解法当中,我们是将下一步可拓展的点存入到一个数组中,然后逐个对下一个点进行Backtrack函数操作。为了节省时间,我们对候选点进行一个筛选。比如说,现在处于A点,并且下一步可以调到B,C,D点中的任何一个点。棋盘为5x6的做法就是直接按照顺序选B,C,D点,现在我们需要从B,C,D点钟选择一个点,我们首先分别对B,C,D点进行查询,查出对于B,C,D点来说有多少个候选点。假如B,C,D点的候选点个数分别是4,3,2,那么我们选择候选点数最少的点作为下一个点,也就是我们选择了D作为下一个点。换句话说,我们先走可选择最少的路,这样才能更快地从不可行的道路中跳出来。

代码解释:在下面的代码中,选择用一个vector<pair<int,int>>来存放候选点的相关信息,其实pair中的第一个int表示候选点的相对于当前点的偏移位置的索引(因为每一个当前点都有8个走法),第二个int表示候选点的可走路线数。

#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <vector>
#include <string.h>
#include <map>
#define M 8
#define N 8
#define NUM M*N

using namespace std;
int Jump[8][2] = {{-1,-2},{1,-2},{2,-1},{2,1},{1,2},{-1,2},{-2,1},{-2,-1}};   // 记录马跳位置偏移路径
int path[NUM];
struct coordinate{       //定义一个坐标结构体,存放棋盘位置
int m_x;
int m_y;
coordinate(int x,int y):m_x(x),m_y(y){};
};
typedef pair<int, int> PAIR;
int cmp(const PAIR &x, const PAIR &y){   // 定义sort函数中的比较函数,实现对候选点的候选路线数进行排序
return x.second < y.second;
}

bool canJump(int x,int y, int t){        // 下一步是否可行
if (x < 0 || x >= M || y < 0 || y >= N)return false;   // 是否在棋盘内
else{
int index = x * N + y + 1;
for (int i = 0; i <= t; i ++) {   // 是否已经跳过
if (index == path[i]) {
return false;
}
}
}
return true;
}
int stepNum(int x, int y,int t){     //计算某一个点的可拓展点数
int sum = 0;
for (int i = 0; i < 8; i++) {    // 对该点的周围8个位置进行遍历,要是可拓展则可拓展点数+1
if (canJump(x + Jump[i][0], y + Jump[i][1], t)) {
sum ++;
}
}
return sum;
}
void printPath(){                     //打印出结果
for (int i = 0; i <NUM-1; i++) {
cout << path[i] << " ";
}
cout << path[NUM -1] << endl;
}
int Backtrack(coordinate ci, int t){
path[t] = ci.m_x * N + ci.m_y + 1;
if (t >= NUM - 1) {
return 1;   // 在这里表示已经有一种走法出现了,此时需要不断地往上跳出
}
else{
vector<PAIR>nextstep;   // 用于存储下一步相关数据(pair的第一个数是可行拓展点的偏移索引i,第二个数表示可行拓展点的可行拓展数
for (int i = 0; i < 8; i ++) {
if (canJump(ci.m_x + Jump[i][0], ci.m_y + Jump[i][1], t)) {
nextstep.push_back(make_pair(i,stepNum(ci.m_x + Jump[i][0],ci.m_y + Jump[i][1], t)));
}
}
sort(nextstep.begin(), nextstep.end(),cmp); // 根据选择可行拓展点的可行拓展数进行排序(从小到大)
for (vector<PAIR>::iterator iter = nextstep.begin(); iter != nextstep.end(); ++ iter){
int tmpindx = iter->first;              //获得下一个可行拓展点的偏移索引
coordinate tmpcoor(ci.m_x + Jump[tmpindx][0], ci.m_y + Jump[tmpindx][1] );
if(Backtrack(tmpcoor, t+1) == 1)return 1;
}
}
return 0;
}
int main(int argc, const char * argv[]) {
// insert code here...
int begin;
while (cin >> begin) {
if (begin == -1 || begin < 1 || begin > NUM) break;
else{
memset(path, 0, sizeof(path));
int x = (begin -1) /N ;   // 根据位置数计算坐标点
int y = begin - x * N -1;
coordinate tmp(x,y);
if (Backtrack(tmp, 0)) {
printPath();
}
else cout << "no result..." << endl;
}
}
return 0;
}


后记:

1.在第二次做马周游问题的时候,发现根据标号计算棋盘的x,y位置有缺陷,因此修改代码1中的关于知道标号求位置的代码

2.对于第二次做马周游问题,虽然同样是用了DFS深度优先搜索,但是在选择下一个候选点的时候添加了条件使得搜索时间大大下降

3.对于使用vector和map,开始的时候使用的是map容器来存放候选点信息,但是发现map实现map的key排序是有点麻烦,既然vector能够存放pair,那么就可以很容易通过vector的sort函数实现排序,能够快速从众多候选点中找到线路最少的候选点。

代码新手,有什么建议和意见欢迎提出
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: