PTA-马周游问题

一、题目

任给大小为n×n(8≤n≤1000)的棋盘, 在棋盘上任意设定马的初始位置,试快速找出一种马能跳到每个棋格恰好一次的棋步(要求最后回到初始位置)。注意,棋盘上马按照跳“日”的规则移动。例如,在8×8的棋盘, 当马位于第3行第4列的方格时,下一步可以跳到箭头所指的八个方格之一。
PTA-马周游问题_第1张图片
输入格式:
在一行中给出三个正整数n,a,b,其中n(8≤n≤1000)为棋盘的行列大小, a和b(1≤a,b≤n)分别是马的初始位置所在方格的行号和列号。

输出格式:
输出占n行,每行含有n个正整数,组成一个n× n方阵。该方阵的n^2
个元素由正整数1,2,…,n^2 组成,其中马走第i步前所处方格其内对应的数为i(i=1,2,…, n*n ) (马在初始位置所处方格内数为1)。
PTA-马周游问题_第2张图片

二、代码

/*
    描述:马周游问题,warnsdorff剪枝+远离中心点剪枝,可行
        oj过了两个点,n中等规模会超时和n较大规模会段错误

    思路:递归回溯+warnsdorff剪枝+远离中心点剪枝
        1、递归回溯:void rec_travel(int i_now,int j_now,int steps)
            (1)递归的出口:
                1)steps代表着步数,当steps等于格子总数的时候,代表着格子被填满了
                2)不仅格子要被填满,而且要能够回到起点,所以要写一个can_back()函数判断能否回到起起点
                3)满足前二者的时候,说明找到了可行解,那么输出,并且exit(0)
            (2)递归+回溯
                1)用for循环循环8次,代表着马可以跳去的八个位置,然后对以上八个位置做安全判断
                因为可能会超出棋盘或者要跳去的位置已经存在了值
                2)若是可以跳去的位置(i_next,j_next)是安全的,那么,给该位置赋值
                graph[i_next][j_next]=steps+1;然后递归:rec_travel(i_next,j_next,steps+1);
                递归完成后,那么回溯:graph[i_next][j_next]=0;
        2、warnsdorff剪枝:
            (1)剪枝想法:在选择now点可以跳去的八个位置中,优先选择下一个位置Next走法最少的那个
            作为当前位置now的下一位置Next。所以对八个位置做个排序,按照如上的规则排序
            (2)实现:
                1)使用node自定义结构,保存横纵坐标(i,j)
                2)写一个cmp_node,按照如上的规则写好
                3)使用vector保存八个位置,然后sort(,cmp_node)对数组排序
                4)将1(2)中的for循环循环八个位置,换成循环2(2)3)中的vector
        3、远离中心点剪枝:
            (1)剪枝想法:
                2剪枝之后,若优先选择的下一个位置不止一个,则优先选择离中心位置较远的位置作为下一步
                (即靠近边边的位置)。通俗点理解,第一点的剪枝就是走那些位置可能走到机会比较小的,
                反正走到的机会小,那么先走完总是好的,要不然你兜一圈回来,还是要走这一个位置的。
                第二点的剪枝就是走法尽量从边走,然后是往中间靠。
            (2)实现:
                1)在cmp_node()函数中增加即可,当2剪枝中,比较的两个点它们的可去的位置相同的时候,
                再比较一次他们离中心的位置,写一个distance_center()函数,获取它们离中心的距离,
                距离远的优先
    oj结果:
        (1)使用剪枝2,可以通过第一个测试点
        (2)使用剪枝3,可以通过第二个测试点。
        (3)目前使用本代码,通过了前两个测试点
        (4)第三个测试点(graph_size中等规模)报错运行超时
        (5)第四个测试点(graph_size大规模)报错段错误

*/
#include
#include
#include 
#include 
#include 
using namespace std;

int x_steps[]={-2,-2,-1,-1,1,1,2,2};
int y_steps[]={1,-1,2,-2,2,-2,1,-1};
int graph_size=0;
int cnt=0;
int i_begin,j_begin;
int i_j_center;
vector<vector<int>>graph(1001,vector<int>(1001,0));//棋盘的初始化

//存储节点的横纵坐标
struct node
{
    int i;
    int j;
};

