马的哈密尔顿回路(骑士周游)问题(+贪心优化)

(一)问题大意:

        这是一个很经典的问题,给定一个n*n的棋盘,一个马从任意位置出发,按照马移动的规则,在不重复走任意一个点的前提下走完所有点,即跳n*n步以后需要遍历了整个棋盘。

(二)问题分析:

        1、按照常规的思路,直接回溯搜索求可行解:棋盘规模小的时候可以得到结果,当n>8时就难以得到结果了。所以在各个OJ上看到的一般都是限制在n<=8的条件下求解。

        2、但是在我们OJ上发现有一道题,其限制为n<=15(其实也是以前某年的赛题,不过因年代久远找不到原题了),故上述方法显然行不通了,需要将其优化。在网上查了一点资料,发现这个问题可以贪心地进行选择,从而增大成功找到路径的概率,其基本思想为:当马从点(x,y)移动时,假如可以跳到k个可行的位置,那么先跳到最难达到的那个点。

        3.为什么那么选择呢?我给不出严格的证明,但是可以这么想:先把难到达的点走了,剩下的点都是比较容易到达的点,那么后面能走完的几率不就大一些吗。反之,如果先把容易到达的点给走了,最后面发现那些难到达的点不能满足(并且这个几率比较大),那么前面就会浪费非常多的步骤(基本就这个想法吧QWQ)。

        4、然后就是比较达到某个点难易程度:其实这个就是比较有多少个点能到达这个点,也就是比较从这个点出发能到达的点的数目,数目越少,说明到达这个点越困难。不难得到规律:越靠近边界的点越难到达,特别的有角落最难。

        5、再原代码的基础上加上这个贪心做法进行优化。

        6、这里给出原题目吧,毕竟我们校内OJ外网不能访问~

The knight
Time Limit: 1000ms, Special Time Limit:2500ms, Memory Limit:32768KB
Total submit users: 58, Accepted users: 34
Problem 10239 : Special judge
Problem description
Even paratroopers have vacations. The flight to Sirius in the depths of “The Admiral Brisco” Leo Hao whiled away with chessboard. No, he did not like usual chess game, and in addition, he did not have likely rival. The whole day Leo amused himself with an interesting thing: he tried to travel over all cells of the chessboard with the knight so that the knight visited each cell only one time. Leo attempted one time, then second, but always something was wrong. Leo became a little angry. Then he attempted board 4*4 instead of 8*8. Again failure after failure. A little angry, with the chessboard under his arm, Leo went to look for a local programmer. They two together indeed will solve this problem.
Input
There is only one number N (1 <= N <= 15) in the input.
Output
If it is possible to travel with the knight over the square field N×N cells, then output should contain N^2 lines with tour over the chessboard with mentioned property, otherwise the only word “IMPOSSIBLE”.
Sample Input
5
Sample Output
aA
bC
aE
cD
eE
dC
eA
cB
aC
bA
dB
eD
cE
aD
bB
dA
eC
dE
bD
aB
cA
eB
cC
bE
dD

(三)具体代码:

#include<iostream>
#include<cstring>
#include<string>
#include<cstdio>
#include<vector>
#include<cmath>
#include<algorithm>
using namespace std;
int vis[100][100],ok=0,n;
struct node{int x,y;};
struct NEXT{int num,dire;};
vector<node>Road;
bool cmp(NEXT n1,NEXT n2){return n1.num<n2.num;}
bool check(int x,int y){return x>=0&&x<n&&y>=0&&y<n;}
int dir[8][2]={2,-1,1,-2,-1,-2,-2,-1,-2,1,-1,2,1,2,2,1};
int getdir(int x,int y){                            //计算从(x,y)出发,能达到多少个点
    int res=0;
    for(int i=0;i<8;i++){
        int x1=x+dir[i][0];
        int y1=y+dir[i][1];
        if(check(x1,y1)&&!vis[x1][y1])res++;
    }
    return res;
}
void dfs(int x,int y,int steps){
    if(x<0||x>=n||y<0||y>=n||vis[x][y]||ok)return;
    vis[x][y]=1;steps++;
    Road.push_back((node){x,y});
    if(steps==n*n){
        for(int i=0;i<Road.size();i++)
            cout<<char(Road[i].x+'a')<<char(Road[i].y+'1')<<endl;
        ok=1;Road.pop_back();vis[x][y]=0;return;
    }
    vector<NEXT>Dire;
    for(int i=0;i<8;i++){
        int x1=x+dir[i][0];
        int y1=y+dir[i][1];
        if(check(x1,y1)&&!vis[x1][y1]){
            int cnt=getdir(x1,y1);
            Dire.push_back((NEXT){cnt,i});
        }
    }
    sort(Dire.begin(),Dire.end(),cmp);              //按到达的难易程度进行排序
    for(int i=0;i<Dire.size();i++)                  //按排完以后顺序进行搜索
        dfs(x+dir[Dire[i].dire][0],y+dir[Dire[i].dire][1],steps);
    vis[x][y]=0;
    Road.pop_back();
}
int main(){
    while(cin>>n){
        ok=0;dfs(0,0,0);
        if(!ok)cout<<"IMPOSSIBLE\n";
    }
    return 0;
}

(四)总结:

        1、算法分析课上面讨论过该问题的分治求法,先不管时间复杂度如何,求解过程总归比较繁琐。

        2、这样贪心选择以后,对于搜索的效率有了显著的提升(非常显著),我这里使用递归来写的,故无法开特别大的棋盘(爆栈),用循坏来写可以处理更大的规模。

        3、做完以后,对于这个效率还是非常惊讶的,没想到就这样一个贪心地选择一下,竟然可以达到这样地效果。

猜你喜欢

转载自blog.csdn.net/xbb224007/article/details/80294498