三个策略:
1、先从中心点开始走;
2、往靠边走;
3、对下一步进行评分,低分的先走。
/* 马周游问题,m*n的棋盘,放置在其上的马能否恰好访问每一个方格一次并回到起始位置 深度优先搜索,若寻找到满足要求的解,则输出;否则推回上一层往下一个方向搜索。(非递归) 对于当前所在位置(x,y),依次枚举8个方向搜索,直到找到一组可行解为止。 使用剪枝有3处: 第一、使用Warnsdorff’s rule,枚举当前解得时候优先选择下一步可行步数最少的方向; 第二、若第一点中的方向存在不止一个,则优先选择离中心位置较远的方向; 第三、每次都从中心点开始出发,求出一条合法路径后再平移映射回待求路径。 */
#include<iostream>
#include<ctime>
#include<cstdlib>
#include<iomanip>
#include<algorithm>//sort()函数头文件
using namespace std;
int m,n;//棋盘大小设置为m*n
int midx,midy;//计算棋盘的中心坐标
int dirx[8]={-2,-1,2,1,-2,-1,2,1};//x,y表示马在棋盘中跳步
int diry[8]={-1,-2,-1,-2,1,2,1,2};//左上->左下->右上->右下
int num;//计数器,记录步数
bool visit[10][10];//标志是否游历
int chessboard[10][10];//棋盘存放跳马的顺序
int direction[100],bn[100];//方向组数为10*10,因此该马周游的棋盘最大为10*10
struct Node {
int x, y;
Node(int xx = 0, int yy = 0):x(xx), y(yy) {}//构造函数
}BeforeStep[100];//存放前一步坐标
struct Data {//存放坐标及出口数
int x, y, c;
Data(int xx = 0, int yy = 0, int cc = 0):x(xx), y(yy), c(cc) {}
bool operator < (const Data & b) const {//比较出口数时,以出口数为参考值
if (c != b.c) return c < b.c;//当出口数不等时,返回小于
return abs(x - midx) + abs(y - midy) > abs(b.x - midx) + abs(b.y - midy);//当出口数相等时,返回距离中点最远的出口
}
}b[100][8], *tb;
bool check(int x, int y) //检查出路是否符合要求
{
if (x < 1 || x > n || y < 1 || y > n) return 0;//检查是否超出棋盘
if (visit[x][y]) return 0;//是否已经走过
return 1;
}
bool find(int x, int y) //判断最后的坐标能否返回起点
{
for (int i = 0; i < 8; ++i)
if (x + dirx[i] == midx && y + diry[i] == midy)//最后回到起点
return 1;
return 0;
}
bool travel(int x,int y)
{
int i,j,change,nx,ny,mx,my,ndir;
num=1;//记录走的步数
visit[x][y]=1;
chessboard[x][y]=0;
BeforeStep[num]=Node(x,y);
direction[num]=-1;
while(num)
{
if(num==m*n && find(BeforeStep[num].x,BeforeStep[num].y))//num=m*n且能回到起点,则完成
return true;
if(num == m*n)//1、走完棋盘,却不能回到起点则剪枝
{
visit[BeforeStep[num].x][BeforeStep[num].y]=0;
--num;
}
else if(direction[num]==-1)//2、检查当前坐标每一个方向的出口数
{
x=BeforeStep[num].x;
y=BeforeStep[num].y;
change=0;
tb=b[num];
for(i=0;i<8;++i)//依次走8个方向
{
nx=x+dirx[i];
ny=y+diry[i];
if(!check(nx,ny))
continue;
ndir=0;//出口数
for(j=0;j<8;++j)
{
mx=nx+dirx[j];
my=ny+diry[j];
if(check(mx,my))
++ndir;
}
tb[change++]=Data(nx,ny,ndir);//存储当前每一方向的情况
}
if (change) {//对下一步的坐标进行选择,出口少的优先选择
bn[num] = change;//每一步可以选择的出口数
sort(tb, tb + change);//存储下一步坐标,以及对该坐标路数(tb[tbn])进行升序排序
tb = b[num];
i = ++direction[num];
visit[ tb[0].x ][ tb[0].y ] = 1;
chessboard[ tb[0].x ][ tb[0].y ] = num;
BeforeStep[++num] = Node(tb[0].x, tb[0].y);//记录前一步的坐标
direction[num] = -1;//初始化下一步坐标
} else {//如果下一步的出口数为0则剪枝
visit[ BeforeStep[num].x ][ BeforeStep[num].y ] = 0;
--num;
}
} else if (direction[num] == bn[num] - 1) {//3、无路可走时,则剪枝
visit[ BeforeStep[num].x ][ BeforeStep[num].y ] = 0;
--num;
} else {//4、剪枝后,接着走下一方向的路
tb = b[num];
i = ++direction[num];
visit[ tb[i].x ][ tb[i].y ] = 1;
chessboard[ tb[i].x ][ tb[i].y ] = num;
BeforeStep[++num] = Node(tb[i].x, tb[i].y);
direction[num] = -1;
}
}
return 0;
}
void output(int Sx,int Sy)//将棋盘的数值映射回对应的起点坐标并输出
{/*原理:比如1、2、3、4四个数,如果是2开始的,则运用映射将2转变为1,原先的1变为4,路径还是不变的*/
int k;
k=m*n-chessboard[Sx][Sy];//计算对应的映射参数(m*n-1)
for(int i=1;i<=m;i++)
{
cout<<endl;
for(int j=1;j<=n;j++)
{
chessboard[i][j]=(chessboard[i][j]+k)%(m*n)+1;
cout<<setw(8)<<chessboard[i][j];
}
cout<<endl;
}
}
void main()
{
int Sx,Sy;//起始位置
cout<<"Input the chessboard size(m*n):";//棋盘为长方形
cin>>m>>n;
midx=m/2;
midy=n/2;//计算中心坐标
bool flag=true;
time_t start,end;//计算时间的开始和结束
cout<<"The size of chessboard:"<<m<<"*"<<n<<";"<<endl;
while(flag)
{
cout<<"Input the start position(Sx,Sy):"<<endl;
cin>>Sx>>Sy;
if(Sx>m||Sx<1||Sy>n||Sy<1)
cout<<"Error!"<<endl;//输入不符合棋盘需要重新输入
else
flag=false;
}
flag=false;
start=clock();
flag=travel(midx,midy);//调用深度遍历算法
end=clock();
cout<<"The program is running..."<<endl;
if(flag==true)
output(Sx,Sy);
else
cout<<"no solution!"<<endl;
cout<<endl;
cout<<"It's cost time:"<<difftime(end,start)<<"ms"<<endl;//输出算法所需要的时间
}