//计算点到中心点的距离,用于第二种剪枝
int distance_center(int i_now,int j_now)
{
    int res;
    int a=i_now-i_j_center;
    int b=j_now-i_j_center;
    res=a*a+b*b;
    return res;
}

//用于判断该位置是否合法
bool is_safe(int i,int j)
{
    if(i<0||j<0||i>=graph_size||j>=graph_size) 
    //判断是否越界
        return false;
    if(graph[i][j]!=0) 
    //判断是否有值存在
        return false;
    return true;
}

//获取该位置八个去向中,可去(为0)的数量
int safenode_nums(int i_now,int j_now)
{
    int res=0;
    for(int i=0;i<8;i++)
    {
        int i_next=i_now+x_steps[i];
        int j_next=j_now+y_steps[i];
        if(is_safe(i_next,j_next)) 
        {
            res++;
        }
    }
    return res;
}

//用于比较哪个点的可去节点,少的排在前面
bool cmp_node(node n1,node n2)
{
    int safenode_nums1=safenode_nums(n1.i,n1.j);
    int safenode_nums2=safenode_nums(n2.i,n2.j);
    if(safenode_nums1<safenode_nums2) return true;
    if(safenode_nums1==safenode_nums2)
    //如果第一种剪枝得出的结果相同,进行第二种剪枝
    //计算出到中心点的距离,远的在前面
    {
        int distance1=distance_center(n1.i,n1.j);
        int distacne2=distance_center(n2.i,n2.j);
        return distance1>distacne2;
    }
    return false;
}

//输出
void output()
{
    for(int i=0;i<graph_size;i++)
    {
        for(int j=0;j<graph_size;j++)
        {
            //cout<
            //cout<
            printf("%d ",graph[i][j]);
        }
        cout<<endl;
    }
}

//用于判断是否可以回到起点
bool can_back(int i_now,int j_now)
{
    for(int i=0;i<8;i++)
    {
        int i_next=i_now+x_steps[i];
        int j_next=j_now+y_steps[i];
        if(i_next==i_begin&&j_next==j_begin) return true;
    }
    return false;
}

void rec_travel(int i_now,int j_now,int steps)
{
    //如果棋盘走满了,且同时可以回到起点,那就输出,退出程序;否则return回溯
    if(steps==graph_size*graph_size) 
    {
        if(can_back(i_now,j_now)==true)
        {
            output();
            exit(0);
        }
        return;
    }

    //warnsdorff算法(第一种剪枝)+远离中心点(第二种剪枝),对每一个点可去的那八个点排序
    vector<node>v;
    for(int i=0;i<8;i++)
    //将可去的八个点放入数组中
    {
        int i_next=i_now+x_steps[i];
        int j_next=j_now+y_steps[i];
        if(is_safe(i_next,j_next))
        //如果该点是合法的,编入node,塞进vector
        {
            node n_temp;
            n_temp.i=i_next;
            n_temp.j=j_next;
            v.push_back(n_temp);
        }
    }

    //使用cmp_node方法进行比对剪枝,然后排序
    sort(v.begin(),v.end(),cmp_node);

    //循环v数组中排好序的节点,并递归
    for(int k=0;k<v.size();k++)
    {
        int i_next=v[k].i;
        int j_next=v[k].j;
        if(is_safe(i_next,j_next))
        //如果下一个点安全,那么递归,递归结束后回溯
        {
            graph[i_next][j_next]=steps+1;
            rec_travel(i_next,j_next,steps+1);
            graph[i_next][j_next]=0;
        }
    }

    return;
}

int main()
{
    //输入数据
    cin>>graph_size>>i_begin>>j_begin;

    //计算中心点坐标,用于第二次剪枝
    i_j_center=graph_size/2;

    //因为数组下标从0开始,所以--
    i_begin--;j_begin--;

    //设置初始位置
    graph[i_begin][j_begin]=1;

    //递归找出路径并输出
    rec_travel(i_begin,j_begin,1);

    return 0;
}

三、改进

还需要剪枝算法,因为不能够AC,只通过了两个测试点。
PTA-马周游问题_第3张图片

你可能感兴趣的:(SCNU_PTA,算法,数据结构)