1152简单的马周游问题

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的时候,它可以到达2471119232628。但是规定马是不能跳出棋盘外的,例如从位置1只能到达914

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][N];
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)是直接中断整个程序,对于多个输入无法处理。

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

解题思路:我们发现,在上面的解法当中,我们是将下一步可拓展的点存入到一个数组中,然后逐个对下一个点进行Backtrack函数操作。为了节省时间,我们对候选点进行一个筛选。比如说,现在处于A点,并且下一步可以调到B,C,D点中的任何一个点。棋盘为5×6的做法就是直接按照顺序选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函数实现排序,能够快速从众多候选点中找到线路最少的候选点。


代码新手,有什么建议和意见欢迎提出

    原文作者:骑士周游问题
    原文地址: https://blog.csdn.net/vipin_pei/article/details/51241853
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