1. 程式人生 > >The knight\'s tour(馬周遊問題)

The knight\'s tour(馬周遊問題)

三個策略:

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;//輸出演算法所需要的時間
}